Scrapy 框架

介绍

Scrapy 是一个基于 Twisted 的异步处理框架,是纯 Python 实现的开源爬虫框架,其架构清晰,模块之间的耦合程度低,可扩展性极强,可以灵活完成各种需求。

Scrapy 框架的架构

如下图所示:
Scrapy 框架_第1张图片
其中各个组件含义如下:

  • Scrapy Engine(引擎):负责 Spiders、Item Pipeline、Downloader、Scheduler 之间的通信,包括信号和数据传输等。
  • Item(项目):定义爬取结构的数据结构,爬取的数据会被赋值成该对象。
  • Scheduler(调度器):负责接收引擎发送过来的 Request 请求,并按照一定的方式进行整理排列和入队,当引擎需要时,交还给引擎。
  • Downloader(下载器):负责下载引擎发送的所有 Requests;并将其获取到的 Responses 交还给引擎,由引擎交给 Spider 来处理。
  • Spiders(蜘蛛):负责处理所有 Responses,从中分析提取数据,获取 Item 字段需要的数据,并将需要跟进的 URL 提交给引擎,再次进入调度器。
  • Item Pipeline(项目管道):负责处理 Spiders 中获取到的 Item 数据,并进行后期处理(比如清洗、验证和存储数据)。
  • Downloader Middlewares(下载中间件):是一个可以自定义扩展下载功能的组件,主要是处理引擎与下载器之间的请求及响应。
  • Spider Middlewares(蜘蛛中间件):是一个可以自定义扩展 Scrapy Engine 和 Spiders 中间通信的功能组件,主要是处理 Spiders 输入的响应和 Spiders 输出的请求。

Scrapy 框架运作流程

Scrapy 中的数据流由引擎控制,其过程如下:

  1. 引擎向 Spiders 请求第一个要爬取的 URL
  2. 引擎从 Spiders 中获取到第一个要爬取的 URL 并封装成 Request 交给调度器
  3. 引擎向调度器请求下一个要爬取的 Request
  4. 调度器返回下一个要爬取的 Request 给引擎,引擎将 Request 通过下载中间件转发给下载器
  5. 页面下载完毕之后,下载器生成一个该页面的 Response 并将其通过下载中间件发送给引擎
  6. 引擎从下载器中接收到 Response 并通过 Spider 中间件发送给 Spider 处理
  7. Spider 处理 Response 并返回爬取到的 Item 以及新的 Request 给引擎
  8. 引擎将爬取到的 Item 交给 Item Pipeline,将 Request 交给调度器
  9. 重复第二步到最后一步,直到调度器中没有更多的 Request

安装

pip install scrapy

Scrapy 项目结构

创建项目
scrapy startproject tutorial
cd tutorial
tree /f .

结果如下:
Scrapy 框架_第2张图片
其中各个文件的功能描述如下:

  • scrapy.cfg:它是 Scrapy 项目的配置文件,其内定义了项目的配置文件路径、部署相关信息等内容。
  • items.py:它定义了 Item 数据结构,所有 Item 的定义都可以放在这里
  • pipeline.py:它定义了 Item Pipeline 的实现,所有的 Item Pipeline 的实现都可以放在这里
  • settings.py:它定义了项目的全局配置
  • middlewares.py:它定义了 Spider Middlewares 和 Downloader Middlewares 的实现
  • spiders:其内包含了一个个 Spider 的实现,每个 Spider 都有一个文件

基本使用

创建 Spider

scrapy genspider quotes quotes.toscrape.com

执行完成之后,可以发现 spiders 文件夹中多了一个 quotes.py 文件,其中文件内容如下图所示:
Scrapy 框架_第3张图片
可以发现这里有三个属性和一个方法:

  • name: 表示这个爬虫的识别名称。爬虫的名称在项目中是唯一的,不同的爬虫需要使用不同的名称。
  • allowed_domains:它是允许爬取的域名,如果初始或后续的请求连接不是在这个域名下的,则请求链接会被过滤掉。
  • start_urls:表示爬取的起始 URL 元组或列表,初始请求是由它来定义的。
  • parse:它是 Spider 的一个方法。默认情况下,被调用时 start_urls 里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数。该方法负责解析返回的响应、提取数据或进一步生成要处理的请求。

创建 Item

Item 是保存爬取数据的容器,创建 Item 需要继承 scrapy.Item 类,并且定义类型为 scrapy.Field 的字段。观察目标网站定义要爬取的内容:
Scrapy 框架_第4张图片

解析 Response

