python3编写网络爬虫21-scrapy框架的使用

一、scrapy框架的使用

前面我们讲了pyspider 它可以快速的完成爬虫的编写 不过pyspider也有一些缺点 例如可配置化不高 异常处理能力有限
对于一些反爬虫程度非常强的网站 爬取显得力不从心

1. scrapy框架介绍

scrapy是一个基于Twisted 的异步处理框架 是纯python实现的爬虫框架 架构清晰 模块之间耦合度低 可拓展性极强
可以灵活完成各种需求 只需要定制开发几个模块 就可以轻松实现一个爬虫

1.1 架构介绍

可以分为如下几个部分

Engine 引擎 处理整个系统的数据流处理 触发事务 是整个框架的核心

item 项目 定义了爬取结果的数据结构 爬取的数据会被赋值成该Item对象

Scheduler 调度器 接受引擎发过来的请求并将其加入队列中 在引擎再次请求的时候 将请求提供给引擎

Download 下载器 下载网页内容 并将网页内容返回给蜘蛛

Spider 蜘蛛 其内定义了爬取的逻辑和网页解析规则 它主要负责解析响应 并生成提取结果和新的请求

item Pipline 项目管理 负责处理由蜘蛛从网页中抽取的项目 它的主要任务是清洗 验证 和存储数据

Downloader Middlewares 下载器中间件 位于引擎和下载器之间的钩子框架 主要处理引擎与下载器之间的请求和响应

Spider Middlewares 蜘蛛中间件 位于引擎和蜘蛛之间的钩子框架 主要处理蜘蛛输入的响应和输出结果以及引得请求


2. 数据流

scrapy 中数据流由引擎控制 数据流的过程如下

1. Engine 首先打开一个网站 找到处理该网站的Spider 并向该Spider请求第一个要爬取的URL
2. Engine 从Spider 中获取到第一个要爬取的URL 并通过 Scheduler 以 Request 的形式调度
3. Engine 向 Scheduler 请求下一个要爬取的URL
4. Scheduler 返回下一个要爬取的URL给 Engine Engine 将URL 通过 Downloader Middlewares 转发给 Downloader下载
5. 一旦页面 下载完毕 Downloader 生成该网页的 Response 并将其通过 Downloader Middlewares 发送给 Engine
6. Engine 从下载器中接收到 Response 并将其通过 Spider Middlewares 发送给 Spider 处理
7. Spider 处理 Response 并返回爬取到的Item 及新的 Request 给 Engine
8. Engine 将 Spider 返回的 Item 给 item Pipline 将新的 Request 给 Scheduler
9. 重复第2步到第8步 直到 Scheduler 中没有更多的 Request Engine关闭该网站 爬取结束

 python3编写网络爬虫21-scrapy框架的使用

通过多个组件的相互协调 不同组件完成工作的不同 组件对异步处理的支持 scrapy 最大限度的利用了网络带宽
大大提高了数据爬取和处理的效率

3. 项目结构

scrapy 框架和 pyspider 不同 它是通过命令行来创建项目的 代码的编写还是需要IDE 项目创建之后 项目文件结构如下:

scrapy.cfg
project/
  __init__.py 
  items.py
  piplines.py
  settings.py
  middlewares.py
  spiders/
    __init__.py
    spider1.py
    spider2.py
    。。。

各个文件的功能描述如下:

scrapy.cfg 是Scrapy项目的配置文件 其中定义了项目的配置文件路径 部署相关信息等内容

items.py 定义了item数据结构 所有得item的定义都可以放这里

pipelines.py 定义了 item pipeline的实现 所有的 item pipeline 的实现都放在这

settings.py 定义项目的全局配置

middlewares.py 定义 Spider Middlewares 和 Downloader Middlewares的实现

spiders 内部包含一个个Spider的实现 每个Spider 都有一个文件


4.Scrapy入门

写一个简单的项目 完成一遍Scrapy抓取流程 通过这个过程 可以对Scrapy 的基本用法和原理有一个大体了解

4.1 目标

创建一个scrapy项目
创建一个spider来抓取站点和处理数据
通过命令行将抓取的内容导出
将抓取的内容保存到MongoDB数据库

4.2 安装

