爬虫框架Scrapy(1)Scrapy基础1

文章目录

一. Scrapy框架简介

Scrapy是一个使用Python语言(基于Twisted框架)编写的开源网络爬虫框架,目前由 Scrapinghub Ltd 维护。Scrapy 简单易用、灵活易拓展、开发社区活跃,并且是跨平台的。在 Linux、MaxOS 以及 Windows 平台都可以使用。Scrapy 应用程序也使用 Python 进行开发,目前可以支持 Python 2.7 以及 Python 3.4+ 版本。

Scrapy 功能非常强大,爬取效率高,相关扩展组件多,可配置和可扩展程度非常高,它几乎可以应对所有反爬网站,是目前 Python 使用最广泛的爬虫框架。

1. Scrapy 框架介绍

首先我们看看 Scrapy 框架的架构:
爬虫框架Scrapy(1)Scrapy基础1

它可以分为如下的几个部分:

模块 作用
Engine 引擎,处理整个系统的数据流处理、触发事务,是整个框架的核心
Item 项目,它定义了爬取结果的数据结构,爬取的数据会被赋值成该 Item 对象
Scheduler 调度器,接受引擎发过来的请求并将其加入列中,在引擎再次请求的时候将请求提供给引擎
Downloader 下载器,下载网页内容,并将网页内容返回给蜘蛛
Spiders 蜘蛛,其内定义了爬取的逻辑和网页解析规则,它主要负责解析响应并生成提取结果和新的请求
Item Pipeline 项目管道,负责处理由蜘蛛从网页中抽取的项目,它的主要任务是清洗、验证和存储数据
Downloader Middlewares 下载器中间件,位于引擎和下载器之间的钩子框架,主要处理引擎与下载器之间的请求及响应
Spide Middlewares 蜘蛛中间件,位于引擎和蜘蛛之间的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求

对于用户来说,Spider 是最核心的组件,Scrapy 爬虫开发是围绕实现 Spider 展开的。

2. 数据处理流程

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

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

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

如果把框架中的组件比作人体的各个器官,Request 和 Response 对象便是血液,Item 则是代谢产物。

二. Scrapy 及其依赖库的安装

在任意操作系统下,都可以使用 pip 安装 Scrapy,包括在虚拟机中。下面我们在远程终端进入相应的 env 通过如下命令下载 Scrapy 框架:

(pyspider) pyvip@VIP:~$ pip3 install scrapy
Looking in indexes: https://pypi.douban.com/simple
...
Installing collected packages: scrapy
Successfully installed scrapy-2.4.1

为确认 Scrapy 已安装成功,在 shell 中测试能否执行 Scrapy 这条命令:

(pyspider) pyvip@VIP:~$ scrapy
Scrapy 2.4.1 - no active project

Usage:
  scrapy <command> [options] [args]

Available commands:
  bench         Run quick benchmark test
  commands      
  fetch         Fetch a URL using the Scrapy downloader
  genspider     Generate new spider using pre-defined templates
  runspider     Run a self-contained spider (without creating a project)
  settings      Get settings values
  shell         Interactive scraping console
  startproject  Create new project
  version       Print Scrapy version
  view          Open URL in browser, as seen by Scrapy

  [ more ]      More commands available when run from project directory

Use "scrapy <command> -h" to see more info about a command

通过以上检测,说明 Scrapy 安装成功了。如上所示,我们安装的是当前最新版本2.4.1。

除了上述 Scrapy 库,我们还需要安装其依赖的其他几个第三方库,例如 'lxml'、'Twisted'、'pyOpenSSL' 等。这些第三方库的安装方式可以参考 Scrapy 的安装方式。

除了上述的安装方式,我们可以直接在 Pycharm 的设置里面下载。

三. Scrapy 项目开发流程

首先我们来看看爬虫框架 Scrapy 的常用命令。

1. 常用命令

