爬虫框架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 框架的架构:
它可以分为如下的几个部分:
模块 | 作用 |
---|---|
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 scrapyLooking in indexes: https://pypi.douban.com/simple...Installing collected packages: scrapySuccessfully installed scrapy-2.4.1
为确认 Scrapy
已安装成功,在 shell 中测试能否执行 Scrapy 这条命令:
(pyspider) pyvip@VIP:~$ scrapyScrapy 2.4.1 - no active projectUsage: 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 directoryUse "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 tutorialNew 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/tutorialYou 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.comCreated spider 'quotes' using template 'basic' in module: tutorial.spiders.quotes
执行完毕之后,spiders 文件夹中多了一个 quotes.py
,它就是刚刚创建的 Spider,内容如下所示:
import scrapyclass 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 scrapyclass 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 scrapyfrom ..items import TutorialItemclass 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 scrapyfrom ..items import TutorialItemclass 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 scrapyclass 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 scrapyfrom ..items import TutorialItemclass 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$ lsscrapy.cfg tutorial(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial$ scrapy crawl quotes2021-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-bionic2021-03-15 11:51:18 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor2021-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: fdef5e4b01ff9fce2021-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 格式。如下图所示:
另外我们还可以每一个 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 中打开本地文件夹,连接远程文件。
从Ubuntu 或者 Pycharm 中进入终端,然后在对应的虚拟环境下进入到爬虫项目工程文件夹,再运行下面的命令:
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book$ scrapy startproject toscrapyNew 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/toscrapyYou 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.comCreated spider 'books' using template 'basic' in module: toscrapy.spiders.books
3. 解析 Response
在 books.py
文件中修改 parse
函数,修改内容如下:
import scrapyclass 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 books2021-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-bionic2021-03-15 18:09:09 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor2021-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: ee928a9f1bd414d42021-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......
上述文章内容如有错误,欢迎各位朋友在评论区留言!