parse 方法的参数 response 是 start_urls 里面的链接爬取后的结果。所以在 parse 方法中,可以直接对 response 变脸包含的内容进行解析,比如浏览请求结果的网页源代码,或者进一步分析源代码内容,或者找出结果中的链接而得到下一个请求。
Scrapy 框架_第5张图片

运行

scrapy crawl quotes -o quotes.json

结果如下:
Scrapy 框架_第6张图片

Middleware 的用法

Downloader Middleware

Downloader Middlewares 即下载中间件,它是处于 Scrapy Engine 和 Downloader 之间的组件,主要是处理引擎与下载器之间的请求及响应。

在引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理(例如,增加 headers 信息,增加 proxy 信息等)。在下载器完成网络请求,传递响应给引擎的过程中,下载中间件可以对响应进行处理(例如,进行 gzip 压缩等)。

编写下载中间件只需要实现以下的一个或全部即可:

process_request(self, request, spider)

Request 被 Scrapy 引擎调度给 Downloader 之前,process_request 方法就会被调用,也就是在 Request 从队列里调度出来到 Downloader 下载执行之前,可以用 process_request 方法对 Request 进行处理。方法的返回值必须为 None、Response 对象、Request 对象之一,或者抛出 IgnoreRequest 异常。该方法的参数包括:

  • request:要处理的 Request 对象
  • spider:该 Request 对应的 Spider 对象

该方法的返回结果包括:

  • 如果返回 None,Scrapy 将继续处理该 Request,接着执行其他 Downloader Middleware 的 process_request 方法,直到 Downloader 把 Request 执行后得到 Response 才结束。这个过程其实就是修改 Request 的过程,不同的 Downloader Middleware 按照设置的优先级顺序依次对 Request 进行修改,最后推送至 Downloader 执行。
  • 如果返回 Response 对象,更低优先级的 Downloader Middleware 的 process_request 和 process_exception 方法就不会被继续调用,每个 Downloader Middleware 的 process_response 方法转而被依次调用。调用完毕之后,直接将 Response 对象发送给 Spider 来处理。
  • 如果返回 Request 对象,更低优先级的 Downloader Middleware 的 process_request 方法会停止执行。这个 Request 会重新放到调度队列里,其实它就是一个全新的 Request,等待被调度。如果被 Scheduler 调度了,那么所有的 Downloader Middleware 的 process_request 方法会被重新按照顺序执行。
  • 如果抛出一个 IgnoreRequest 异常,则所有的 Downloader Middleware 的 process_exception 方法会依次执行。如果没有一个方法处理这个异常,那么 Request 的 errorback 方法就会回调。如果该异常还没有被处理,那么它便会被忽略。
process_response(self, request, response, spider)

Downloader 执行 Request 下载之后,会得到对应的 Response。Scrapy 引擎便会将 Response 发送给 Spider 进行解析。在发送之前,我们都可以用 process_response() 方法来对 Response 进行处理。方法的返回值必须为 Request 对象、Response 对象之一,或者抛出 IgnoreRequest 异常。该方法的参数包括:

  • request:是 Request 对象,即此 Response 对应的 Request。
  • response:是 Response 对象,即此被处理的 Response。
  • spider:是 Spider 对象,即此 Response 对应的 Spider。

该方法的返回结果包括:

  • 当返回为 Request 对象时,更低优先级的 Downloader Middleware 的 process_response 方法不会继续调用。该 Request 对象会重新放到调度队列里等待被调度,它相当于一个全新的 Request。然后,该 Request 会被 process_request 方法顺次处理。
  • 当返回为 Response 对象时,更低优先级的 Downloader Middleware 的 process_response 方法会继续调用,继续对该 Response 对象进行处理。
  • 当返回为 Response 对象时,更低优先级的 Downloader Middleware 的 process_response 方法会继续调用,继续对该 Response 对象进行处理。
process_exception(self, request, exception, spider)

当 Downloader 或 process_request 方法抛出异常时,例如抛出 IgnoreRequest 异常,process_exception() 方法就会被调用。方法的返回值必须为 None、Response 对象、Request 对象之一。该方法的参数包括:

  • request:即 Request 对象,即产生异常的 Request。
  • exception:即 Exception 对象,即抛出的异常。
  • spider:即 Spider 对象,即 Request 对应的 Spider。

该方法的返回结果包括:

  • 当返回为 None 时,更低优先级的 Downloader Middleware 的 process_exception 会被继续顺次调用,直到所有的方法都被调度完毕。
  • 当返回为 Response 对象时,更低优先级的 Downloader Middleware 的 process_exception 方法不再被继续调用,每个 Downloader Middleware 的 process_response 方法转而被依次调用。
  • 当返回为 Request 对象时,更低优先级的 Downloader Middleware 的 process_exception 也不再被继续调用,该 Request 对象会重新放到调度队列里面等待被调度,它相当于一个全新的 Request。然后,该 Request 又会被 process_request 方法顺次处理。