命令 作用
scrapy startproject <project_name> 在当前目录下创建一个名为<project_name>的项目
scrapy settings [options] 该命令将会输出 Scrapy 默认设定;如果你在项目中运行这个命令将会输出项目的设定值
scrapy runspider <spider_file.py> 在未创建项目的情况下,运行一个编写在 python 文件中的 spider
scrapy shell [url] 以给定的URL(如果给出)或者空(没有给出URL)启动 Scrapy shell
scrapy fetch <url> 使用 Scrapy 下载器 (downloader) 下载给定的URL,并将获取到的内容送到标准输出
scrapy view <url> 在你的默认浏览器中打开给定的 URL,并以Scrapy spider 获取到的形式展现
scrapy version [-v] 输出Scrapy版本
scrapy –help 查看帮助信息
scrapy bench scrapy 基准测试,用于检测 scrapy 安装环境是否完整
scrapy crawl <spider_name> 使用你项目中的 spider 进行爬取,即启动你的项目
crapy check [-l] <spider> 运行 contract 检查,检查你项目中的错误之处
scrapy list 列出当前项目中所有可用的 spider,每行输出一个 spider
scrapy genspider [-t template] <name> <domain> 在当前项目中创建 spider

2. 创建 Scrapy 项目

在安装完成 Scrapy框架之后,我们开始创建第一个 Scrapy 项目,通过这个示例,我们了解一下 Scrapy 框架的开发流程。

创建 Scrapy 框架的过程与 Django 框架中创建项目文件有些相似。初步流程如下:

  • Step1:创建虚拟环境,并创建项目工程文件
  • Step2:安装 Scrapy 框架
  • Step3:在本地文件夹中创建相应的工程文件,然后在 Pycharm 中连接远程工程文件

然后从Ubuntu 或者 Pycharm 中进入到终端,然后在对应的虚拟环境下进入到爬虫项目工程文件夹,然后运行下面的命令:

