【Python爬虫】第13篇:scrapy项目配置和数据获取。从0到scrapy高手笔记(附代码,可自取)

举报
程序员一诺112 发表于 2023/11/29 20:28:03 2023/11/29
【摘要】 我们知道常用的流程web框架有django、flask,那么接下来,我们会来学习一个全世界范围最流行的爬虫框架scrapy 内容 * scrapy的概念作用和工作流程 * scrapy的入门使用 * scrapy构造并发送请求 * scrapy模拟登陆 * scrapy管道的使用 * scrapy中间件的使用 * scrapy_redis概念作用和流程 * scrapy_

本文主要学习一下关于爬虫的相关前置知识和一些理论性的知识,通过本文我们能够知道什么是爬虫,都有那些分类,爬虫能干什么等,同时还会站在爬虫的角度复习一下http协议。


全套笔记和代码自取地址: 请移步这里

感兴趣的小伙伴可以自取哦,欢迎大家点赞转发~



共 8 章,37 子模块




scrapy爬虫框架



介绍


我们知道常用的流程web框架有django、flask,那么接下来,我们会来学习一个全世界范围最流行的爬虫框架scrapy


内容


  • scrapy的概念作用和工作流程

  • scrapy的入门使用

  • scrapy构造并发送请求

  • scrapy模拟登陆

  • scrapy管道的使用

  • scrapy中间件的使用

  • scrapy_redis概念作用和流程

  • scrapy_redis原理分析并实现断点续爬以及分布式爬虫

  • scrapy_splash组件的使用

  • scrapy的日志信息与配置

  • scrapyd部署scrapy项目


scrapy官方文档


https://scrapy-chs.readthedocs.io/zh_CN/0.24/index.html


scrapy的概念和流程




学习目标:


  1. 了解 scrapy的概念

  2. 了解 scrapy框架的作用

  3. 掌握 scrapy框架的运行流程

  4. 掌握 scrapy中每个模块的作用



1. scrapy的概念


Scrapy是一个Python编写的开源网络爬虫框架。它是一个被设计用于爬取网络数据、提取结构性数据的框架。

