FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • Linux音视频

  • Docker

  • Python环境

  • Python爬虫

    • Python - 爬虫基础与requests模块
    • Python - 爬虫之数据提取
    • Python - 爬虫之Selenium
    • Python - 爬虫之Scrapy
      • 一、scrapy 概念和流程
        • 1、概念
        • 2、工作流程
        • 3、各模块的具体作用
      • 二、scrapy 入门使用
        • 1、安装 scrapy
        • 2、项目开发流程
        • 3、三个内置对象
        • 1)定位元素以及提取数据
        • 2)response 响应对象的常用属性
        • 4、入门实战
      • 三、scrapy 数据建模(items)
        • 1、数据建模
      • 四、scrapy 处理翻页(Request)
        • 1、思路
        • 2、实现步骤
        • 3、实战(网易招聘爬虫)
        • 4、scrapy.Request 的更多参数
      • 五、scrapy 模拟登录(cookie)
        • 1、模拟登录的不同实现方式
        • 2、scrapy 直接携带 cookies 获取需要登录信息的页面
        • 2、scrapy.Request 发送 post 请求
      • 六、scrapy 管道的使用
        • 1、pipeline 中常用的方法
        • 2、pipeline 判断数据来源
        • 3、pipeline 使用注意点
      • 七、scrapy 中间件
        • 1、介绍
        • 2、使用方法
        • 3、实现随机 User-Agent 下载中间件
        • 4、实现 Selenium 渲染的下载中间件
  • 后端
  • Python爬虫
FullStackAction
2021-07-13
目录

Python - 爬虫之Scrapy

欢迎关注微信公众号:[FSA全栈行动 👋]

# 一、scrapy 概念和流程

# 1、概念

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

  • 作用:少量的代码,就能够快速的抓取
  • 官方文档:https://scrapy-chs.readthedocs.io/zh_CN/0.24/ (opens new window)

补充:Scrapy 使用了 Twisted 异步网络框架,可以加快下载速度

# 2、工作流程

scrapy流程图

其流程描述如下:

  1. 爬虫中起始的 url 构造成 request 对象 --> 爬虫中间件 --> 引擎 --> 调度器
  2. 调度器把 request --> 引擎 --> 下载中间件 --> 下载器
  3. 下载器发送请求,获取 response 响应 --> 下载中间件 --> 引擎 --> 爬虫中间件 --> 爬虫
  4. 爬虫提取 url 地址,组装成 request 对象 --> 爬虫中间件 --> 引擎 --> 调度器,重复步骤 2
  5. 爬虫提取数据 --> 引擎 --> 管道处理和保存数据

注意:

  • 图中绿色线条表示数据的传递
  • 图中中间件的位置决定了其作用
  • 引擎处于 4 个模块中间,各个模块之间相互独立,只和引擎进行交互

# 3、各模块的具体作用

模块 作用 是否实现
Scrapy Engine(引擎) 总指挥:负责数据和信号在不同模块之间传递 scrapy 已经实现
Scheduler(调度器) 一个队列,存放引擎发过来的 request 请求 scrapy 已经实现
Downloader(下载器) 下载把引擎发过来的 request 请求,并返回给引擎 scrapy 已经实现
Spider(爬虫) 处理引擎发来的 response,提取数据、url,并交给引擎 需要手写
Item Pipeline(管道) 处理引擎传过来的数据,比如存储 需要手写
Downloader Middlewares(下载中间件) 可以自定义的下载扩展,比如设置代理 一般不用手写
Spider Middlewares(爬虫中间件) 可以自定义 requests 请求和进行 response 过滤 一般不用手写

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

# 二、scrapy 入门使用

# 1、安装 scrapy

scrapy 有 2 种安装方式:

  • 命令:

    sudo apt-get install scrapy
    
  • pip:

    pip/pip3 install scrapy
    