(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider$ scrapy startproject tutorial
New Scrapy project 'tutorial', using template directory '/home/pyvip/.virtualenvs/pyspider/lib/python3.6/site-packages/scrapy/templates/project', created in:
    /home/pyvip/project/Python_Spider/Spider_Project/Quote_Spider/tutorial

You can start your first spider with:
    cd tutorial
    scrapy genspider example example.com

创建好一个名为 first_scrapy 的项目后,可使用tree命令查看项目目录下的文件,显示如下:

tutorial
├── tutorial
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       └── __init__.py
└── scrapy.cfg

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

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

3. 创建 Spider

Spider 是需要自己定义的类, Scrapy 用它来从网页里抓取内容,并解析抓取的结果。不过这个类必须继承 Scrapy 提供的 Spider 类 scrapy.Spider,还要定义 Spider 的名称和起始请求,以及怎样处理爬取的结果的方法。spider 模块我们可以使用命令行来创建,例如我们要生成 Quotes 这个 Spider,可以执行如下命令:

(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider$ cd tutorial/tutorial
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial/tutorial$ ls
__init__.py  items.py  middlewares.py  pipelines.py  __pycache__  settings.py  spiders
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial/tutorial$ scrapy genspider quotes quotes.toscrape.com
Created spider 'quotes' using template 'basic' in module:
  tutorial.spiders.quotes

执行完毕之后,spiders 文件夹中多了一个 quotes.py,它就是刚刚创建的 Spider,内容如下所示:

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'、'allowed_domains'、' start_urls' ,还有一个方法 parse

  • name:它是每个项目唯一的名字,用来区分不同的 Spider;
  • allowed domains:它是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉;
  • start_urls:它包含了 Spider 在启动时爬取的 url 列表,初始请求是由它来定义的,即 start_urls 属性用来设置一个爬虫的起始爬取点;
  • parse:它是 Spider 的一个方法。默认情况下,被调用时 start_urls 里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数。该方法负责解析返回的响应、提取数据或者进一步生成要处理的请求。

4. 创建 Item

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

创建 Item 需要继承 scrapy.Item 类,并且定义类型为 scrapy.Field 的字段。观察目标网站,我们可以获取到的内容有 'text'、'author'、'tags' 。定义 Item ,将 items.py 的内容修改如下:

import scrapy

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

5. 解析 Response

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

在这里,我们使用 CSS 选择器进行选择,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()

6. 使用 Item

Item 可以理解为字典,不过在声明的时候需要实例化,然后依次用刚才解析的结果赋值 Item 的每一个字段, 最后将 Item 返回即可。QuotesSpider 的改写如下所示:

import scrapy
from ..items import TutorialItem

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 = TutorialItem()
            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

7. 后续 Request

上面的操作实现了从初始页面抓取内容。那么,下一页的内容该如何抓取?这就需要我们从当前页面中找到信息来生成下一个请求,然后在下一个请求的页面里找到信息再构造再下一个请求。这样循环往复迭代,从而实现整站的爬取。

由于 parse() 就是解析 'text'、'author'、'tags' 的方法,而下一页的结构和刚才已经解析的页面结构是一样的,所以我们可以再次使用 parse() 方法来做页面解析。接下来我们要做的就是利用选择器得到下一页链接并生成请求,在 parse() 方法后追加如下的代码:

import scrapy
from ..items import TutorialItem

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 = TutorialItem()
            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)

在上述代码中,有四个关键步骤是爬虫的关键:

  • 01 继承scrapy.Spider;
  • 02 为Spider取名;
  • 03 设定起始爬取点;
  • 04 实现页面解析函数。
(1)继承 Scrapy.spider

Scrapy 框架提供了一个 Spider 基类,我们编写的 Spider 需要继承它:

import scrapy

class QuotesSpider(scrapy.Spider):

这个 Spider 基类实现了以下内容:

  • 供 Scrapy 引擎调用的接口,例如用来创建 Spider 实例的类方法 from_crawler;
  • 供用户使用的实用工具函数,例如可以调用 log 方法将调试信息输出到日志;
  • 供用户访问的属性,例如可以通过 settings 属性访问配置文件中的配置。
(2)为 Spider 命名

在一个 Scrapy 项目中可以实现多个 Spider,每个 Spider 需要有一个能够区分彼此的唯一标识,Spider 的类属性 name 便是这个唯一标识。例如,上述示例项目中的 Spider 名称是 quotes。执行 scrapy crawl XXX 命令时就用到了这个标识,告诉 Scrapy 使用哪个 Spider 进行爬取。

(3)设定起始爬取点

Spider 必然要从某个或某些页面开始爬取,我们称这些页面为起始爬取点,可以通过类属性 start_urls 来设定起始爬取点。start_urls 通常被实现成一个列表,其中放入所有起始爬取点的 url。看到这里,大家可能会产生疑问:我们仅定义了url列表,是谁暗中构造并提交了相应的Request对象呢?通过阅读 Spider 基类的源码可以找到答案,相关代码如下:

class Spider(object_ref):
	...
	def start_requests(self):
		for url in self.start_urls:
			yield self.make_requests_from_url(url)
	
	def make_requests_from_url(self, url):
    	return Request(url, dont_filter=True) 
    
    def parse(self, response): 
    	raise NotImplementedError 
    ...

从代码中可以看出,Spider 基类的 start_requests 方法帮助我们构造并提交了 Request 对象,对其中的原理做如下解释:

  • 实际上,对于起始爬取点的下载请求是由 Scrapy 引擎调用 Spider 对象的 start_requests 方法提交的,由于 BooksSpider 类没有实现start_requests 方法,因此引擎会调用 Spider 基类的 start_requests 方法;
  • 在start_requests 方法中,self.start_urls 便是我们定义的起始爬取点列表(通过实例访问类属性),对其进行迭代,用迭代出的每个 url 作为参数调用 make_requests_from_url 方法;
  • 在 make_requests_from_url 方法中,我们找到了真正构造 Reqeust 对象的代码,仅使用 url 和 dont_filter 参数构造 Request 对象;
  • 由于构造 Request 对象时并没有传递 callback 参数来指定页面解析函数,因此默认将 parse 方法作为页面解析函数。此时 BooksSpider 必须实现 parse 方法,否则就会调用 Spider 基类的 parse 方法,从而抛出 NotImplementedError 异常(可以看作基类定义了一个抽象接口);
  • 起始爬取点可能有多个,start_requests 方法需要返回一个可迭代对象(列表、生成器等),其中每一个元素是一个 Request 对象。这里,start_requests 方法被实现成一个生成器函数(生成器对象是可迭代的),每次由 yield 语句返回一个 Request 对象。

由于起始爬取点的下载请求是由引擎调用 Spider 对象的 start_requests 方法产生的,因此我们也可以在 BooksSpider 中实现 start_requests 方法(覆盖基类 Spider 的 start_requests 方法),直接构造并提交起始爬取点的 Request 对象。在某些场景下使用这种方式更加灵活,例如有时想为 Request 添加特定的 HTTP 请求头部,或想为 Request 指定特定的页面解析函数。

以下是通过实现 start_requests 方法定义起始爬取点的示例代码(改写QuotesSpider):

import scrapy
from ..items import TutorialItem

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    def start_requests(self):
        yield scrapy.Request('http://quotes.toscrape.com/',
                             callback=self.parse_quote,
                             headers={'User-Agent': 'Mozilla/5.0'},
                             dont_filter=True)

    def parse_quote(self, response):
        quotes = response.css('.quote')
        for quote in quotes:
            item = TutorialItem()
            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)

