崔庆才python3爬虫-13章 Scrapy框架的使用


崔庆才python3爬虫-13章 Scrapy框架的使用

Scrapy框架介绍

1. 架构介绍

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

qkSa1P.md.png

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

2. 数据流

1
2
3
4
5
6
7
8
9
10
11
12
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发送给Engineo
(6) Engine从下载器中接收到Response, 并将其通过Spider Middlewares发 送 给 Spider处理。
(7) Spider处 理 Response, 并返回爬取到的Item 及 新 的 Request给 Engine
(8) Engine 将 Spider 返回的 Item 给 Item Pipeline, 将新的 Request 给 Scheduler
(9) 重复第(2)步到第(8)步,直到 Scheduler中没有更多的Request, Engine关闭该网站,爬取结束。

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

3 . 项目结构

1
2
3
4
5
6
7
8
9
10
11
scrapy.cfg
project/
_init_.p y
items.py
pipelines.py
settings.py
middlewares.py
spiders/
_init _.py
spiderl.py
spider2.py
1
2
3
4
5
6
7
这里各个文件的功能描述如下。
□ scrapy.cfg:它是Scrapy项目的配置文件,其内定义了项目的配置文件路径、部署相关信息等内容。
□ item s.py:它定义Item数据结构,所有的Item的定义都可以放这里。
□ pipelines.py:它定义 Item Pipeline的实现,所有的 Item Pipeline的实现都可以放这里。
□ settings.py: 它定义项目的全局配置。
□ middlew ares.py: 它定义Spider Middlewares和Downloader Middlewares的实现。
□ spiders:其内包含一个个Spider的实现,每个Spider都有一个文件。

4 .结语

1
2

本节介绍了 Scrapy框架的基本架构、数据流过程以及项目结构。后面我们会详细了解Scrapy的用法,感受它的强大。

Scrapy 入门

Scrapy安装

创建项目

1
2
3
创 建 一 个 Scrapy项目,项目文件可以直接用scrapy命令生成,命令如下所示:
scrapy startproject tutorial
这个命令可以在任意文件夹运行
1
2
3
4
5
6
7
8
9
10
11
12
这个命令将会创
建一个名为tutorial的文件夹,文件夹结构如下所示:
scrapy.cfg # Scrapy部署时的配置文件
tutorial # 项目的模块,需要从这里引入

_init_.py
items.py # Items的定义 ,定义爬取的数据结构
middlewares.py # Middlewares的定义 ,定义爬取时的中间件
pipelines.py # Pipelines的定义 ,定义数据管道
settings.py # 配置文件
spiders # 放置Spiders的文件夹
_init_.py

创建Spider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Spider是自己定义的类,Scrapy用它来从网页里抓取内容,并解析抓取的结果。不过这个类必须继承Scrapy提供的Spider类 scrapy.Spider,还要定义Spider的名称和起始请求,以及怎样处理爬取后的结果的方法。
也可以使用命令行创建一个Spidero 比如要生成Quotes这个 Spider,可以执行如下命令:
cd tutorial
scrapy genspider quotes quotes.toscrape.com

进入刚才创建的tutorial文件夹,然 后 执 行 genspidet命令。第一个参数是Spider的名称,第二个参数是网站域名。执行完毕之后, spiders文件夹中多了一个quotes.py,它就是刚刚创建的Spider,

内容如下所示:
import scrapy
class OuotesSpider(scrapy.Spider):
name = "quotes"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
pass
1
2
3
4
5
这里有三个属性--- name、 allowed_domains和 start_ur ls ,还有一个方法parse。
□ name,它是每个项目唯一的名字,用来区分不同的Spider。
□ allowed_domains,它是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉。
□ start_urls ,它包含了 Spider在启动时爬取的url列 表 ,初始请求是由它来定义的。
□ parse ,它是 Spider的一个方法。默认情况下,被调用时sta rt_ u rls里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数。该方法负责解析返回的响应 、提取数据或者进一步生成要处理的请求。

创建Item

1
2
3
Item是保存爬取数据的容器,它的使用方法和字典类似。不过,相比字典,Item多了额外的保护机制,可以避免拼写错误或者定义字段错误。
创建Item需要继承scrapy.Item类 ,并且定义类型为scrapy.Field的字段。观察目标网站,我们可以获取到到内容有text、author、tags。
定义 Item ,此时将items.py修改如下:
1
2
3
4
5
6
import scrapy
class Ouoteltem(scrapy.Item):
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
这里定义了三个字段,接下来爬取时我们会使用到这个Item

解析Response

1
2
前面我们看到, parse()方法的参数 resposne是start_urls里面的链接爬取后的结果。所以在parse()方法中,我们可以直接对response变量包含的内容进行解析,比如浏览请求结果的网页源代码,或者进一步分析源代码内容,或者找岀结果中的链接而得到下一个请求。
我们可以看到网页中既有我们想要的结果,又有下一页的链接,这两部分内容我们都要进行处理
1
2
3
4
5
6
7
8
9
10
首先看看网页结构,如 图 13・2所示。每一页都有多个class为quote的区块,每个区块内都包含text、author、tags。 那么我们先找出所有的quote ,然后提取每一个quote中的内容。