需要安装Scrapy框架 mongoDB 和 pymongo库

安装 scrapy

scrapy 依赖库比较多 例如 Twisted 14.0 lxml 3.4 pyOpenSSL 0.14 不同平台环境 依赖库也不同

windows 下

安装 lxml pip install lxml 如果报错 采用wheel方式安装

安装 pyOpenSSL 官方网站 https://pypi.python.org/pypi/pyOpenSSL#downloads

安装 Twisted 下载 https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

安装 PyWin32 官网 https://pypi.org/project/pywin32/#files 下载对应版本

安装scrapy pip install Scrapy 无报错信息表示安装成功


4.3 创建项目

cmd 运行

scrapy startproject tutorial

会创建一个名为 tutorial 的文件夹 结构如下:

scrapy.cfg #Scrapy部署时配置文件
tutorial #项目模块 需要从这里引入
  __init__.py 
  items.py #items的定义 定义爬取的数据结构
  middlewares.py # 定义爬取是的中间件
  piplines.py # 定义数据管道
  settings.py #配置文件
  spiders 
    __init__.py


4.4 创建spider

Spider是自己定义的类 Scrapy用它来从网页抓取内容 并解析抓取结果 不过这个类必须继承Scrapy提供的Spider类scrapy.Spider
还要定义Spider的名称和起始请求 以及怎样处理爬取后结果的方法

也可以通过命令行创建一个Spider 例如生成一个Quotes这个Spider 可以执行

cd tutorial
scrapy genspider quotes quotes.toscrape.com

进入 tutorial 执行 genspider 第一个参数是Spider名 第二个参数是 网站域名

执行之后 spider路径下多了一个文件 quotes.py 内容如下

# -*- coding: utf-8 -*-
import scrapy


class QuotesSpider(scrapy.Spider):
  name = 'quotes'
  allowed_domains = ['quotes.toscrape.com']
  start_urls = ['http://quotes.toscrape.com/']

  def parse(self, response):
    pass

这里有三个属性

name 它是每个项目唯一的名字 用来区分spider

allowed_domains 允许爬取的域名 如果初始化或者后续的请求链接不再这个域名下 请求链接会被过滤掉

start_urls 包含了spider在启动时的url列表 初始请求时由它定义的

parse 方法 默认情况下 被调用 start_urls 里面的链接请求完成下载执行后 返回的响应就会作为唯一参数传递给这个函数
该方法负责解析返回的响应 提取数据或者进一步生成处理的请求

 

4.5 创建item

item是保存爬取数据的容器 它的使用方法和字典类似 不过 相比字典 item多了额外的保护机制 避免拼写错误或者定义字段错误

创建item需要继承scrapy.Item类 并且定义类型为 scrapy.Field 字段 观察目标网站我们可以获取到的内容有 text author tags

定义item 将items.py 修改如下:

import scrapy


class QuoteItem(scrapy.Item):
  text = scrapy.Field()
  author = scrapy.Field()
  tags = scrapy.Field()

这里定义三个字段 后面爬取会用到item


4.6 解析Response

前面可以看到 parse() 方法的参数是response 是 start_urls 里面的链接爬取后的结果
所以在parse方法里面 可以直接对response变量包含的内容进行解析 例如浏览请求结果的网页源代码 或者进一步分析代码内容
或者找出结果中的链接 得到下一个请求

网页中既有我们要的内容 又有下一页的链接 两部分都要处理

每一页都多个class 为 quote 的区块 每个区块都包含 text author tags 先找出所有的quote 再提取每一个quote的内容

提取方式 可以是css选择器 也可以是xpath选择器 将parse方法修改为

def parse(self,response):
  quotes = response.css('.quote')
  for quote in quotes:
    text = quote.css('.text::text').extract_first()
    author = quote.css('.author::text').extract_first()
    tags = quote.css('.tags .tag::text').extract()

这里首先利用选择器选取所有的quote 并将其赋值为 quotes 变量 利用for循环遍历每个quote内容
对于text class为text 可以使用 .text 获取 但是结果实际上是整个带有标签的节点 要获取正文内容 可以加 ::text 结果为长度1的列表
借助 extract_first 方法获取第一个元素
对于tags 获取所有的标签 用 extract 获取所有列表