# 2、项目开发流程

  1. 创建项目:

    scrapy startproject <项目名称>
    
    eg:
    	scrapy startproject myspider
    
  2. 生成一个爬虫:

    scrapy genspider <爬虫名字> <允许爬取的域名>
    
    eg:
    	cd myspider
    	scrapy genspider example example.com
    
    • 爬虫名字:作用爬虫运行时的参数

    • 允许爬取的域名:为对于爬虫设置的爬取范围,设置之后用于过滤要爬取的 url,如果爬取的 url 与允许的域不同则被过滤掉

      注意:执行完命令后,myspider/spiders 目录下,会多出一个 example.py 爬虫文件

  3. 提取数据:

    根据网站结构在 spider 中实现数据采集相关内容

  4. 保存数据:

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

  5. 运行 scrapy

    scrapy crawl <爬虫名字>
    scrapy crawl <爬虫名字> --nolog
    
    eg:
    	scrapy crawl example
    

    注意 :需要在项目录下执行命令;--nolog 可以关闭框架日志信息输出

# 3、三个内置对象

  • request 请求对象:由 url method post_data headers 等构成
  • response 响应对象:由 url body status headers 等构成
  • item 数据对象:本质是字典

# 1)定位元素以及提取数据

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

  • response.xpath():返回一个类似 List 的类型,其中包含的是 selector 对象,操作和列表一样,但是有一些额外方法:
    • extract():返回一个包含有字符串的列表
    • extract_first():返回列表中的第一个字符串,列表为空则返回 None

# 2)response 响应对象的常用属性

  • response.url:当前响应的 url 地址
  • response.request.url:当前响应对应的请求的 url 地址
  • response.headers:响应头
  • response.request.headers:当前响应的请求头
  • response.body:响应体,也就是 html 代码,byte 类型
  • response.status:响应状态码

# 4、入门实战

# 1)创建项目&爬虫

scrapy startproject myspider
cd myspider
scrapy genspider itcast itcast.cn

# 2)完善爬虫(抓取数据)

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

import scrapy

class ItcastSpider(scrapy.Spider):
    name = 'itcast'
    # 2.检查域名
    allowed_domains = ['itcast.cn']
    # 1.修改起始url
    start_urls = ['http://itcast.cn/channel/teacher.shtml']  # 设置起始的url,我们只需要设置就好,通常会被自动的创建成请求发送

    # 3.在parse方法中实现爬取逻辑
    def parse(self, response):
        # 解析方法,通常用于起始url对应响应的解析
        # 定义对于网站的相关操作
        # with open('itcast.html', 'wb') as f:
        #     f.write(response.body)

        # 获取所有老师节点
        node_list = response.xpath('//div[@class="li_txt"]')

        # 遍历老师节点列表
        for node in node_list:
            temp = {}

            # xpath方法返回的是选择器对象列表,extract()用于从选择器对象中提取数据
            # temp['name'] = node.xpath('./h3/text()')[0].extract()
            # temp['title'] = node.xpath('./h4/text()')[0].extract()
            # temp['desc'] = node.xpath('./p/text()')[0].extract()

            # xpath结果为只含有一个值的列表,可以使用extract_first(),如果为多个值则使用extract()
            temp['name'] = node.xpath('./h3/text()').extract_first()
            temp['title'] = node.xpath('./h4/text()').extract_first()
            temp['desc'] = node.xpath('./p/text()').extract_first()

            # {'name': [<Selector xpath='./h3/text()' data='黄老师'>], 'title': [<Selector xpath='./h4/text()' data='高级讲师'>], 'desc': [<Selector xpath='./p/text()' data='15年+的软件开发与教学经验,参与中国联通VOP系统,中国联通在线计费系统...'>]}
            # extract()之后:
            # {'name': '黄老师', 'title': '高级讲师', 'desc': '15年+的软件开发与教学经验,参与中国联通VOP系统,中国联通在线计费系统,道路交通事故救助基金管理系统等的开发工作;\r\n拥有丰富的教学经验。'}
            print(temp)

            yield temp