到此,我们介绍完了为爬虫设定起始爬取点的两种方式: 一、定义 start_urls 属性;二、实现 start_requests 方法。

(4)实现页面解析函数

页面解析函数也就是构造 Request 对象时通过 callback 参数指定的回调函数(或默认的 parse 方法)。页面解析函数是实现 Spider 中最核心的部分,它需要完成以下两项工作:

  • 使用选择器提取页面中的数据,将数据封装后(Item 或字典)提交给 Scrapy 引擎;
  • 使用选择器或 LinkExtractor 提取页面中的链接,用其构造 新的 Request 对象并提交给 Scrapy 引擎(下载链接页面)。

一个页面中可能包含多项数据以及多个链接,因此页面解析函数被要求返回一个可迭代对象(通常被实现成一个生成器函数),每次迭代返回一项数据(Item或字典)或一个 Request 对象。

8. 运行

接下来,进入目录,运行如下命令:

scrapy crawl quotes

Scrapy 运行结果如下所示:

(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider$ cd tutorial
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial$ ls
scrapy.cfg  tutorial
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial$ scrapy crawl quotes
2021-03-15 11:51:18 [scrapy.utils.log] INFO: Scrapy 2.4.1 started (bot: tutorial)
2021-03-15 11:51:18 [scrapy.utils.log] INFO: Versions: lxml 4.5.2.0, libxml2 2.9.10, cssselect 1.1.0, parsel 1.6.0, w3lib 1.22.0, Twisted 21.2.0, Python 3.6.9 (default, Jul 17 2020, 12:50:27) - [GCC 8.4.0], pyOpenSSL 20.0.1 (OpenSSL 1.1.1j  16 Feb 2021), cryptography 3.4.6, Platform Linux-4.15.0-136-generic-x86_64-with-Ubuntu-18.04-bionic
2021-03-15 11:51:18 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor
2021-03-15 11:51:18 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'tutorial',
 'NEWSPIDER_MODULE': 'tutorial.spiders',
 'ROBOTSTXT_OBEY': True,
 'SPIDER_MODULES': ['tutorial.spiders']}
2021-03-15 11:51:18 [scrapy.extensions.telnet] INFO: Telnet Password: fdef5e4b01ff9fce
2021-03-15 11:51:18 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.memusage.MemoryUsage',
 'scrapy.extensions.logstats.LogStats']
2021-03-15 11:51:18 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2021-03-15 11:51:29 [scrapy.dupefilters] DEBUG: Filtered duplicate request: <GET http://quotes.toscrape.com/page/10/> - no more duplicates will be shown (see DUPEFILTER_DEBUG to show all duplicates)
2021-03-15 11:51:29 [scrapy.core.engine] INFO: Closing spider (finished)
2021-03-15 11:51:29 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 2881,
 'downloader/request_count': 11,
 'downloader/request_method_count/GET': 11,
 'downloader/response_bytes': 23388,
 'downloader/response_count': 11,
 'downloader/response_status_count/200': 10,
 'downloader/response_status_count/404': 1,
 'dupefilter/filtered': 1,
 'elapsed_time_seconds': 10.21772,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2021, 3, 15, 3, 51, 29, 96046),
 'item_scraped_count': 100,
 'log_count/DEBUG': 112,
 'log_count/INFO': 10,
 'memusage/max': 56266752,
 'memusage/startup': 56266752,
 'request_depth_max': 10,
 'response_received_count': 11,
 'robotstxt/request_count': 1,
 'robotstxt/response_count': 1,
 'robotstxt/response_status_count/404': 1,
 'scheduler/dequeued': 10,
 'scheduler/dequeued/memory': 10,
 'scheduler/enqueued': 10,
 'scheduler/enqueued/memory': 10,
 'start_time': datetime.datetime(2021, 3, 15, 3, 51, 18, 878326)}