Scrapy 使用了Twisted['twɪstɪd]异步网络框架,可以加快我们的下载速度。

Scrapy文档地址:http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html


2. scrapy框架的作用


少量的代码,就能够快速的抓取


3. scrapy的工作流程




3.1 回顾之前的爬虫流程



3.2 上面的流程可以改写为



3.3 scrapy的流程



其流程可以描述如下:


  1. 爬虫中起始的url构造成request对象-->爬虫中间件-->引擎-->调度器

  2. 调度器把request-->引擎-->下载中间件--->下载器

  3. 下载器发送请求,response响应---->下载中间件---->引擎--->爬虫中间件--->爬虫

  4. 爬虫提取url地址,组装成request对象---->爬虫中间件--->引擎--->调度器,重复步骤2

  5. 爬虫提取数据--->引擎--->管道处理和保存数据


注意:


  • 图中中文是为了方便理解后加上去的

  • 图中绿色线条的表示数据的传递

  • 注意图中中间件的位置,决定了其作用

  • 注意其中引擎的位置,所有的模块之前相互独立,只和引擎进行交互


3.4 scrapy的三个内置对象


  • request请求对象:由url method post_data headers等构成

  • response响应对象:由url body status headers等构成

  • item数据对象:本质是个字典


3.5 scrapy中每个模块的具体作用



注意:


  • 爬虫中间件和下载中间件只是运行逻辑的位置不同,作用是重复的:如替换UA等



小结


  1. scrapy的概念:Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架

  2. scrapy框架的运行流程以及数据传递过程:

    1. 爬虫中起始的url构造成request对象-->爬虫中间件-->引擎-->调度器

    2. 调度器把request-->引擎-->下载中间件--->下载器

    3. 下载器发送请求,response响应---->下载中间件---->引擎--->爬虫中间件--->爬虫

    4. 爬虫提取url地址,组装成request对象---->爬虫中间件--->引擎--->调度器,重复步骤2

    5. 爬虫提取数据--->引擎--->管道处理和保存数据

  3. scrapy框架的作用:通过少量代码实现快速抓取

  4. 掌握scrapy中每个模块的作用: 引擎(engine):负责数据和信号在不腰痛模块间的传递 调度器(scheduler):实现一个队列,存放引擎发过来的request请求对象 下载器(downloader):发送引擎发过来的request请求,响应,并将响应交给引擎 爬虫(spider):处理引擎发过来的response,提取数据,提取url,并交给引擎 管道(pipeline):处理引擎传递过来的数据,比如存储 下载中间件(downloader middleware):可以自定义的下载扩展,比如设置代理ip 爬虫中间件(spider middleware):可以自定义request请求和进行response过滤,与下载中间件作用重复



scrapy的入门使用




学习目标:


  1. 掌握 scrapy的安装

  2. 应用 创建scrapy的项目

  3. 应用 创建scrapy爬虫

  4. 应用 运行scrapy爬虫

  5. 应用 scrapy定位以及提取数据或属性值的方法

  6. 掌握 response响应对象的常用属性



1 安装scrapy


命令: sudo apt-get install scrapy 或者: pip/pip3 install scrapy


2 scrapy项目开发流程


  1. 创建项目: scrapy startproject mySpider

  2. 生成一个爬虫: scrapy genspider itcast itcast.cn

  3. 提取数据: 根据网站结构在spider中实现数据采集相关内容

  4. 保存数据: 使用pipeline进行数据后续处理和保存


3. 创建项目


通过命令将scrapy项目的的文件生成出来,后续步骤都是在项目文件中进行相关操作,下面以抓取传智师资库来学习scrapy的入门使用:http://www.itcast.cn/channel/teacher.shtml

创建scrapy项目的命令: scrapy startproject <项目名字> 示例: scrapy startproject myspider

生成的目录和文件结果如下:


4. 创建爬虫


通过命令创建出爬虫文件,爬虫文件为主要的代码作业文件,通常一个网站的爬取动作都会在爬虫文件中进行编写。

命令: 在项目路径下执行: scrapy genspider <爬虫名字> <允许爬取的域名>

爬虫名字: 作为爬虫运行时的参数 允许爬取的域名: 为对于爬虫设置的爬取范围,设置之后用于过滤要爬取的url,如果爬取的url与允许的域不通则被过滤掉。

示例:

cd myspider
    scrapy genspider itcast itcast.cn


生成的目录和文件结果如下:


5. 完善爬虫


在上一步生成出来的爬虫文件中编写指定网站的数据采集操作,实现数据提取


5.1 在/myspider/myspider/spiders/itcast.py中修改内容如下:


import scrapy
​
class ItcastSpider(scrapy.Spider):  # 继承scrapy.spider
    # 爬虫名字 
    name = 'itcast' 
    # 允许爬取的范围
    allowed_domains = ['itcast.cn'] 
    # 开始爬取的url地址
    start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
​
    # 数据提取的方法,接受下载中间件传过来的response
    def parse(self, response): 
        # scrapy的response对象可以直接进行xpath
        names = response.xpath('//div[@class="tea_con"]//li/div/h3/text()') 
        print(names)
​
        # 具体数据文本的方式如下
        # 分组
        li_list = response.xpath('//div[@class="tea_con"]//li') 
        for li in li_list:
            # 创建一个数据字典
            item = {}
            # 利用scrapy封装好的xpath选择器定位元素,并通过extract()或extract_first()来结果
            item['name'] = li.xpath('.//h3/text()').extract_first() # 老师的名字
            item['level'] = li.xpath('.//h4/text()').extract_first() # 老师的级别
            item['text'] = li.xpath('.//p/text()').extract_first() # 老师的介绍
            print(item)



注意:


  • scrapy.Spider爬虫类中必须有名为parse的解析

  • 如果网站结构层次比较复杂,也可以自定义其他解析函数

  • 在解析函数中提取的url地址如果要发送请求,则必须属于allowed_domains范围内,但是start_urls中的url地址不受这个限制,我们会在后续的本文中学习如何在解析函数中构造发送请求

  • 启动爬虫的时候注意启动的位置,是在项目路径下启动

  • parse()函数中使用yield返回数据,注意:解析函数中的yield能够传递的对象只能是:BaseItem, Request, dict, None


5.2 定位元素以及提取数据、属性值的方法


解析并scrapy爬虫中的数据: 利用xpath规则字符串进行定位和提取

  1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法

  2. 额外方法extract():返回一个包含有字符串的列表

  3. 额外方法extract_first():返回列表中的第一个字符串,列表为空没有返回None


5.3 response响应对象的常用属性


  • response.url:当前响应的url地址

  • response.request.url:当前响应对应的请求的url地址

  • response.headers:响应头

  • response.requests.headers:当前响应的请求头

  • response.body:响应体,也就是html代码,byte类型

  • response.status:响应状态码


6 保存数据


利用管道pipeline来处理(保存)数据


6.1 在pipelines.py文件中定义对数据的操作


  1. 定义一个管道类

  2. 重写管道类的process_item方法

  3. process_item方法处理完item之后必须返回给引擎

import json
​
class ItcastPipeline():
    # 爬虫文件中提取数据的方法每yield一次item,就会运行一次
    # 该方法为固定名称函数
    def process_item(self, item, spider):
        print(item)
        return item



6.2 在settings.py配置启用管道


ITEM_PIPELINES = {
    'myspider.pipelines.ItcastPipeline': 400
}


配置项中键为使用的管道类,管道类使用.进行分割,第一个为项目目录,第二个为文件,第三个为定义的管道类。 配置项中值为管道的使用顺序,设置的数值约小越优先执行,该值一般设置为1000以内。


7. 运行scrapy


命令:在项目目录下执行scrapy crawl <爬虫名字>

示例:scrapy crawl itcast



小结


  1. scrapy的安装:pip install scrapy

  2. 创建scrapy的项目: scrapy startproject myspider

  3. 创建scrapy爬虫:在项目目录下执行 scrapy genspider itcast itcast.cn

  4. 运行scrapy爬虫:在项目目录下执行 scrapy crawl itcast

  5. 解析并scrapy爬虫中的数据:

    1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法

    2. extract() 返回一个包含有字符串的列表

    3. extract_first() 返回列表中的第一个字符串,列表为空没有返回None

  6. scrapy管道的基本使用:

    1. 完善pipelines.py中的process_item函数

    2. 在settings.py中设置开启pipeline

  7. response响应对象的常用属性

    1. response.url:当前响应的url地址

    2. response.request.url:当前响应对应的请求的url地址

    3. response.headers:响应头

    4. response.requests.headers:当前响应的请求头

    5. response.body:响应体,也就是html代码,byte类型

    6. response.status:响应状态码




scrapy数据建模与请求




学习目标:


  1. 应用 在scrapy项目中进行建模

  2. 应用 构造Request对象,并发送请求

  3. 应用 利用meta参数在不同的解析函数中传递数据



1. 数据建模


通常在做项目的过程中,在items.py中进行数据建模


1.1 为什么建模


  1. 定义item即提前规划好哪些字段需要抓,防止手误,因为定义好之后,在运行过程中,系统会自动检查

  2. 配合注释一起可以清晰的知道要抓取哪些字段,没有定义的字段不能抓取,在目标字段少的时候可以使用字典代替

  3. 使用scrapy的一些特定组件需要Item做支持,如scrapy的ImagesPipeline管道类,百度搜索了解更多


1.2 如何建模


在items.py文件中定义要提取的字段:

class MyspiderItem(scrapy.Item): 
    name = scrapy.Field()   # 讲师的名字
    title = scrapy.Field()  # 讲师的职称
    desc = scrapy.Field()   # 讲师的介绍



1.3 如何使用模板类


模板类定义以后需要在爬虫中导入并且实例化,之后的使用方法和使用字典相同

job.py:

from myspider.items import MyspiderItem   # 导入Item,注意路径
...
    def parse(self, response)
​
        item = MyspiderItem() # 实例化后可直接使用
​
        item['name'] = node.xpath('./h3/text()').extract_first()
        item['title'] = node.xpath('./h4/text()').extract_first()
        item['desc'] = node.xpath('./p/text()').extract_first()
​
        print(item)


注意:

  1. from myspider.items import MyspiderItem这一行代码中 注意item的正确导入路径,忽略pycharm标记的错误

  2. python中的导入路径要诀:从哪里开始运行,就从哪里开始导入


1.4 开发流程总结


  1. 创建项目 scrapy startproject 项目名

  2. 明确目标 在items.py文件中进行建模

  3. 创建爬虫 3.1 创建爬虫

scrapy genspider 爬虫名 允许的域

3.2 完成爬虫

修改start_urls
 检查修改allowed_domains
 编写解析方法


  1. 保存数据 在pipelines.py文件中定义对数据处理的管道 在settings.py文件中注册启用管道


2. 翻页请求的思路


对于要提取如下图中所有页面上的数据该怎么办?

回顾requests模块是如何实现翻页请求的:

  1. 找到下一页的URL地址

  2. 调用requests.get(url)

scrapy实现翻页的思路:

  1. 找到下一页的url地址

  2. 构造url地址的请求对象,传递给引擎


3. 构造Request对象,并发送请求




3.1 实现方法


  1. 确定url地址

  2. 构造请求,scrapy.Request(url,callback)

    • callback:指定解析函数名称,表示该请求返回的响应使用哪一个函数进行解析

  3. 把请求交给引擎:yield scrapy.Request(url,callback)


3.2 网易招聘爬虫


通过爬取网易招聘的页面的招聘信息,学习如何实现翻页请求

地址:https://hr.163.com/position/list.do


思路分析:


  1. 首页的数据

  2. 寻找下一页的地址,进行翻页,数据


注意:


  1. 可以在settings中设置ROBOTS协议

  
  
# False表示忽略网站的robots.txt协议,默认为True
  
  
ROBOTSTXT_OBEY = False


  1. 可以在settings中设置User-Agent:

  
  
# scrapy发送的每一个请求的默认UA都是设置的这个User-Agent
  
  
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'



3.3 代码实现


在爬虫文件的parse方法中:

......
    # 提取下一页的href
    next_url = response.xpath('//a[contains(text(),">")]/@href').extract_first()
​
    # 判断是否是最后一页
    if next_url != 'javascript:void(0)':
​
        # 构造完整url
        url = 'https://hr.163.com/position/list.do' + next_url
​
        # 构造scrapy.Request对象,并yield给引擎
        # 利用callback参数指定该Request对象之后的响应用哪个函数进行解析
        yield scrapy.Request(url, callback=self.parse)
......



3.4 scrapy.Request的更多参数


scrapy.Request(url[,callback,method="GET",headers,body,cookies,meta,dont_filter=False])



参数解释


  1. 中括号里的参数为可选参数

  2. callback:表示当前的url的响应交给哪个函数去处理

  3. meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,比如下载延迟,请求深度等

  4. dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动

  5. method:指定POST或GET请求

  6. headers:接收一个字典,其中不包括cookies

  7. cookies:接收一个字典,专门放置cookies

  8. body:接收json字符串,为POST的数据,发送payload_post请求时使用(在下一章节中会介绍post请求)


4. meta参数的使用


meta的作用:meta可以实现数据在不同的解析函数中的传递

在爬虫文件的parse方法中,提取详情页增加之前callback指定的parse_detail函数:

def parse(self,response):
    ...
    yield scrapy.Request(detail_url, callback=self.parse_detail,meta={"item":item})
...
​
def parse_detail(self,response):
    #之前传入的item
    item = resposne.meta["item"]



特别注意


  1. meta参数是一个字典

  2. meta字典中有一个固定的键proxy,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件的学习中进行介绍



小结


  1. 完善并使用Item数据类:

    1. 在items.py中完善要爬取的字段

    2. 在爬虫文件中先导入Item

    3. 实力化Item对象后,像字典一样直接使用

  2. 构造Request对象,并发送请求:

    1. 导入scrapy.Request类

    2. 在解析函数中提取url

    3. yield scrapy.Request(url, callback=self.parse_detail, meta={})

  3. 利用meta参数在不同的解析函数中传递数据:

    1. 通过前一个解析函数 yield scrapy.Request(url, callback=self.xxx, meta={}) 来传递meta

    2. 在self.xxx函数中 response.meta.get('key', '') 或 response.meta['key'] 的方式取出传递的数据



参考代码


wangyi/spiders/job.py

import scrapy
​
​
class JobSpider(scrapy.Spider):
    name = 'job'
    # 2.检查允许的域名
    allowed_domains = ['163.com']
    # 1 设置起始的url
    start_urls = ['https://hr.163.com/position/list.do']
​
    def parse(self, response):
        # 所有的职位节点列表
        node_list = response.xpath('//*[@class="position-tb"]/tbody/tr')
        # print(len(node_list))
​
        # 遍历所有的职位节点列表
        for num, node in enumerate(node_list):
            # 索引为值除2取余为0的才是含有数据的节点,通过判断进行筛选
            if num % 2 == 0:
                item = {}
​
                item['name'] = node.xpath('./td[1]/a/text()').extract_first()
                item['link'] = node.xpath('./td[1]/a/@href').extract_first()
                item['depart'] = node.xpath('./td[2]/text()').extract_first()
                item['category'] = node.xpath('./td[3]/text()').extract_first()
                item['type'] = node.xpath('./td[4]/text()').extract_first()
                item['address'] = node.xpath('./td[5]/text()').extract_first()
                item['num'] = node.xpath('./td[6]/text()').extract_first().strip()
                item['date'] = node.xpath('./td[7]/text()').extract_first()
                yield item
​
        # 翻页处理
        # 翻页url
        part_url = response.xpath('//a[contains(text(),">")]/@href').extract_first()
​
        # 判断是否为最后一页,如果不是最后一页则进行翻页操作
        if part_url != 'javascript:void(0)':
            # 拼接完整翻页url
            next_url = 'https://hr.163.com/position/list.do' + part_url
​
            yield scrapy.Request(
                url=next_url,
                callback=self.parse
            )


wangyi/items.py

class WangyiItem(scrapy.Item):
    # define the fields for your item here like:
​
    name = scrapy.Field()
    link = scrapy.Field()
    depart = scrapy.Field()
    category = scrapy.Field()
    type = scrapy.Field()
    address = scrapy.Field()
    num = scrapy.Field()
    date = scrapy.Field()



scrapy模拟登陆




学习目标:


  1. 应用 请求对象cookies参数的使用

  2. 了解 start_requests函数的作用

  3. 应用 构造并发送post请求



1. 回顾之前的模拟登陆的方法




1.1 requests模块是如何实现模拟登陆的?


  1. 直接携带cookies请求页面

  2. 找url地址,发送post请求存储cookie


1.2 selenium是如何模拟登陆的?


  1. 找到对应的input标签,输入文本点击登陆


1.3 scrapy的模拟登陆


  1. 直接携带cookies

  2. 找url地址,发送post请求存储cookie


2. scrapy携带cookies直接需要登陆后的页面




应用场景


  1. cookie过期时间很长,常见于一些不规范的网站

  2. 能在cookie过期之前把所有的数据拿到

  3. 配合其他程序使用,比如其使用selenium把登陆之后的cookie到保存到本地,scrapy发送请求之前先读取本地cookie


2.1 实现:重构scrapy的starte_rquests方法


scrapy中start_url是通过start_requests来进行处理的,其实现代码如下

  
  
# 这是源代码
  
  
def start_requests(self):
    cls = self.__class__
    if method_is_overridden(cls, Spider, 'make_requests_from_url'):
        warnings.warn(
            "Spider.make_requests_from_url method is deprecated; it "
            "won't be called in future Scrapy releases. Please "
            "override Spider.start_requests method instead (see %s.%s)." % (
                cls.__module__, cls.__name__
            ),
        )
        for url in self.start_urls:
            yield self.make_requests_from_url(url)
    else:
        for url in self.start_urls:
            yield Request(url, dont_filter=True)


所以对应的,如果start_url地址中的url是需要登录后才能访问的url地址,则需要重写start_request方法并在其中手动添加上cookie


2.2 携带cookies登陆github


测试账号 noobpythoner zhoudawei123

import scrapy
import re
​
class Login1Spider(scrapy.Spider):
    name = 'login1'
    allowed_domains = ['github.com']
    start_urls = ['https://github.com/NoobPythoner'] # 这是一个需要登陆以后才能访问的页面
​
    def start_requests(self): # 重构start_requests方法
        # 这个cookies_str是抓包的
        cookies_str = '...' # 抓包
        # 将cookies_str转换为cookies_dict
        cookies_dict = {i.split('=')[0]:i.split('=')[1] for i in cookies_str.split('; ')}
        yield scrapy.Request(
            self.start_urls[0],
            callback=self.parse,
            cookies=cookies_dict
        )
​
    def parse(self, response): # 通过正则表达式匹配用户名来验证是否登陆成功
        # 正则匹配的是github的用户名
        result_list = re.findall(r'noobpythoner|NoobPythoner', response.body.decode()) 
        print(result_list)
        pass



注意:


  1. scrapy中cookie不能够放在headers中,在构造请求的时候有专门的cookies参数,能够接受字典形式的coookie

  2. 在setting中设置ROBOTS协议、USER_AGENT


3. scrapy.Request发送post请求


我们知道可以通过scrapy.Request()指定method、body参数来发送post请求;但是通常使用scrapy.FormRequest()来发送post请求


3.1 发送post请求


注意:scrapy.FormRequest()能够发送表单和ajax请求,参考阅读 https://www.jb51.net/article/146769.htm


3.1.1 思路分析


  1. 找到post的url地址:点击登录按钮进行抓包,然后定位url地址为https://github.com/session

  2. 找到请求体的规律:分析post请求的请求体,其中包含的参数均在前一次的响应中

  3. 否登录成功:通过请求个人主页,观察是否包含用户名



3.1.2 代码实现如下:


import scrapy
import re
​
class Login2Spider(scrapy.Spider):
   name = 'login2'
   allowed_domains = ['github.com']
   start_urls = ['https://github.com/login']
​
   def parse(self, response):
       authenticity_token = response.xpath("//input[@name='authenticity_token']/@value").extract_first()
       utf8 = response.xpath("//input[@name='utf8']/@value").extract_first()
       commit = response.xpath("//input[@name='commit']/@value").extract_first()
​
        #构造POST请求,传递给引擎
       yield scrapy.FormRequest(
           "https://github.com/session",
           formdata={
               "authenticity_token":authenticity_token,
               "utf8":utf8,
               "commit":commit,
               "login":"noobpythoner",
               "password":"***"
           },
           callback=self.parse_login
       )
​
   def parse_login(self,response):
       ret = re.findall(r"noobpythoner|NoobPythoner",response.text)
       print(ret)



小技巧


在settings.py中通过设置COOKIES_DEBUG=TRUE 能够在终端看到cookie的传递传递过程



小结


  1. start_urls中的url地址是交给start_request处理的,如有必要,可以重写start_request函数

  2. 直接携带cookie登陆:cookie只能传递给cookies参数接收

  3. scrapy.Request()发送post请求




未完待续, 同学们请等待下一期


全套笔记和代码自取地址: 请移步这里

感兴趣的小伙伴可以自取哦,欢迎大家点赞转发~

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。