注意:

  • scrapy.Spider爬虫类中必须有名为 parse 的解析
  • 如果网站结构层次比较复杂,也可以自定义其他解析函数
  • 在解析函数中提取的 url 地址如果要发送请求,则必须属于 allowed_domains 范围内,但是 start_urls 中的 url 地址不受这个限制
  • parse() 函数中使用 yield 返回数值。
  • yield 能够传递的对象只能是:BaseItem,Request,dict,None

# 3)完善管道(保存数据)

  1. 定义一个管道类
  2. 重写管道类的 process_item() 方法
  3. process_item() 方法处理完 item 之后必须返回给引擎

这里直接使用创建项目时默认创建好的管道类:/myspider/myspider/pipelines/MyspiderPipeline,修改内容如下:

import json


class MyspiderPipeline:

    def __init__(self):
        self.file = open('itcast.json', 'w')

    def process_item(self, item, spider):
        # print('item:', item)
        # 将字典数据序列化
        json_data = json.dumps(item, ensure_ascii=False) + ',\n'

        # 将数据写入文件
        self.file.write(json_data)

        # 默认使用完管道之后需要将数据返回给引擎
        return item

    def __del__(self):
        self.file.close()

# 4)启用管道

ITEM_PIPELINES = {
   'myspider.pipelines.MyspiderPipeline': 300,
   # 'myspider.pipelines.MyspiderPipelineOther': 301,
}
  • 配置项中,键为使用的管道类,管道类引用声明使用.进行分割,分别为:项目目录.文件.管理类。
  • 配置项中,值为管道的使用顺序,设置的数值越小越先执行,该值一般设置在 1000 以内。

# 5)运行爬虫

在控制台运行如下命令即可运行爬虫:

scrapy crawl itcast
scrapy crawl itcast --nolog

# 三、scrapy 数据建模(items)

# 1、数据建模

通常在项目开发过程中,需要在 items.py 中进行数据建模

# 1)为什么要建模

  • 定义 item 即提前规划好哪些字段需要抓取,防止手误,因为定义好之后,在运行过程中,系统会自动检查
  • 配置注释一起可以清晰的知道要抓取哪些字段,没有定义的字段不能抓取,在目标字段少的时候可以使用字典代替
  • 使用 scrapy 的一些特定组件中需要 Item 做支持,如 scrapy 的 ImagesPipeline 管道类。

# 2)如何建模

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

import scrapy

class MyspiderItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()  # 讲师的名字
    title = scrapy.Field()  # 讲师的职称
    desc = scrapy.Field()  # 讲师的介绍

# if __name__ == '__main__':
#     item = MyspiderItem()
#     item['name'] = 'lqr'  # ok
#     item['nama'] = 'lqr'  # KeyError: 'MyspiderItem does not support field: nama'

注意:scrapy.Item 可以理解为一个更高级的 “字典”,可以对键名进行限制、校验。但切记它不是字典,如果你需要对字典进行操作,可以使用 dict() 将scrapy.Item 进行强制转换。

# 3)使用模板类

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

# 如果报包报错有2种解决办法:
# 1、PyCharm以myspider为根目录重新打开项目
# 2、不以myspider为根目录的话,可以右键myspider-->Make Directory as-->Sources Root
from myspider.items import MyspiderItem

    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()
		...
        yield item

# 4)开发流程总结

  1. 创建项目

    scrapy startproject 项目名
    
  2. 明确目标

    • 在 items.py 文件中进行建模
  3. 创建爬虫

    • 创建爬虫

      scrapy genspider 爬虫名 允许的域
      
    • 编写爬虫

      修改start_urls
      检查修改allowed_domains
      编写解析方法
      
  4. 保存数据

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

# 四、scrapy 处理翻页(Request)

# 1、思路

  1. 找到下一页的 url 地址
  2. 构造 url 地址的请求对象,传递给引擎