4.7 使用item

4.5定义了item 下面就要使用它 item可以理解为一个字典 不过在声明的时候需要实例化 然后依次用刚才的解析结果赋值item的每一个字段
最后将结果返回

QuoteSpider 改写为

# -*- coding: utf-8 -*-
import scrapy
from tutorial.items import QuoteItem

class QuotesSpider(scrapy.Spider):
  name = 'quotes'
  allowed_domains = ['quotes.toscrape.com']
  start_urls = ['http://quotes.toscrape.com/']

  def parse(self, response):
    quotes = response.css('.quote')
    for quote in quotes:
      item = QuoteItem()
      item['text'] = quote.css('.text::text').extract_first()
      item['author'] = quote.css('.author::text').extract_first()
      item['tags'] = quote.css('.tags .tag::text').extract()
      yield item

首页的所有内容被解析出来 并被赋值成一个个QuoteItem


4.8 后续Request

上面操作 实现了从初始页面抓取内容 下一页的内就需要从当前页面信息生成下一页请求
然后在下一页请求的页面找信息 再构造下一个请求 循环迭代 实现整个网站爬取

将页面拉到最底部 查看next按钮 链接为/page/2/ 完整链接为 http://quotes.toscrape.com/page/2

构造请求需要用到scrapy.Request 传递两个参数
url 请求链接
callback 回调函数 当指定了该回调函数的请求完成之后 获取响应 引擎会将该响应结果作为参数传递给这个回调函数
回调函数会进行解析或生成下一个请求 回调函数如上面的parse

由于 parse()就是解析text author tags 的方法 而下一页的结构和刚才解析的页面结构是一样的 所以可以再次使用parse方法来做解析

利用选择器得到下一页的链接并生成请求 在 parse() 方法后追加代码

#获取下一页页面链接

next = response.css('.pager .next a::attr(href)').extract_first()


#将相对URL构造成绝对URL

url = response.urljoin(next)


#通过url和callback 构造一个新的请求 请求完成后响应会重新经过parse方法处理 得到第二页解析结果 然后生成第三页请求
#爬虫就进入了一个循环 将每个页面都爬取下来

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


更改代码后的Spider类

# -*- coding: utf-8 -*-
import scrapy
from tutorial.items import QuoteItem

class QuotesSpider(scrapy.Spider):
  name = 'quotes'
  allowed_domains = ['quotes.toscrape.com']
  start_urls = ['http://quotes.toscrape.com/']

  def parse(self, response):
    quotes = response.css('.quote')
    for quote in quotes:
      item = QuoteItem()
      item['text'] = quote.css('.text::text').extract_first()
      item['author'] = quote.css('.author::text').extract_first()
      item['tags'] = quote.css('.tags .tag::text').extract()
      yield item


    next = response.css('.pager .next a::attr(href)').extract_first()
    url = response.urljoin(next)
    yield scrapy.Request(url=url, callback=self.parse)

4.9 运行

进入目录 进行命令

scrapy crawl quotes

首先输出版本号 及启动项目名称 输出了一写settings中的一些配置 然后输出当前应用的 Middlewares 和 Piplines
Middlewares默认是启用的 Piplines 默认是空 可以在settings里面修改

接着输出爬取结果 可以看到爬虫在一边解析 一边翻页 直至将所有内容抓取完毕然后终止

最后输出统计信息 请求字节数 请求次数 响应次数 等等


4.10 保存到文件

scrapy 提供了 Feed Exports 可以将抓取结果输出
例如保存成json文件 可以执行命令

scrapy crawl quotes -o quotes.json

另外还可以每一个Item输出一行 JSON 输出后缀为 jl (jsonline缩写) 命令如下

scrapy crawl quotes -o quotes.jl

输出格式还支持 csv xml pickle marshal 以及ftp远程输出

scrapy crawl quotes -o quotes.csv
scrapy crawl quotes -o quotes.xml
scrapy crawl quotes -o quotes.pickle
scrapy crawl quotes -o quotes.marshal
scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/to/quotes.csv #需要正确配置用户名 密码 地址 输出路径 否则会报错

对于一些小型项目来说应该足够了 如果想输出到数据库就需要 Item Pipline来完成