2021-03-15 11:51:29 [scrapy.core.engine] INFO: Spider closed (finished)

这里只是部分运行结果 ,中间一些抓取结果已省略。

首先,Scrapy 输出了当前的版本号以及正在启动的项目名称。接着输出了当前 settings.py 中一些重写后的配置。然后输出了当前所应用的 Middlewares 和 Pipelines 。Middlewares 默认是启用的 ,可以在 settings.py 中修改。Pipelines 默认是空 ,同样也可以在 settings py 中配置。

接下来就是输出各个页面的抓取结果了,可以看到爬虫一边解析,一边翻页,直至将所有内容抓取完毕,然后终止。

最后, Scrapy 输出了整个抓取过程的统计信息,如请求的字节数、请求次数、响应次数、完成原因等。

9. 保存到文件

运行完 Scrapy 后,我们只在控制台看到了输出结果,那如何保存输出结果呢?要完成这个任务其实不需要任何额外的代码,Scrapy 提供的 Feed Exports 可以轻松将抓取结果输出。例如,我们想将上面的结果保存成 JSON 文件,可以执行如下命令:

scrapy crawl quotes -o quotes.json

命令运行后,项目内多了一个 quotes.json 文件,文件包含了刚才抓取的所有内容,内容是 JSON 格式。如下图所示:
爬虫框架Scrapy(1)Scrapy基础1

另外我们还可以每一个 Item 输出一行 JSON ,输出后缀为 jl,为 jsonline 的缩写,命令如下所示:

scrapy crawl quotes -o quotes.jl
# 或者
scrapy crawl quotes -o quotes.jsonlines

输出格式还支持很多种,例如:'csv'、'xml'、'pickle'、‘marshal' 等,还支持 'ftp'、's3' 等远程输出,另外还可以通过自定义 ItemExporter来实现其他的输出。

通过 Scrapy 提供的 Feed Exports,我们可以轻松地输出抓取结果到文件。对于一些小型项目来说,这应该足够了。如果想要更复杂的输出,如输出到数据库等,我们可以使用 Item Pileline 来完成。

四. 实例——爬取书籍信息

1. 创建项目

在本地和远程服务器的相应文件夹中创建爬虫项目的文件夹,然后在 Pycharm 中打开本地文件夹,连接远程文件。
爬虫框架Scrapy(1)Scrapy基础1

从Ubuntu 或者 Pycharm 中进入终端,然后在对应的虚拟环境下进入到爬虫项目工程文件夹,再运行下面的命令:

(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book$ scrapy startproject toscrapy
New Scrapy project 'toscrapy', using template directory '/home/pyvip/.virtualenvs/pyspider/lib/python3.6/site-packages/scrapy/templates/project', created in:
    /home/pyvip/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy

You can start your first spider with:
    cd toscrapy
    scrapy genspider example example.com

2. 创建爬虫

从终端进入项目工程文件,运行如下命令:

(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy$ cd toscrapy
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy/toscrapy$ ls
__init__.py  items.py  middlewares.py  pipelines.py  settings.py  spiders
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy/toscrapy$ scrapy genspider books books.toscrape.com
Created spider 'books' using template 'basic' in module:
  toscrapy.spiders.books

3. 解析 Response

books.py 文件中修改 parse 函数,修改内容如下:

import scrapy

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

    def parse(self, response):
        # 提取数据
        for book in response.css('article.product_pod'):
            name = book.xpath('./h3/a/@title').extract_first()
            price = book.css('p.price_color::text').extract_first()
            yield {
                'name': name,
                'price': price,
            }

        # 提取链接
        next_url = response.css('ul.pager li.next a::attr(href)').extract_first()
        if next_url:
            next_url = response.urljoin(next_url)
            yield scrapy.Request(next_url, callback=self.parse)

4. 运行爬虫并保存数据

从终端进入项目工程文件,运行如下命令:

(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy/toscrapy$ scrapy crawl books
2021-03-15 18:09:09 [scrapy.utils.log] INFO: Scrapy 2.4.1 started (bot: toscrapy)
2021-03-15 18:09:09 [scrapy.utils.log] INFO: Versions: lxml 4.5.2.0, libxml2 2.9.10, cssselect 1.1.0, parsel 1.6.0, w3lib 1.22.0, Twisted 21.2.0, Python 3.6.9 (default, Jul 17 2020, 12:50:27) - [GCC 8.4.0], pyOpenSSL 20.0.1 (OpenSSL 1.1.1j  16 Feb 2021), cryptography 3.4.6, Platform Linux-4.15.0-136-generic-x86_64-with-Ubuntu-18.04-bionic
2021-03-15 18:09:09 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor
2021-03-15 18:09:09 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'toscrapy',
 'NEWSPIDER_MODULE': 'toscrapy.spiders',
 'ROBOTSTXT_OBEY': True,
 'SPIDER_MODULES': ['toscrapy.spiders']}
2021-03-15 18:09:09 [scrapy.extensions.telnet] INFO: Telnet Password: ee928a9f1bd414d4
2021-03-15 18:09:09 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.memusage.MemoryUsage',
 'scrapy.extensions.logstats.LogStats']
2021-03-15 18:09:09 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
......
......
2021-03-15 18:11:54 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books.toscrape.com/catalogue/page-50.html>
{'name': "A Spy's Devotion (The Regency Spies of London #1)", 'price': '£16.97'}
2021-03-15 18:11:54 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books.toscrape.com/catalogue/page-50.html>
{'name': "1st to Die (Women's Murder Club #1)", 'price': '£53.98'}
2021-03-15 18:11:54 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books.toscrape.com/catalogue/page-50.html>
{'name': '1,000 Places to See Before You Die', 'price': '£26.08'}
2021-03-15 18:11:54 [scrapy.core.engine] INFO: Closing spider (finished)
2021-03-15 18:11:54 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 3,
 'downloader/exception_type_count/twisted.internet.error.DNSLookupError': 3,
 'downloader/request_bytes': 15515,
 'downloader/request_count': 53,
 'downloader/request_method_count/GET': 53,
 'downloader/response_bytes': 291473,
 'downloader/response_count': 50,
 'downloader/response_status_count/200': 50,
 'elapsed_time_seconds': 164.723678,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2021, 3, 15, 10, 11, 54, 716833),
 'item_scraped_count': 1000,
 'log_count/DEBUG': 1052,
 'log_count/ERROR': 2,
 'log_count/INFO': 12,
 'memusage/max': 62898176,
 'memusage/startup': 56143872,
 'request_depth_max': 49,
 'response_received_count': 50,
 'retry/count': 2,
 'retry/max_reached': 1,
 'retry/reason_count/twisted.internet.error.DNSLookupError': 2,
 "robotstxt/exception_count/<class 'twisted.internet.error.DNSLookupError'>": 1,
 'robotstxt/request_count': 1,
 'scheduler/dequeued': 50,
 'scheduler/dequeued/memory': 50,
 'scheduler/enqueued': 50,
 'scheduler/enqueued/memory': 50,
 'start_time': datetime.datetime(2021, 3, 15, 10, 9, 9, 993155)}
2021-03-15 18:11:54 [scrapy.core.engine] INFO: Spider closed (finished)

如果要将上述爬取的数据保存到文件,则需要运行如下命令:

scrapy crawl books -o books.csv

然后在项目工程文件夹下生成文件 books.csv ,在终端查看该文件内容如下:

(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy/toscrapy$ sed -n '2,$p' books.csv | cat -n
	 1 A Light in the Attic,£51.77 
	 2 Tipping the Velvet,£53.74 
	 3 Soumission,£50.10 
	 4 Sharp Objects,£47.82 
	 5 Sapiens: A Brief History of Humankind,£54.23 
	 6 The Requiem Red,£22.65 
     7  The Dirty Little Secrets of Getting Your Dream Job,£33.34
     8  "The Coming Woman: A Novel Based on the Life of the Infamous Feminist, Victoria Woodhull",£17.93
     9  The Boys in the Boat: Nine Americans and Their Epic Quest for Gold at the 1936 Berlin Olympics,£22.60
    10  The Black Maria,£52.15
	......

上述文章内容如有错误,欢迎各位朋友在评论区留言!

上一篇:addslashes — 使用反斜线引用字符串


下一篇:【Python爬虫】一个简单的网络爬虫