# 2、实现步骤

  1. 确定 url 地址

  2. 构造请求

    scrapy.Request(url, callback)
    
    • callback:指定解析函数名称,表示该请求返回的响应使用哪一个函数进行解析
  3. 把请求交给引擎

# 3、实战(网易招聘爬虫)

# 1)思路分析

  1. 获取首页数据
  2. 寻找下一页地址,进行翻页,获取数据

# 2)代码实现

  • 创建项目
scrapy startproject wangyi

cd wangyi
scrapy gespider job 163.com
  • 定义模板类
import scrapy


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()
  • 编写爬虫(抓取数据)
import scrapy
from wangyi.items import WangyiItem


class JobSpider(scrapy.Spider):
    name = 'job'
    # 2.检查修改allowed_domains
    allowed_domains = ['163.com']
    # 1.修改start_urls
    start_urls = ['https://hr.163.com/position/list.do']

    def parse(self, response):
        # 提取数据
        # 获取所有职位节点列表
        node_list = response.xpath('//*[@class="position-tb"]/tbody/tr')

        # 遍历节点列表
        for num, node in enumerate(node_list):
            # 设置过滤条件,将目标节点获取出来
            if num % 2 == 0:
                item = WangyiItem()
                item['name'] = node.xpath('./td[1]/a/text()').extract_first()
                # item['link'] = 'https://hr.163.com' + node.xpath('./td[1]/a/@href').extract_first()
                # response.urljoin()用于拼接相对路径的url,可以理解为自动补全
                item['link'] = response.urljoin(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()
                # print(item)
                yield item

        # 模拟翻页
        part_url = response.xpath('/html/body/div[2]/div[2]/div[2]/div/a[last()]/@href').extract_first()
        # 判断终止条件
        if part_url != 'javascript:void(0)':
            next_url = response.urljoin(part_url)
            # 构建请求对象,并且返回给引擎
            yield scrapy.Request(
                url=next_url,
                callback=self.parse
            )

注意:引擎根据爬虫 yield 的对象类型,将对象分配给对应的模块处理,比如:如果是模板类对象或字典,则会交给管道(Item Pipeline);如果是 Request 对象,则会交给队列(Scheduler)。

  • 编写管道(存储数据)
import json


class WangyiPipeline:

    def __init__(self):
        self.file = open('wangyi.json', 'w')

    def process_item(self, item, spider):
        item = dict(item)

        str_data = json.dumps(item, ensure_ascii=False) + ',\n'

        self.file.write(str_data)

        return item

    def __del__(self):
        self.file.close()
  • 启用管道
ITEM_PIPELINES = {
    'wangyi.pipelines.WangyiPipeline': 300,
}
  • 运行爬虫
scrapy crawl job --nolog

# 3)扩展:

  • 可以在 settings 中设置 ROBOTS 协议

    # False表示忽略网站的robots.txt协议,默认为True
    ROBOTSTXT_OBEY = False
    
  • 可以在 settings 中设置 User-Agent:

    # scrapy发送的每一个请求的默认UA都是设置的这个User-Agent
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'
    

# 4、scrapy.Request 的更多参数

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

注意:中括号[]里的参数为可选参数

参数解释:

  • callback:表示当前 url 的响应交给哪个函数去处理
  • meta:实现数据在不同的解析函数中传递,meta 默认带有部分数据,比如下载延迟,请求深度等
  • dont_filter:默认为 False,会过滤请求的 url 地址,即请求过的 url 地址不会继续被请求,对需要重复请求的 url 地址可以把它设置为 True,比如贴吧的翻页请求,页面的数据总是在变化,start_urls 中的地址会被反复请求,否则程序不会启动
  • method:指定 POST 或 GET 请求
  • headers:接收一个字典,其中不包括 cookies
  • cookies:接收一个字典,专门放置 cookies
  • body:接收 json 字符串,为 POST 的数据,发送 payload_post 请求时使用

# 1)meta 参数的使用

  • meta 的使用:

    • meta 可以实现数据在不同的解析函数中传递
  • meta 的注意事项:

    • meta 参数是一个字典
    • meta 字典中有一个固定的键 proxy,表示代理 ip
  • 使用举例:

def parse(self, response):
    ...
    yield scrapy.Request(detail_url, callback=self.parse_detail, meta={'item': item})
    ...


def parse_detail(self,response):
    # 获取之前传入的item
    item = response.meta['item']

# 五、scrapy 模拟登录(cookie)

# 1、模拟登录的不同实现方式

  • requests 模块
    • 直接携带 cookies 请求页面
    • 找到 url 地址,发送 post 请求存储 cookie
  • selenium(浏览器自动处理 cookie)
    • 找到对应的 input 标签,输入文本点击登录
  • scrapy
    • 直接携带 cookies
    • 找 url 地址,发送 post 请求存储 cookie

# 2、scrapy 直接携带 cookies 获取需要登录信息的页面

应用场景:

  • cookie 过期时间很长,常见于一些不规范的网站
  • 能在 cookie 过期之前把所有的数据拿到
  • 配合其他程序使用,比如其使用 selenium 把登录之后的 cookie 获取到并保存到本地,scrapy 发送请求之前先读取本地 cookie

# 1、start_url 携带 cookie(重写 start_requests 方法)

scrapy 中 start_url 是通过 start_requests 来进行处理的,可能通过重写该方法让 start_url 携带上请求头信息,实现代码如下:

import scrapy


class Git1Spider(scrapy.Spider):
    name = 'git1'
    allowed_domains = ['github.com']
    start_urls = ['https://github.com/GitLqr']

    def start_requests(self):
        """
        重写start_requests,发送携带cookies的Request。
        默认start_requests只是普通的get请求,不会携带自定义的头信息
        """
        url = self.start_urls[0]

        temp = '_octo=GH1.1.1045146750.1615451260; _device_id=cd8d64981fcb3fd4ba7f587873e97804'
        # 把cookies字符串转成字典
        cookies = {data.split('=')[0]: data.split('=')[-1] for data in temp.split('; ')}

        yield scrapy.Request(
            url=url,
            callback=self.parse,
            cookies=cookies
        )

    def parse(self, response):
        print(response.xpath('/html/head/title/text()').extract_first())

注意:

  • scrapy 中 cookie 不能够放在 headers 中,在构造请求的时候有专门的 cookies 参数,能够接收字典形式的 cookie
  • 可能需要在 settings 中设置 ROBOTS 协议、USER_AGENT

# 2、scrapy.Request 发送 post 请求

scrapy.Request 发送 post 请求有两种方式:

  • 通过 scrapy.Request() 指定 method、body 参数来发送 post 请求(不推荐)

  • 使用 scrapy.FormRequest() 来发送 post 请求(推荐)

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

举例:

import scrapy


class Git2Spider(scrapy.Spider):
    name = 'git2'
    allowed_domains = ['github.com']
    start_urls = ['http://github.com/login']

    def parse(self, response):
        username = 'GitLqr'
        password = 'balabala'

        # 从登录页面响应中解析出post数据
        token = response.xpath('//input[@name="authenticity_token"]/@value').extract_first()

        post_data = {
            'commit': 'Sign in',
            'authenticity_token': token,
            'login': username,
            'password': password,
            'webauthn-support': 'supported',
        }
        print(post_data)

        # 针对登录url发送post请求
        yield scrapy.FormRequest(
            url='https://github.com/session',
            callback=self.after_login,
            formdata=post_data
        )

    def after_login(self, response):
        yield scrapy.Request('https://github.com/GitLqr', callback=self.check_login)

    def check_login(self, response):
        print(response.xpath('/html/head/title/text()').extract_first())

注意:在 settings.py 中通过设置 COOKIES_DEBUG = True 能够在终端查看到 cookie 的传递过程

# 六、scrapy 管道的使用

# 1、pipeline 中常用的方法

  • process_item(self, item, spider):
    • 管道类中必须有的函数
    • 实现对 item 数据的处理
    • 必须 return item
  • open_spider(self, spider):在爬虫开启的时候仅执行一次
  • close_spider(self, spider):在爬虫关闭的时候仅执行一次

注意:以上 3 个方法,可以通过 spider.name 获取爬虫的名字

# 2、pipeline 判断数据来源

scrapy 的 Item Pipeline 模块可以有多个管道,当有一个 spider 把数据对象通过引擎交给 Item Pipeline 模块时, Item Pipeline 模块中的所有管道会按 settings.py 中指定的管道顺序一一被执行。但很多时候,我们需要管道针对特定爬虫做数据存储的,这时就需要在管道中对数据对象的来源做判断。

注意:不同的 pipeline 能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存

举例:

  • 爬虫 1:job.py
import scrapy
from wangyi.items import WangyiItem


class JobSpider(scrapy.Spider):
    name = 'job'
    ...

    def parse(self, response):
        ...
        yield item
  • 爬虫 2:job_simple.py
import scrapy
from wangyi.items import WangyiSimpleItem


class JobSimpleSpider(scrapy.Spider):
    name = 'job_simple'
    ...

    def parse(self, response):
        ...
        yield item
  • 2 个管道:pipelines.py
class WangyiPipeline:

    def open_spider(self, spider):
        if spider.name == 'job':
            self.file = open('wangyi.json', 'w')

    def process_item(self, item, spider):
        if spider.name == 'job':
            item = dict(item)
            str_data = json.dumps(item, ensure_ascii=False) + ',\n'
            self.file.write(str_data)
        return item

    def close_spider(self, spider):
        if spider.name == 'job':
            self.file.close()


class WangyiSimplePipeline:

    def open_spider(self, spider):
        if spider.name == 'job_simple':
            self.file = open('wangyi_simple.json', 'w')

    def process_item(self, item, spider):
        if spider.name == 'job_simple':
            item = dict(item)
            str_data = json.dumps(item, ensure_ascii=False) + ',\n'
            self.file.write(str_data)
        return item

    def close_spider(self, spider):
        if spider.name == 'job_simple':
            self.file.close()

# 3、pipeline 使用注意点

  • 使用之前需要在 settings 中开启
  • pipeline 在 settings 中键表示位置(即 pipeline 在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过:权重值小的优先执行
  • 有多个 pipeline 的时候,process_item 的方法必须 return item,否则后一个 pipeline 取到的数据为 None 值
  • pipeline 中 process_item 方法必须要有,否则 item 没有办法接收和处理
  • process_item 方法接收 item 和 spider,其中 spider 表示当前传递 item 过来的 spider
  • open_spider(spider):能够在爬虫开启的时候执行一次
  • close_spider(spider):能够在爬虫关闭的时候执行一次
  • 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接

# 七、scrapy 中间件

# 1、介绍

# 1)分类

根据 scrapy 运行流程中所在位置不同,对 scrapy 中间件进行分类:

  • 下载中间件
  • 爬虫中间件

# 2)作用

scrapy 中间件的作用是:预处理 request 和 response 对象

  • 对 header 以及 cookie 进行更换和处理
  • 使用代理 ip 等
  • 对请求进行定制化操作

# 3)比较

  • 默认情况下,两种中间件都在 middlewares.py 一个文件中

  • 爬虫中间件使用方法和下载中间件相同,且功能重复,通常使用下载中间件

# 2、使用方法

注意:结合 scrapy 流程图可方便理解

scrapy流程图

中间件使用步骤如下:

  1. 在 middlerware.py 中定义中间件类

  2. 在中间件类中,重写处理请求或者响应的方法(以Downloader Middlewares为例)

    • process_request(self, request, spider)

      当每个 request 通过下载中间件时,该方法被调用【Scrapy Engine --> Downloader】

      • 返回 None【继续】:该 request 对象通过引擎传递给其他权重低的 process_request 方法,如果所有中间件都返回 None,则请求最终被交给下载器处理。注意:没有 return 也是返回 None。
      • 返回 Request 对象【中断】:把 request 对象通过引擎交给调度器,此时将不通过其他权重低的 process_request 方法
      • 返回 Response 对象【中断】:请求不会达到下载器,会直接把 response 通过引擎交给 Spider 进行解析
    • process_response(self, request, response, spider)

      当下载器完成 http 请求,传递响应给引擎的时候调用【Scrapy Engine <-- Downloader】

      • 返回 Request 对象【中断】:通过引擎交给调度器继续请求,此时将不通过其他权重低的 process_response 方法
      • 返回 Response 对象【继续】:通过引擎交给爬虫处理或交给权重更低的其他下载中间件的 process_response 方法
  3. 在 settings 文件中开启中间件,权重值越小越优先执行(同管道注册一样)

# 3、实现随机 User-Agent 下载中间件

# 1)准备 UA 列表

在 settings.py 文件中定义一个存放了大量 UA 的列表 USER_AGENT_LIST:

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36'

USER_AGENT_LIST = [
    'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
    'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
    'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;',
    'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
    'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
    'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
    'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11',
    'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
    'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
    'MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
    'Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10',
    'Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13',
    'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+',
    'Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0',
    'Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124',
    'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)',
    'UCWEB7.0.2.37/28/999',
    'NOKIA5700/ UCWEB7.0.2.37/28/999',
    'Openwave/ UCWEB7.0.2.37/28/999',
    'Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999',
    'UCWEB7.0.2.37/28/999',
    'NOKIA5700/ UCWEB7.0.2.37/28/999',
    'Openwave/ UCWEB7.0.2.37/28/999',
    'Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999'
]

注意:该 USER_AGENT_LIST 变量名可以自定义,也可以写在其他 py 文件中,写在 settings.py 文件中,只是为了规范而已。

# 2)定义下载中间件

在 middlewares.py 文件中定义随机 UA 的下载中间件:

import random

from Douban.settings import USER_AGENT_LIST


class RandomUserAgentMiddleware(object):
    def process_request(self, request, spider):
        # print(request.headers['User-Agent'])
        ua = random.choice(USER_AGENT_LIST)
        request.headers['User-Agent'] = ua

# 3)启用下载中间件

在 settings.py 文件中配置开启自定义的下载中间件:

DOWNLOADER_MIDDLEWARES = {
    'Douban.middlewares.RandomUserAgentMiddleware': 543,
}

# 4、实现 Selenium 渲染的下载中间件

scrapy 的 Downloader 模块只会根据请求获取响应,但实际开发过程中,有些页面上的数据是通过 ajax 延迟加载出来的,Downloader 模块无法应对这种情况,这时就需要用到 Selenium 来处理这类请求,等页面渲染完成后,再把渲染好的页面返回给爬虫即可:

from selenium import webdriver
from scrapy.http import HtmlResponse


class SeleniumMiddleware(object):
    def __init__(self):
        self.driver = webdriver.Chrome()

    def process_request(self, request, spider):
        url = request.url

        # 过滤需要使用selenium渲染的request
        if 'daydata' in url:
            self.driver.get(url)
            time.sleep(3)
            data = self.driver.page_source
            self.driver.close()

            # 返回HtmlResponse,请求不会达到Downloader,而是直接通过引擎交给爬虫
            response = HtmlResponse(url=url, body=data, encoding='utf-8', request=request)
            return response

    def __del__(self):
        self.driver.quit()
#Python爬虫#Scrapy
Python - 爬虫之Selenium

← Python - 爬虫之Selenium

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×