4.11 使用Item Pipline

将结果保存到mongoDB数据库中 可以定义item pipline 来实现

Item Pipline为项目管道 当item生成后 会自动被被送到 item pipline进行处理 一般用item pipline 做如下操作

清理 HTML数据
验证爬取数据 检查爬取字段
去重并丢弃重复内容
将爬取结果保存到数据库中

要是实现 item pipline 只需要定义一个类并实现 process_item方法 启用 item pipline 后会自动调用这个方法
process_item方法 必须返回包含数据的字典或item对象 或者抛出DropItem异常

process_item方法有两个参数 一个是item 每次Spider生成的item都会作为参数传过来 另一个参数就是spider的实例

实现一个item pipline 筛选text长度大于50的item 并将结果保存到MongoDB数据库中

修改项目piplines.py文件 之前用命令行生成的内容可以删除 增加一个 TextPipeline类 内容如下

from scrapy.exceptions import DropItem

class TextPipeline(object):
  def __init__(self):
    self.limit = 50

  def process_item(self,item,spider):
    if item['text']:
      if len(item['text']) > self.limit:
        item['text'] = item['text'][0:self.limit].rstrip() + '...'
          return item
    else:
      return DropItem('Missing Text')

接下来将处理后的item存入MongoDB 定义另外一个Pipline 同样在piplines.py中
实现另一个类 MongoPipline 内容如下

import pymongo

class MongoPipline(object):
  def __init__(self,mongo_url,mongo_db):
    self.mongo_url = mongo_url
    self.mongo_db = mongo_db
    #类方法 是一种依赖注入方式 通过参数 crawler 可以拿到全局配置的每个配置信息
    #在settings.py中可以定义MONGO_URL 和 MONGO_DB 来指定mongodb的连接地址和数据库名
    #主要是获取settings中配置的
  @classmethod
  def from_crawler(cls,crawler):
    return cls(
      mongo_url = crawler.settings.get('MONGO_URL'),
      mongo_db = crawler.settings.get('MONGO_DB')
    )
  #当Spider开启时 这个方法被调用
  def open_spider(self,spider):
    self.client = pymongo.MongoClient(self.mongo_url)
    self.db = self.client[self.mongo_db]

  #主要执行数据库插入操作
  def process_item(self,item,spider):
    name = item.__class__.__name__
    self.db[name].insert(dict(item))
    return item

  #当Spider关闭时 这个方法被调用
  def close_spider(self,spider):
    self.client.close()

定义好两个类后 还需要在settings.py中使用 并且定义MongoDB的连接信息

在settings.py中加入代码

ITEM_PIPELINES = {
  'tutorial.pipelines.TextPipeline': 300,
  'tutorial.pipelines.MongoPipline': 400,
}
MONGO_URL = 'localhost'
MONGO_DB = 'tutorial'

键名为类名 键值是调用优先级 数字越小 对应的Pipeline 越先被调用

再重新执行爬取 scrapy crawl quotes

爬取结束后 MongoDB 中创建了一个tutorial数据库 QuoteItem的表

这样我们就简单的完成了整个Scrapy的爬取流程 分析 抓取 解析 存储

推荐MongoDB 可视化管理工具 下载地址 https://robomongo.org/download

5.selector的使用

Scrapy提供了自己的数据提取方法 selector(选择器) 是基于lxml构建的支持Xpath选择器 css选择器 以及正则表达式
功能全面 解析速度和精确度非常高


5.1 直接使用

selector是一个可以独立使用的模块 我们可以直接利用selector这个类来构建一个选择器对象,然后调用它的相关方法
例如 xpath() css()等来提取数据

示例 针对一段HTML代码 可以使用如下方式构建 Selector 对象来提取数据

from scrapy import Selector

body = '<html><head><title>Hello World</title></head><body></body></html>'
selector = Selector(text=body)
title = selector.xpath('//title/text()').extract_first()
print(title)

尝试运行 构建的时候传入text参数 就生成一个Selector选择器对象 然后调用xpath方法 提取文本
与其他解析库类似 也是强大的网页解析库 也可在其他项目中使用selector来提取数据


5.2 Scrpay shell


selector主要是与Scrapy结合使用 例如 Scrapy的回调函数中参数response直接调用xpath 或者css 方法来提取数据

