接下来介绍一个简单的项目,完成一遍Scrapy抓取流程。通过这个过程,我们可以对Scrapy的基本用法和原理有大体了解。
一,准备工作
本节要完成的任务如下。
- 创建一个Scrapy项目。
- 创建一个蜘蛛来抓取站点和处理数据。
- 通过命令行将抓取的内容导出。
- 将抓取的内容保存的到的MongoDB数据库。
二,准备工作
我们需要安装好Scrapy框架,MongoDB的和PyMongo库。
三,创建项目
创建一个Scrapy项目,文件项目可以直接用
scrapy
命令生成,命令如下所示:
scrapy startproject教程复制代码
这个命令可以在任意文件夹运行如果提示权限问题,可以加须藤运行该命令这个命令将会创建一个名为教程的文件夹,文件夹结构如下所示。:
scrapy.cfg#Scrapy部署时的配置文件教程#项目的模块,需要从这里引入 __init__.py items.py#Items的定义,定义爬取的数据结构 middlewares.py#Middlewares的定义,定义爬取时的中间件 pipelines.py#管道的定义,定义数据管道 settings.py#配置文件 蜘蛛#放置蜘蛛的文件夹 __init__.py复制代码
四,创建蜘蛛
蜘蛛是自己定义的类,Scrapy用它来从网页里抓取内容,并解析抓取的结果。不过这个类必须继承Scrapy提供的蜘蛛类
scrapy.Spider
,还要定义蜘蛛的名称和起始请求,以及怎样处理爬取后的结果的方法。
也可以使用命令行创建一个蜘蛛比如要生成行情这个蜘蛛,可以执行如下命令:
光盘教程scrapy genspider报价复制代码
进入刚才创建的教程文件夹,执行然后
genspider
命令。第一个参数是蜘蛛的名称,第二个参数是网站域名。执行完毕之后,蜘蛛文件夹中多了一个quotes.py,它就是刚刚创建的蜘蛛,内容如下所示:
import scrapy class QuotesSpider (scrapy.Spider): name = “quotes” allowed_domains = [ “quotes.toscrape.com” ] start_urls = [ 'http://quotes.toscrape.com/' ] def parse (self,response): 通过复制代码
这里有三个属性 -
name
,
allowed_domains
和
start_urls
,还有一个方法
parse
。
name
,它是每个项目唯一的名字,用来区分不同的蜘蛛。allowed_domains
,它是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉。start_urls
,它包含了蜘蛛在启动时爬取的URL列表,初始请求是由它来定义的。parse
,它是蜘蛛的一个方法。默认情况下,调用被时start_urls
里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数。该方法负责解析返回的响应,提取数据或者进一步生成要处理的请求。
五,创建项目
项目是保存爬取数据的容器,它的使用方法和字典类似。不过,相比字典,项目多了额外的保护机制,可以避免拼写错误或者定义字段错误。
创建项目继承需要
scrapy.Item
类,定义并且类型为
scrapy.Field
的字段。观察目标网站,我们可以获取到到内容有
text
,
author
,
tags
。
定义项,此时将items.py修改如下:
import scrapy class QuoteItem (scrapy.Item): text = scrapy.Field() author = scrapy.Field() tags = scrapy.Field()复制代码
这里定义了三个字段,接下来爬取时我们会使用到这个项目。
六,解析响应
上文中我们看到,
parse()
方法的参数
resposne
的英文
start_urls
里面的链接爬取后的查询查询结果。在所以
parse
方法中,可以我们直接对
response
变量所有游戏的内容进行解析,比如浏览请求结果的网页源代码,或者进一步分析源代码内容,或者找出结果中的链接而得到下一个请求
。
我们可以看到网页中既有我们想要的结果,又有下一页的链接,这两部分内容我们都要进行处理。
首先看看网页结构,如下图所示。页每一都有多个
class
为
quote
的区块,每个区块内都包含
text
, ,
author
。
tags
那么我们先找出所有的
quote
,提取然后每一个
quote
中的内容。
。提取的方式可以是CSS选择器或XPath的选择器在这里我们使用CSS选择器进行选择,
parse()
方法的改写如下所示:
高清 解析(个体经营,响应): 报价= response.css( '.quote' ) 的报价在报价: 文本= quote.css( ':: .text区段文本').extract_first() 作者= quote.css( ”。 author'text ').extract_first() tags = quote.css( '.tags .tag :: text').extract()复制代码
这里首先利用选择器选取所有的报价,其并将赋值为
quotes
变量,利用然后
for
循环对每个
quote
遍历,每个解析
quote
的内容。
对
text
来说,到观察它的
class
为
text
,所以用可以
.text
选择器来选取,这个结果实际上是整个带有标签的节点
,要获取它的正文内容,加可以
::text
来电子杂志。这时的结果是长度为1的列表,还需所以要用
extract_first()
方法来第电子杂志一个元素。对于而
tags
来说,由于我们要获取所有的标签,所以用
extract()
方法电子杂志整个列表即可。
第以一个
quote
的查询查询结果为例,各个选择方法及结果的说明如下内容
。
源码如下:
< div class = “quote” itemscope = “” itemtype = “http://schema.org/CreativeWork” > < span class = “text” itemprop = “text” > “我们创造它的世界是一个过程,我们的想法。“ < span > by < small class = ”author“ itemprop = ”author“ >阿尔伯特爱因斯坦< 一个 HREF = “/作者/阿尔伯特-爱因斯坦” >(约) < DIV 类 = “标签” > 标签: < 元 类 = “关键词” itemprop = “关键词” 内容 = “变化,深的思想,思维,世界” > < 一 类 = “标签” HREF = “/标签/变更/页/ 1 /” >变更 <一个 class="“标记”" href="“/标记/深想法/页面/" 1 ”> 深思想 < 一 类 = “标签” HREF = “/标签/思维/页面/ 1 /” >思 < 一 类 = “标签” HREF = “/标签/世界/页面/ 1 /” >世界 复制代码 一个>
不同选择器的返回结果如下内容。
1。 quote.css('.text')
[“The"> ]复制代码
2。 quote.css('.text::text')
[
3。 quote.css('.text').extract()
['span class =“text”itemprop =“text”>“我们创造它的世界是我们思考的过程。不改变我们的想法就无法改变。“ ']复制代码
4。 quote.css('.text::text').extract()
[''我们创造的世界是我们思考的过程。不改变我们的想法就无法改变。“']复制代码
5。 quote.css('.text::text').extract_first()
“我们创造它的世界是我们思考的过程。如果不改变我们的想法,它就不能改变。“复制代码
所以,对于
text
,获取结果的第一个元素即可,使用所以
extract_first()
方法,对于
tags
,要获取所有结果组成的列表,使用所以
extract()
方法。
七,使用项目
上文定义了项目,接下来就要使用它了.Item可以理解为一个字典,不过在声明的时候需要实例化。然后依次用刚才解析的结果赋值项目的每一个字段,最后将产品返回即可。
QuotesSpider
的改写如下所示:
进口 scrapy 从 tutorial.items 导入 QuoteItem 类 QuotesSpider (scrapy.Spider) : 名称= “引号” allowed_domains = [ “quotes.toscrape.com” ] start_urls = [ 'http://quotes.toscrape.com/' ] DEF 解析(个体,响应): 报价= response.css('.quote' ) 用于引用在引号: 项= QuoteItem() 项[ '文本' ] = quote.css(':: .text区段文本')。extract_first() 项目['author' ] = quote.css('.author :: text').extract_first() item [ 'tags' ] = quote.css('.tags .tag :: text').extract() yield item复制代码
如此一来,首页的所有内容被解析出来,并被赋值成了一个个
QuoteItem
。
八,后续请求
上面的操作实现了从初始页面抓取内容。那么,下一页的内容该如何抓取?这就需要我们从当前页面中找到信息来生成下一个请求,然后在下一个请求的页面里找到信息再构造再下一个请求。这样循环往复迭代,从而实现整站的爬取。
将刚才的页面拉到最底部,如下图所示。
这里有一个接着按钮查看它的源代码,可以发现它的链接是/页/ 2 /,全链接就是:HTTP://quotes.toscrape.com/page/2,通过这个链接我们就可以构造下一个请求。
构造请求时需要用到scrapy.Request这里我们传递两个参数-
url
和
callback
,这两个参数的说明如下。
url
:它是请求链接。callback
:。它是回调函数当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数回调函数进行解析或生成下一个请求,函数回调文如上的parse()
所示。
由于
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)复制代码
第一句代码首先通过CSS选择器获取下一个页面的链接,即要获取一个链接超中的
href
属性。用到这里了
::attr(href)
操作。再然后调用
extract_first()
方法电子杂志内容。
句第二代码调用了
urljoin()
方法,
urljoin()
方法可以将相对URL构造成一个绝对的URL例如,获取到的下一页地址/是/第2页。
urljoin()
方法处理后得到的结果就是:HTTP://quotes.toscrape .COM /页/ 2 /。
句第三代码通过
url
状语从句:
callback
变量构造了一个新的请求,函数回调
callback
依然使用
parse()
方法。这个请求完成后,会响应重新经过
parse
方法处理,得到第二页的解析结果,然后生成第二页的下一页,也就是第三页的请求。这样爬虫就进入了一个循环,直到最后一页。
通过几行代码,我们就轻松实现了一个抓取循环,将每个页面的结果抓取下来了。
现在,之后改写整个的
Spider
类如下所示:
进口 scrapy 从 tutorial.items 导入 QuoteItem 类 QuotesSpider (scrapy.Spider) : 名称= “引号” allowed_domains = [ “quotes.toscrape.com” ] start_urls = [ 'http://quotes.toscrape.com/' ] DEF 解析(个体,响应): 报价= response.css('.quote' ) 用于引用在引号: 项= QuoteItem() 项[ '文本' ] = quote.css(':: .text区段文本')。extract_first() 项目['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)复制代码
九,运行
接下来,进入目录,运行如下命令:
scrapy抓取报价复制代码
就可以看到Scrapy的运行结果了。
2017-02-19 13:37:20 [scrapy.utils.log]信息:Scrapy 1.3.0开始(bot:教程)2017-02-19 13:37:20 [scrapy.utils.log]信息:重写设置:{ 'NEWSPIDER_MODULE':'tutorial.spiders','SPIDER_MODULES':['tutorial.spiders'],'ROBOTSTXT_OBEY':True ,'BOT_NAME':'教程'}2017-02-19 13:37:20 [scrapy.middleware]信息:启用扩展:[ 'scrapy.extensions.logstats.LogStats', 'scrapy.extensions.telnet.TelnetConsole', 'scrapy.extensions.corestats.CoreStats']2017-02-19 13:37:20 [scrapy.middleware]信息:启用下载中间件:[ '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.stats.DownloaderStats']2017-02-19 13:37:20 [scrapy.middleware]信息:启用蜘蛛中间件:[ 'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware', 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware', 'scrapy.spidermiddlewares.referer.RefererMiddleware', 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware', 'scrapy.spidermiddlewares.depth.DepthMiddleware']2017-02-19 13:37:20 [scrapy.middleware]信息:启用项目管道:[]2017-02-19 13:37:20 [scrapy.core.engine]信息:蜘蛛打开2017-02-19 13:37:20 [scrapy.extensions.logstats]信息:爬行0页(0页/分钟),刮0项(0项/分钟)2017-02-19 13:37:20 [scrapy.extensions.telnet] DEBUG:Telnet控制台监听127.0.0.1:60232017-02-19 13:37:21 [scrapy.core.engine] DEBUG:Crawled(404)(referer:无)2017-02-19 13:37:21 [scrapy.core.engine] DEBUG:Crawled(200) (referer:无)2017-02-19 13:37:21 [scrapy.core.scraper]调试:从<200 http://quotes.toscrape.com/刮掉>{ '作者':爱因斯坦爱因斯坦', '标签':[u'change',u'deep-thoughts',u'thinking',u'world'], '文本':u'\ u201c我们创造它的世界是我们思考的过程。不改变我们的想法就不能改变。\ u201d'}2017-02-19 13:37:21 [scrapy.core.scraper]调试:从<200 http://quotes.toscrape.com/刮掉>{'作者':u'JK罗琳', 'tags':[u'abilities',u'choices'], '文本':你是我们的选择,哈利,这表明我们真正的存在,远远超过我们的能力。\ u201d'}...2017-02-19 13:37:27 [scrapy.core.engine]信息:关闭蜘蛛(完成)2017-02-19 13:37:27 [scrapy.statscollectors]信息:倾销Scrapy统计信息:{ 'downloader / request_bytes':2859, 'downloader / request_count':11, 'downloader / request_method_count / GET':11, 'downloader / response_bytes':24871, 'downloader / response_count':11, 'downloader / response_status_count / 200':10, 'downloader / response_status_count / 404':1, 'dupefilter / filtered':1, 'finish_reason':'完成', 'finish_time':datetime.datetime(2017,2,19,5,37,27,227438), 'item_scraped_count':100, 'log_count / DEBUG':113, 'log_count / INFO':7, 'request_depth_max':10, 'response_received_count':11, '调度程序/出队':10, 'scheduler / dequeued / memory':10, '调度程序/入队':10, 'scheduler / enqueued / memory':10, 'start_time':datetime.datetime(2017,2,19,5,37,20,321557)}2017-02-19 13:37:27 [scrapy.core.engine]信息:蜘蛛关闭(完成)复制代码
这里只是部分运行结果,中间一些抓取结果已省略。
首先,Scrapy输出了当前的版本号以及正在启动的项目名称。接着输出了当前settings.py中一些重写后的配置。然后输出了当前所应用的中间件和Pipelines.Middlewares默认是启用的,可以在settings.py中修改.Pipelines默认是空,同样也可以在settings.py中配置。后面会对它们进行讲解。
接下来就是输出各个页面的抓取结果了,可以看到爬虫一边解析,一边翻页,直至将所有内容抓取完毕,然后终止。
最后,Scrapy输出了整个抓取过程的统计信息,如请求的字节数,请求次数,响应次数,完成原因等。
整个Scrapy程序成功运行。我们通过非常简单的代码就完成了一个网站内容的爬取,这样相比之前一点点写程序简洁很多。
十,保存到文件
运行完Scrapy后,我们只在控制台看到了输出结果。如果想保存结果该怎么办呢?
要完成这个任务其实不需要任何额外的代码,Scrapy提供的Feed Export可以轻松将抓取结果输出。例如,我们想将上面的结果保存成JSON文件,可以执行如下命令:
scrapy抓取引号-o quotes.json复制代码
命令运行后,项目内多了一个quotes.json文件,文件包含了刚才抓取的所有内容,内容是JSON格式。
另外我们还可以每一个项目输出一行JSON,输出后缀为JL,为jsonline的缩写,命令如下所示:
scrapy抓取引号-o quotes.jl复制代码
或
scrapy抓取引号-o引用.jsonlines复制代码
输出格式还支持很多种,例如CSV,XML,咸菜,元帅等,还支持FTP,S3等远程输出,另外还可以通过自定义ItemExporter来实现其他的输出。
例如,下面命令对应的输出分别为CSV,XML,咸菜,元帅格式以及FTP远程输出:
scrapy抓取引号-o quotes.csvscrapy抓取引号-o quotes.xmlscrapy抓取引号-o quotes.picklescrapy抓取引号-o引用.marshalscrapy抓取引号-o ftp:// user:pass@ftp.example.com/path/to/quotes.csv复制代码
其中,FTP输出需要正确配置用户名,密码,地址,输出路径,否则会报错。
通过Scrapy提供的Feed Exports,我们可以轻松地输出抓取结果到文件。对于一些小型项目来说,这应该足够了。不过如果想要更复杂的输出,如输出到数据库等,我们可以使用Item Pileline来完成。
十一,使用Item Pipeline
如果想进行更复杂的操作,如将结果保存到MongoDB数据库,或者筛选某些有用的Item,则我们可以定义Item Pileline来实现。
Item Pipeline为项目管道。当Item生成后,它会自动被送到Item Pipeline进行处理,我们常用Item Pipeline来做如下操作。
- 清理HTML数据。
- 验证爬取数据,检查爬取字段。
- 查重并丢弃重复内容。
- 将爬取结果保存到数据库。
要实现Item Pipeline很简单,只需要定义一个类并实现
process_item()
方法即可。启用Item Pipeline后,Item Pipeline会自动调用这个方法。
process_item()
方法必须返回包含数据的字典或Item对象,或者抛出DropItem异常。
process_item()
方法有两个参数。一个参数是
item
,每次蜘蛛生成的项目都会作为参数传递过来。另一个参数是
spider
,就是蜘蛛的实例。
接下来,我们实现一个Item Pipeline,筛掉
text
长度大于50的Item,并将结果保存到MongoDB。
修改项目里的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'))复制代码
这段代码在构造方法里定义了限制长度为
50,了实现
process_item()
方法,参数其的英文
item
状语从句:
spide
- [R首先该方法判断。
item
的
text
属性是否存在,如果不存在,抛出则
DropItem
异常;如果存在,再判断长度是否大于50,如果大于,那就截断然后拼接省略号,再将
item
报道查看即可。
接下来,将我们处理后的
item
存入MongoDB中,定义另外一个管道同样在pipelines.py中,我们实现另一个类。
MongoPipeline
,内容如下所示:
导入 pymongo 类 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(dict(item)) return item def close_spider (self,蜘蛛): self.client.close()复制代码
MongoPipeline
类实现了API定义的另外几个方法。
from_crawler
。它是一个类方法,用@classmethod
标识,是一种依赖注入的方式。它的参数就是crawler
,通过crawler
我们可以拿到全局配置的每个配置信息。在全局配置settings.py中,可以我们定义MONGO_URI
状语从句:MONGO_DB
来指定MongoDB的连接需要的地址和数据库名称,拿到配置信息之后返回类对象即可。所以这个方法的定义主要是用来获取 settings.py中的配置的。open_spider
。当蜘蛛开启时,这个方法被调用。上文程序中主要进行了一些初始化操作。close_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'复制代码
赋值
ITEM_PIPELINES
字典搜索,键名是管道的类名称,键值是调用优先级,是一个数字,数字越小则对应的管道越先被调用。
再重新执行爬取,命令如下所示:
scrapy抓取报价复制代码
爬取结束后,MongoDB的中创建了一个教程的数据库,QuoteItem的表,如下图所示。
的长
text
已经被处理并追加了省略号,的短
text
保持不变,
author
状语从句:
tags
也。都相应保存。
十二,源代码
本节代码地址为:HTTPS://github.com/Python3WebSpider/ScrapyTutorial。
如想了解更多爬虫资讯,请关注我的个人微信公众号:进击的Coder
(二维码自动识别)