提取的方式可以是CSS选择器或XPath选择器。在这里我们使用CSS选择器进行选择, parse()
方法的改写如下所示:
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
text = quote.css(1 .text::text').extract_first()
author = quote.css('.autho:r::text').extrmct_first()
tags = quote.css('.tags .tag::text').extract()
1
2
这里首先利用选择器选取所有的quote,并将其赋值为quotes变量,然后利用for循环对每个quote遍历,解析每个quote的内容。
对 text来说,观察到它的class为 text,所以可以用.text选择器来选取,这个结果实际上是整个带有标签的节点,要获取它的正文内容,可以加::text来获取。这时的结果是长度为1 的列表,所以还需要用extract_first()方法来获取第一个元素。而 对 于tags来说,由于我们要获取所有的标签,所以用extract()方法获取整个列表即可。

使用Item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上文定义了Item ,接下来就要使用它了。Item可以理解为一个字典,不过在声明的时候需要实例化。然后依次用刚才解析的结果赋值Item 的每一个字段,最后 将Item返回即可。
OuotesSpider的改写如下所示:
import scrapy
from tutorial.items import Ouoteltem
class OuotesSpider(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
1
如此一来,首页的所有内容被解析出来,并被赋值成了一个个Quoteltem

后续Request

1
上面的操作实现了从初始页面抓取内容。那么,下一页的内容该如何抓取?这就需要我们从当前页面中找到信息来生成下一个请求,然后在下一个请求的页面里找到信息再构造再下一个请求。 这样循环往复迭代,从而实现整站的爬取。
1
2
3
4
5
6
将刚才的页面拉到最底部,这里有一个Next按钮。查看它的源代码,可以发现它的链接是/page/2/,全链接就是: http://quotes.toscrape.com/page/2,通过这个链接我们就可以构造下一个请求

构造请求时需要用到scrapy.Request 这里我们传递两个参数----url和callback,这两个参数的
说明如下。
url:它是请求链接。
callback:它是回调函数。当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数。回调函数进行解析或生成下一个请求,回调函数如上文的parse()所示。
1
2
3
4
5
由于parse()就是解析text、author、tags的方法,而下一页的结构和刚才已经解析的页面结构是一样的,所以我们可以再次使用parse()方法来做页面解析。
接下来我们要做的就是利用选择器得到下一页链接并生成请求,在 parse。方法后追加如下的代码:
next = response.css(' .pager .next a: :attr(href)').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url, callback=self.parse)
1
2
3
4
5
6
第一句代码首先通过CSS选择器获取下一个页面的链接,即要获取a超链接中的href属性。这里用到了::attr(href)操作。然后再调用extract_first()方法获取内容。
第二句代码调用了 urljoin()方法,urljoin()方法可以将相对URL构造成一个绝对的URL。例如,
获取到的下一页地址是/page/2, urljoin()方法处理后得到的结果就是: http://quotes.toscrape.eom/page/2/
第三句代码通过url和callback变量构造了一个新的请求,回调函数callback依然使用parse()
方法。这个请求完成后,响应会重新经过parse方法处理,得到第二页的解析结果,然后生成第二页的下一页,也就是第三页的请求。这样爬虫就进入了一个循环,直到最后一页。
通过几行代码,我们就轻松实现了一个抓取循环,将每个页面的结果抓取下来了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 = Quoteltem()
item['text'] = quote.css('.text::text').extract_first()
item['author'] = quote.css(1 .author::text').extract_first()
item['tags, ] = quote.css('.tags .tag::text').extracE()
yield item
next = response.css('.pager .next a::attr("href")').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url, callback=self.parse)

运行

1
2
3
接下来,进入目录,运行如下命令:
scrapy crawl quotes
就可以看到Scrapy的运行结果了
1
2
3
4
	首先, Scrapy输出了当前的版本号以及正在启动的项目名称。接着输出了当前settings.py中一些重写后的配置。然后输出了当前所应用的Middlewares和 Pipelines。Middlewares默认是启用的,可以在settings.py中修改。 Pipelines默认是空,同样也可以在settings.py中配置。后面会对它们进行讲解。
接下来就是输出各个页面的抓取结果了,可以看到爬虫一边解析,一边翻页,直至将所有内容抓取完毕,然后终止。
最后,Scrapy输岀了整个抓取过程的统计信息,如请求的字节数、请求次数、响应次数、完成原因等。
整个Scrapy程序成功运行。我们通过非常简单的代码就完成了一个网站内容的爬取,这样相比之前一点点写程序简洁很多

保存到文件

1
2
3
4
5
6
7
8
9
运行完 Scrapy后 ,我们只在控制台看到了输出结果。如果想保存结果该怎么办呢?
要完成这个任务其实不需要任何额外的代码, Scrapy提供的Feed Exports可以轻松将抓取结果输出。例如,我们想将上面的结果保存成JSON文件,可以执行如下命令:
scrapy crawl quotes -o quotes.json
命令运行后,项目内多了一个quotes.json文件,文件包含了刚才抓取的所有内容,内容是JSON格式。

另外我们还可以每一个Item输出一行JSO N ,输出后缀为jl, 为jsonline的缩写,命令如下所示:
scrapy crawl quotes -o quotes.jl

scrapy crawl quotes -o quotes.jsonlines
1
2
3
4
5
6
7
8
9
10
输出格式还支持很多种,例如csv、xml、pickle、marshal等,还支持 ftp、 s3等远程输出,另外还可以通过自定义ItemExporter来实现其他的输出。

例如,下面命令对应的输出分别为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

其中, ftp输出需要正确配置用户名、密码 、地址 、输出路径,否则会报错。
1
2
通 过 Scrapy提供的Feed Exports,我们可以轻松地输出抓取结果到文件。对于一些小型项目来说,
这应该足够了。不过如果想要更复杂的输出,如输出到数据库等,我们可以使用Item Pileline来完成

使用Item Pipeline

1
2
3
4
5
6
7
如果想进行更复杂的操作,如将结果保存到MongoDB数据库,或者筛选某些有用的Item ,则我们可以定义Item Pileline来实现。
Item Pipeline为项目管道。当Item生成后,它会自动被送到Item Pipeline进行处理,我们常用Item
Pipeline来做如下操作。
□ 清理HTML数据。
□ 验证爬取数据,检查爬取字段。
□ 査重并丢弃重复内容。
□ 将爬取结果保存到数据库。
1
2
3
4
5
要实现Item Pipeline很简单,只需要定义一个类并实现process_item()方法即可。启用Item Pipeline后,Item Pipeline会自动调用这个方法。 process_item()方法必须返回包含数据的字典或Item对象,或者抛出Dropltem异常。

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

接下来,我们实现一个Item Pipeline,筛掉text长度大于50的Item,并将结果保存到MongoDB
1
2
3
4
5
6
7
8
9
10
11
12
修改项目里的pipelines.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')
1
2
这段代码在构造方法里定义了限制长度为50, 实现了 process_item()方法,其参数是item和spider。首先该方法判断item 的text属性是否存在,如果不存在,则抛出Dmpltem异常;如果存在,
再判断长度是否大于50, 如果大于,那就截断然后拼接省略号,再将item 返回即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
接下来,我们将处理后的item存入MongoDB,定义另外一个Pipelineo同样在pipelines.py中,
我们实现另一个类MongoPipeline,内容如下所示:
import pymongo
class MongoPipeline(object):
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):
name = item._ class_._ name_
self.db[name].insert(diet(item))
return item
def close_spider(self, spider):
self.client.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MongoPipeline类实现了 API定义的另外几个方法。
□ from_crawler 它是一个类方法,用@classmethod标识,是一种依赖注入的方式。它的参数就是crawler,通过craw ler我们可以拿到全局配置的每个配置信息。在全局配置settings.py中,我们可以定义MONGO_URI和 MONGO_DB来指定MongoDB连接需要的地址和数据库名称,拿到配置信息之后返回类对象即可。所以这个方法的定义主要是用来获取settings.py中的配置的。
□ open_spider。当Spider开启时,这个方法被调用。上文程序中主要进行了一些初始化操作。
□ close_spider 当Spider关闭时,这个方法会调用。上文程序中将数据库连接关闭。

□ 最主要的process_item()方法则执行了数据插入操作。

定义好TextPipeline和 MongoPipeline这两个类后,我们需要在settings.py中使用它们MongoDB的连接信息还需要定义。
我们在settings.py中加入如下内容:

ITEM_PIPELINES = {
'tutorial.pipelines.TextPipeline': 300,
'tutorial.pipelines.MongoPipeline': 400,

MONGO_URI='localhost'
MONGO_DB='tutorial'
1
赋值ITEM_PIPELINES字典,键名是Pipeline的类名称,键值是调用优先级,是一个数字,数字越小则对应的Pipeline越先被调用。
1
2
3
再重新执行爬取,命令如下所示:
scrapy crawl quotes
爬取结束后, MongoDB中创建了一个tutorial的数据库、 Quoteitem的表

Item Adaptor

官方文档

1
2
3
4
5
6
7
8
9
~~~



## 结语

~~~python

我们通过抓取Quotes网站完成了整个Scrapy的简单入门。但这只是冰山一角, 还有很多内容等待我们去探索。

没有伞的孩子,必须努力奔跑!

Typewriter Mode** 已开启。

可以在视图菜单中关闭

不再显示关闭

本文标题:崔庆才python3爬虫-13章 Scrapy框架的使用

文章作者:TTYONG

发布时间:2020年03月22日 - 17:03

最后更新:2022年03月31日 - 15:03

原始链接:http://tianyong.fun/%E5%B4%94%E5%BA%86%E6%89%8Dpython3%E7%88%AC%E8%99%AB-13%E7%AB%A0(13.1%2013.2)%20Scrapy%E6%A1%86%E6%9E%B6%E7%9A%84%E4%BD%BF%E7%94%A8-%E5%85%A5%E9%97%A8.html

许可协议: 转载请保留原文链接及作者。

多少都是爱
0%