官方文档有一个样例页面做示例 http://doc.scrapy.org/en/latest/_static/selectors-sample1.html

开启 Scrapy shell 在命令行输入如下命令

scrapy shell http://doc.scrapy.org/en/latest/_static/selectors-sample1.html

就进入到 scrapy shell 模式 这个过程是 scrapy 发起一次请求 请求的URL就是刚才命令行输入的URL 然后把一些可操作变量
传递给我们 例如 request response等

我们可以在命令行模式下输入命令调用对象的一些操作方法 回车后显示实时结果 与python命令行交互模式类似

5.3 Xpath选择器

进入shell 模式后 主要操作response 变量解析 因为解析的是HTML代码 selector 自动使用HTML语法分析

response 有一个属性selector 调用 response.selector 返回的内容就相当于resposne的body 构造了一个selector对象
通过selector对象 就可以调用 xpath css 等方法

示例
命令行 输入

result = response.selector.xpath('//a')
result

type(result)

打印结果的形式是seleltor组成的列表 类型为 selectorlist类型
selector 和 selectorlist 都可以继续调用xpath() css() 等方法进一步提取数据

接下来尝试提取a节点内包含的img节点

输入 

result.xpath('./img')

注意 选择器最前面加. 代表提取元素内部的数据 如果没有加. 代表从根节点开始提取

scrapy提供了两个实用的快捷方法

response.xpath 和 response.css 功能完全等同于 response.selector.xpath 和 response.selector.css

现在得到selectorlist 类型的变量 是由selector 对象组成的列表 可以利用索引单获取某个selector元素

示例

输入

 result[0]

返回的是selector 不是真正文本 具体内容可以使用extract

示例 取出a节点的元素

输入

 result.extract()

返回真实的内容

如果选取节点内部文本和属性 就要修改xpath

输入 

response.xpath('//a/text()').extract() #返回文本列表
response.xpath('//a/@href').extract() #返回属性


想一个问题 如果符合要求的节点只有一个 返回结果是什么?


例如 

response.xpath('//a[@href="image1.html"]/text()').extract()

可以看到其结果还是一个列表 通过索引获取 例如

resposne.xpath('//a[@href="image1.html"]/text()').extract()[0]

但是 这个写法明显有风险 一旦xpath出现问题 返回的结果可能是空列表 再用索引去获取 就会导致数组越界


这里使用专门的一个方法 extract_first() 改写为

response.xpath('//a[@href="image1.html"]/text()').extract_first()

该方法将匹配的第一个结果提取出来 就可以避免数组越界问题

也可以给 extract_first() 设置默认参数 如果xpath 提取不到结果 返回默认值
示例 更改xpath为不存在的规则

分别输入

response.xpath('//a[@href="image1"]/text()').extract_first()


如果匹配不到任何元素 会返回空 不会报错

response.xpath('//a[@href="image1"]/text()').extract_first('Default Image')


如果匹配不到任何元素 返回默认值

至此 scrapy中 Xpath相关的用法 嵌套查询 提取内容 提取单个内容 获取文本和属性 就了解完了


6.css选择器

scrapy对接了css选择器用法 使用response.css 方法可以使用css选择器来选择对应元素

例如

 response.css('a') 

获取所有a节点

调用 extract 方法就可以提取出a节点
用法和xpath选择完全一样

另外也可以进行属性选择和嵌套选择

例如 

response.css('a[href="image1.html"]').extract()

如果想查找a节点内的img节点 只需要加空格 例如

response.css('a[href="image1.html"] img').extract()


选择器写法和标准的CSS选择器写法相同

同样可以使用 extract_first() 方法 示例

response.css('a[href="image1.html"] img').extract_first()

获取节点的内部文本和属性写法不一样 示例

response.css('a[href="image1.html"]::text').extract_first()

response.css('a[href="image1.html"] img::attr(src)').extract_first()

另外 css选择器 和 xpath 一样可以嵌套选择

示例 先用xpath 选中所有a节点 再利用 css选中img节点 再用xpath获取属性

response.xpath('//a').css('img').xpath('@src').extract()

成功获取所有 img节点的src属性 两种方法*组合 完全兼容