实例

创建 Spider
scrapy genspider httpbin

将 start_urls 修改为 http://httpbin.org/get 结果如下:
Scrapy 框架_第7张图片

运行
scrapy crawl httpbin --nolog

结果如下:
Scrapy 框架_第8张图片
可以发现 Request 使用的 User-Agent 是 Scrapy/2.9.0 (+https://scrapy.org),这其实是由 Scrapy 内置的 UserAgentMiddleware 设置的,UserAgentMiddleware源码如下:
Scrapy 框架_第9张图片
在 from_crawler 方法中,首先尝试获取 settings 里面的 USER_AGENT,然后把 USER_AGENT 传递给 init 方法进行初始化,其参数就是 user_agent。如果没有传递 USER_AGENT 参数就默认设置为 Scrapy 字符串。接下来,在 process_request 方法中,将 user_agent 变量设置为 headers 变量的一个属性,这样就成功设置了 User-Agent。

如果需要设置随机 User-Agent 就需要借助 Downloader Middleware,在 middlewares.py 里面添加一个 RandomUserAgentMiddleware 的类,如下所示:

class RandomUserAgentMiddleware:

    def __init__(self):
        self.user_agents = [
            'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36',
            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
            '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 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11'
        ]

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

接下来在 settings.py 中,将 DOWNLOADER_MIDDLEWARES 设置成如下内容:

DOWNLOADER_MIDDLEWARES = {
    "tutorial.middlewares.RandomUserAgentMiddleware": 543,
}

重新运行 Spider,就可以看到 Uset-Agent 被成功修改为列表中所定义的随机的一个 User-Agent,如下图所示:

Scrapy 框架_第10张图片

Item Pipeline 的用法

当 Item 在 Spiders 中被收集之后,会被传递到 Item Pipeline。用户可以在 Scrapy 项目中定义多个管道,这些管道按定义的顺序依次处理 Item。

每个管道都是一个 Python 类,在这个类中实现了一些操作 Item 的方法。其中,有的方法用于丢弃重复的 Item,有的方法用于将 Item 存储到数据库或文件等。以下是 Item Pipeline 的一些典型应用:

  1. 验证爬取的数据,检查 Item 包含某些字段,例如 name 字段。
  2. 查重,并丢弃重复数据。
  3. 将爬取结果保存到文件或者数据库中

自定义 Item Pipeline

自定义 Item Pipeline 很简单,每个 Item Pipeline 组件都是一个独立的 Python 类,该类中的 process_item 方法必须实现,每个 Item Pipeline 组件都需要调用 process_item 方法。

process_item 方法必须返回一个 Item 对象,或者抛出 DropItem 异常,被丢弃的 Item 将不会被之后的 Pipeline 组件所处理。该方法的定义如下:

process_item(self, item, spider)

该方法的 2 个参数分别是:

  • Item:表示被爬取的 Item 对象。
  • spider:表示生成该 Item 的 Spider 对象。
实例
  • 生成爬虫文件
scrapy genspider imagesnetbian pic.netbian.com
  • 构造请求
    在 settings.py 里定义一个变量 MAX_PAGE,结果如下:
    Scrapy 框架_第11张图片
    定义 start_request 方法,用来生成 10 次请求,结果如下:

Scrapy 框架_第12张图片

  • 存储信息
    用 MongoPipeline 将信息保存到 MongoDB 中,在 pipeline.py 里添加如下类的实现:
class MongoPipeline:

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(mongo_uri=crawler.settings.get('MONGO_URI'),
                   mongo_db=crawler.settings.get('MONGO_DB'))

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def process_item(self, item, spider):
        self.db[item.collection].insert_one(dict(item))
        return item

    def close_spider(self, spider):
        self.client.close()

这里需要用到两个变量,MONGO_URIMONGO_DB,即存储到 MONGODB 的链接地址和数据库名称。可以在 settings.py 里添加这两个变量,如下所示:
Scrapy 框架_第13张图片

  • 运行
scrapy crawl imagesnetbian --nolog

使用 MongoDB 客户端查看数据库可以发现结果已经保存到 MongoDB 中,结果如下:
Scrapy 框架_第14张图片

参考资料

  1. 崔庆才老师《Python3 网络爬虫开发实战》

你可能感兴趣的:(网络爬虫,scrapy)