7.正则匹配

scarpy选择器 还支持正则匹配 例如 a节点中的文本类似 Name:My image 1 把 Name:后面的内容提取出来
可以借助 re()方法

response.xpath('//a/text()').re('Name:s(.*)')

结果会依次输出

如果存在两个分组

response.xpath('//a/text()').re('(.*?):s(.*)')

结果依然会按序输出

类似 extract_first() 方法 re_first() 选取列表中第一个元素 示例

response.xpath('//a/text()').re_first('(.*?):s(.*)')

response.xpath('//a/text()').re_first('Name:s(.*)')

无论正则表达式匹配了几个分组 都会返回列表第一个元素

注意 response对象 不能直接调用 re 或者 re_first 如果相对全文进行匹配 可以先调用xpath()方法 再使用

以上就是scrapy 选择器的用法 包括两个常用选择器 和 正则匹配功能 熟练掌握xpath语法 css选择器语法 正则表达式语法
可以大大提高数据提取效率


8.spider的用法

在scrapy中 要抓取网站链接配置 抓取逻辑 解析逻辑 都在spider中配置

8.1 spider运行流程

在实现scrapy 爬虫项目时 最核心的类就是Spider类 它定义了如何爬取某个网站的流程和解析方式
简单说 spider 要做的事情就两件

定义爬取网站的动作
分析爬取下来的页面

对于Spider类 来说 整个爬取循环过程如下

以初始的URL初始化 Request 并设置回调函数 当该 Request成功请求并返回时 Response生成并作为参数传给该回调函数

在回调函数内分析返回的页面内容 返回结果又两种 一种是解析到的有效结果返回字典或Item对象 它们可以经过处理
后(或直接)保存 另一种是解析得到下一个(如下一页)链接 可以利用此链接构造 Request 并设置新的回调函数返回
Request 等待后续调度

如果返回的是字典或者Item对象 通过 Feed Exports 等组件将返回结果存入到文件 如果设置了 Pipline
可以使用Pipline处理(过滤 修正等)保存

如果返回的是 Request Request 执行成功得到 Response后 Response 会被传递给 Request中定义的回调函数 在回调
函数中 可以再次利用选择器分析 得到新的网页内容 并分析数据 生成Item
通过以上几步循环进行 就完成了整个站点的爬取

8.2 Spider类分析

前面定义的Spider 是继承自 scrapy.spiders.Spider 其他Spider类 必须继承这个类 后面特殊的Spider类 也都继承自它

scrapy.spiders.Spider 提供了 start_requests() 方法默认实现 读取并请求 start_urls 属性 并根据返回的结果 调用 pares()
方法 解析结果 它还有一些基础属性 如下

name 爬虫名称 定义Spider名字的字符串 Spider的名字定义了Scrapy如何定位并初始化 Spider 必须是唯一的 可以生成多个相同
的Spider 实例 数量没有限制 name属性是 Spider最重要的属性 如果Spider爬取单个网站 一个常见的做法就是以该网站域名名称
来命名Spider

allowed_domains 允许爬取的域名 可选配置 不再此范围内的链接不会被跟进爬取

start_urls 起始URL列表 当没有实现start_requests 方法时 默认会从这个列表抓取

custom_settings 是一个字典 是专属与 Spider的配置 此设置会覆盖项目全局的设置 此设置必须在初始化前被更新 必须定义成类变量

crawler 是由 from_crawler 方法设置的 代表是原本 Spider类对应的 Crawler 对象 包含了很多项目组件 利用它可以获取项目的一些配置信息
常见的获取项目的设置信息 settings

settings Settings 对象 利用它可以直接获取项目的全局设置变量

除了基础 Spider 还有一些常用方法

start_requests()

用于生成初始请求 必须返回一个可迭代对象 默认使用 start_urls 里面的URL 来构造 Request 而且 Request 是GET请求方式
如果想启用POST方式访问某个站点 可以直接重写这个方法 发送POST请求时使用 FromRequest即可

pares() 

当Response没有指定回调函数时 该方法默认被调用 负责处理 Response处理返回结果 并从中提取想要的数据和下一步请求 然后
返回 该方法需要返回一个包含 Request 或 Item的可迭代对象

closed()

当Spider关闭时 该方法被调用 一般定义释放资源的一些操作或其他收尾操作

9. Downloader Middlewares 的用法

下载中间件 处于 Scrapy的 Request 和 Response 之间处理模块 Scheduler 从队列中拿出一个 Request 发送给 Downloader执行下载
这个过程会经过 Downloader Middlewares 的处理 另外 当 Downloader将 Request下载完成得到的 Response返回给 Spider时 会再次
经过 Downloader Middlewares处理

Downloader Middlewares 的功能十分强大 例如修改 User-Agent 处理重定向 设置代理 失败重试 设置cookies 等功能 都需要借助它实现

9.1 使用说明

Scrapy其实已经提供了许多 Downloader Middlewares 比如负责失败重试 自动重定向等功能的 Middlewares 他们被
DOWNLOADER_MIDDLEWARES_BASE变量所定义 是一个字典格式 字典的键名是 Scrapy内置的 Downloader Middlewares 的名称
键值代表调用优先级 数字越小 越靠近 Scrapy引擎 数字越大越靠近 Downloader 数字小的 Downloader Middlewares 会被优先调用
如果自定义 Downloader Middlewares 添加到项目中 可以设置 DOWNLOADER_MIDDLEWARES变量来覆盖Scrapy内置的


示例

新建一个项目

scrapy startproject scrapydownloadertest

进入项目 新建一个Spider

scrapy genspider httpbin httpbin.org

修改 start_urls 为 ['http://httpbin.org/'] 将 parse() 方法添加一行日志输出 将resposne变量的text属性输出出来
内容如下

# -*- coding: utf-8 -*-
import scrapy


class HttpbinSpider(scrapy.Spider):
  name = 'httpbin'
  allowed_domains = ['httpbin.org']
  start_urls = ['http://httpbin.org/get']

  def parse(self, response):
    self.logger.debug(response.text)

  #运行 Spider scrapy crawl httpbin

    "args": {},
    "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Encoding": "gzip,deflate,br",
    "Accept-Language": "en",
    "Connection": "close",
    "Host": "httpbin.org",
    "User-Agent": "Scrapy/1.5.1 (+https://scrapy.org)"
    },
    "origin": "222.69.152.130",
    "url": "https://httpbin.org/get"


观察 Headers Scrapy发送的 Request 使用的 USER_AGENT 是 Scrapy/1.5.1 (+https://scrapy.org)

修改请求时 User-Agent 可以有两种方式

1. 修改 settings里面的 USER_AGENT变量

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

一般推荐此方法 但是要灵活设置 例如随机User-Agent 就需要第二种方法了

2. 通过Downloader Middlewares 的 process_request()
在 middlewares.py中添加 RandomUserAgentMiddleware的类 内容如下:

import random

class RandomUserAgentMiddleware():
  def __init__(self):
    self.user_agent = [
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
      'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16',
    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
    ]

  def process_request(self,request,spider):
    request.headers['User-Agent'] = random.choice(self.user_agent)

要生效的话还需要调用 Downloader Middlewares 类 在settings中将 DOWNLOADER_MIDDLEWARES 取消注释
设置如下

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

重新运行 Spider 就可以看到 User-Agent 被成功修改了列表中随机一个

总结 介绍了 Downloader Middlewares 的基本使用 此组件非常重要 是做异常处理和反爬虫处理的核心

10 Spider Middlewares的用法

是介入到Scrapy的Spider处理机制的钩子框架
作用有三个

在 Downloader 生成的 Response 发送给Spider之前 进行处理

在 Spider 生成的 Request 发送给 Scheduler之前 进行处理

在 Spider 生成 item 发送给 item Pipline之前 进行处理

10.1 使用说明

和 Downloader Middlewares类似

10.2 核心方法

process_spider_input 当 Response 被 Spider Middlewares 处理前被调用
process_spider_output 当 Spider 处理 Response 返回结果时 被调用
process_spider_exception 当process_spider_input 方法抛出异常时被调用
process_start_requests 执行过程类似 process_spider_output 只不过没有相关联的 Response 并且必须返回 Request

使用频率不如 Downloader Middlewares 必要时可以方便数据的处理

以上内容就是 scrapy框架的基本使用