爬虫(十四):Scrapy框架(一) 初识Scrapy、第一个案例
1. scrapy框架
scrapy功能非常强大,爬取效率高,相关扩展组件多,可配置和可扩展程度非常高,它几乎可以应对所有反爬网站,是目前python中使用最广泛的爬虫框架。
1.1 scrapy介绍
1.1.1 架构介绍
scrapy是一个基于twisted的异步处理框架,是纯python实现的爬虫框架,其架构清晰,模块之间的耦合程度低,可扩展性极强,可以灵活完成各种需求。我们只需要定制开发几个模块就可以轻松实现一个爬虫。
它可以分为如下的几个部分:
engine:引擎,处理整个系统的数据流处理、触发事务,是整个框架的核心。
item:项目,它定义了爬取结果的数据结构,爬取的数据会被赋值成该item对象。
scheduler:调度器,接受引擎发过来的请求并将其加入队列中,在引擎再次请求的时候将请求提供给引擎。
downloader:下载器,下载网页内容,并将网页内容返回给蜘蛛。
spiders:蜘蛛,其内定义了爬取的逻辑和网页的解析规则,它主要负责解析响应并生成提取结果和新的请求。
item pipeline:项目管道,负责处理由蜘蛛从网页中抽取的项目,它的主要任务是清洗、验证和存储数据。
downloader middlewares:下载器中间件,位于引擎和下载器之 的钩子框架,主要处理引擎与下载器之间的请求及响应。
spider middlewares:蜘蛛中间件,位于引擎和蜘蛛之间的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求。
1.1.2 数据流
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发送给engine。
(6) engine从下载器中接收到response,并将其通过spider middleware发送给spider处理。
(7) spider处理response,并返回爬取到的item及新的request给engine。
(8) engine将spider返回的item给item pipeline,将新的request给scheduler。
(9)重复第(2)步到第(8)步,直到scheduler中没有更多的request,engine关闭该网站,爬取结束。
通过多个组件的相互协作、不同组件完成工作的不同、组件对异步处理的支持scrapy最大限度地利用了网络带宽,大大提高了数据爬取和处理的效率。
1.1.3 项目结构
scrapy框架是通过命令行来创建项目的,代码的编写还是需要ide。项目创建之后,项目文件的格式如下所示:
scrapy.cfg
project/
__init__.py
items.py
pipelines.py
settings.py
middlewares.py
spiders/
__init__.py
spider1.py
spider2.py
...
这里各个文件的功能描述如下:
scrapy.cfg:它是scrapy项目的配置文件,其内定义了项目的配置文件路径、部署相关信息等内容
items.py:它定义item数据结构,所有的item的定义都可以放这里。
pipelines.py:它定义item pipeline的实现,所有的item pipeline的实现都可以放这里。
settings.py:它定义项目的全局配置。
middlewares.py:它定义spider middlewares和downloader middlewares的实现。
spiders:其内包含一个个spider的实现,每个spider都有一个文件。
1.2 scrapy入门
接下来就写一个简单的项目,让我们对scrapy的基本用法和原理有大致的了解。
1.2.1 创建项目
首先安装scrapy模块,再创建scrapy项目。
pip install scrapy -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
创建scrapy一个项目,项目文件可以直接用scrapy命令生成:
scrapy startproject tutorial
这个命令可以在任意文件夹运行。
这个命令将会创建一个名为tutoria文件夹,文件夹结构如下所示:
1.2.2 创建spider
spider是自己定义的类,scrapy用它来从网页里抓取内容,并解析抓取的结果。不过这个类必须继承scrapy提供的spider类scrapy.spider,还要定义spider的名称和起始请求,以及怎样处理爬取后的结果的方法。
也可以使用命令行创建一个spider。比如要生成top250这个spider。
cd tutorial
scrapy genspider top250 movie.douban.com/250
进入刚才创建的tutorial文件夹,然后执行genspider命令。第一个参数是spider名称,第二个参数是网站域名。执行完毕之后,spiders文件夹中多了一个quotes.py,它就是刚刚创建的spider。
这里有三个属性——name、allowed_domains和start_urls,还有一个方法parse。
name,它是每个项目唯一的名字,用来区分不同的spider。
allowed_domains,它是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉。
start_urls,它包含了spider在启动时爬取的url列表,初始请求是由它来定义的。
parse,它是spider一个方法。默认情况下,被调用时start_urls里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数。该方法负责解析返回的响应、提取数据或者进一步生成要处理的请求。
1.2.3 创建item
item是保存爬取数据的容器,它的使用方法和字典类似。不过,相比字典,item多了额外的保护机制,可以避免拼写错误或者定义字段错误。
创建item需要继承scrapy.item类,并且定义类型为scrapy.field的字段。观察目标网站,我们可以获取到到内容有serial_number、movie_name、introduce、star、evaluate、describe。
定义item,此时修改items.py:
# -*- coding: utf-8 -*- # define here the models for your scraped items # # see documentation in: # https://docs.scrapy.org/en/latest/topics/items.html import scrapy class doubanitem(scrapy.item): # define the fields for your item here like: # name = scrapy.field() serial_number = scrapy.field() movie_name = scrapy.field() introduce = scrapy.field() star = scrapy.field() evaluate = scrapy.field() describe = scrapy.field()
这里定义了三个字段,接下来爬取时我们会使用到这个item。
1.2.4 解析response
前面已经说了,parse()方法的参数resposne是start_urls里面的链接爬取后的结果。所以在parse()方法中,我们可以直接对response变量包含的内容进行解析,比如浏览请求结果的网页源代码,或者进一步分析源代码内容,或者找出结果中的链接而得到下一个请求。我们可以看到网页中既有我们想要的结果,又有下一页的链接,这两部分内容我们都要进行处理。
首先看看网页结构:
每一页都有多个class为item的区块,每个区块内都包含serial_number、movie_name、introduce、star、evaluate、describe那么我们先找出所有的item,然后提取每一个item中的内容。
提取的方式可以是css选择器或xpath选择器,top250.py的xpath改写如下:
# -*- coding: utf-8 -*- import scrapy class top250spider(scrapy.spider): name = 'top250' allowed_domains = ['movie.douban.com'] start_urls = ['http://movie.douban.com/250/'] def parse(self, response): movie_list = response.xpath('//div[@class="item"]') for i_item in movie_list: serial_number = i_item.xpath('.//em/text()').extract_first() movie_name = i_item.xpath('.//div[@class="hd"]/a/span[1]/text()').extract_first() content = i_item.xpath('.//div[@class="bd"]/p[1]/text()').extract() for i_content in content: introduce = "".join(i_content.split()) star = i_item.xpath('.//span[@class="rating_num"]/text()').extract_first() evaluate = i_item.xpath('.//div[@class="star"]//span[4]/text()').extract_first() describe = i_item.xpath('.//p[@class="quote"]//span/text()').extract_first()
这里首先利用选择器选取所有的item,并将其赋值为movie_list变量,然后利用for循环对每个item遍历,解析每个item的内容。
1.2.5 使用item
前面定义了item,接下来就要使用它了。item可以理解为字典,不过在声明的时候需要实例化。然后依次用刚才解析的结果赋值item的每一段,最后将item返回即可。
修改top250.py文件:
# -*- coding: utf-8 -*- import scrapy class top250spider(scrapy.spider): name = 'top250' allowed_domains = ['movie.douban.com'] start_urls = ['http://movie.douban.com/250/'] def parse(self, response): movie_list = response.xpath('//div[@class="item"]') for i_item in movie_list: douban_item = doubanitem() douban_item['serial_number'] = i_item.xpath('.//em/text()').extract_first() douban_item['movie_name'] = i_item.xpath('.//div[@class="hd"]/a/span[1]/text()').extract_first() content = i_item.xpath('.//div[@class="bd"]/p[1]/text()').extract() for i_content in content: content_s = "".join(i_content.split()) douban_item['introduce'] = content_s douban_item['star'] = i_item.xpath('.//span[@class="rating_num"]/text()').extract_first() douban_item['evaluate'] = i_item.xpath('.//div[@class="star"]//span[4]/text()').extract_first() douban_item['describe'] = i_item.xpath('.//p[@class="quote"]//span/text()').extract_first() yield douban_item
这样首页的所有内容都可以被解析出来,并被赋值成一个个doubanitem。
1.2.6 后续request
前面的操作实现了从初始页面抓取内容。那么,下一页的内容该如何抓取?这就需要我们从当前页面中找到信息来生成下一个请求,然后在下一个请求的页面里找到信息再构造再下一个请求。这样 循环往复迭代,从而实现整站的爬取。
将页面拉到最底部,如图:
这里有后页按钮。查看它的源代码,可以发现它的链接是?start=25&filter=,完整链接是:,通过这个链接我们就可以构造下一个请求。
构造请求时需要用到scrapy.request这里我们传递两个参数——url和callback。
request参数:
url:它是请求链接。
callback:它是回调函数。当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数。回调函数进行解析或生成下一个请求,回调函数如上文parse()所示。
由于parse()就是解析serial_number、movie_name、introduce、star、evaluate、describe的方法,而下一页的结构和刚才已经解析的页面结构是一样的,所以我们可以再次使用parse()方法来做页面解析。
接下来我们要做的就是利用选择器得到下一页链接并生成请求,在parse()方法后追加代码:
# -*- coding: utf-8 -*- import scrapy from tutorial.items import doubanitem class top250spider(scrapy.spider): name = 'top250' allowed_domains = ['movie.douban.com'] start_urls = ['http://movie.douban.com/250/'] def parse(self, response): movie_list = response.xpath('//div[@class="item"]') for i_item in movie_list: douban_item = doubanitem() douban_item['serial_number'] = i_item.xpath('.//em/text()').extract_first() douban_item['movie_name'] = i_item.xpath('.//div[@class="hd"]/a/span[1]/text()').extract_first() content = i_item.xpath('.//div[@class="bd"]/p[1]/text()').extract() for i_content in content: content_s = "".join(i_content.split()) douban_item['introduce'] = content_s douban_item['star'] = i_item.xpath('.//span[@class="rating_num"]/text()').extract_first() douban_item['evaluate'] = i_item.xpath('.//div[@class="star"]//span[4]/text()').extract_first() douban_item['describe'] = i_item.xpath('.//p[@class="quote"]//span/text()').extract_first() yield douban_item next_link = response.xpath('//span[@class="next"]/link/@href').extract() if next_link: next_link = next_link[0] yield scrapy.request('https://movie.douban.com/top250' + next_link, callback=self.parse)
1.2.7 运行程序
进入目录,运行如下命令:
scrapy crawl top250
信息刷的太快了,上面都刷没了,只要显示中没有出现报错,这样就算成功了,大家可以自己运行一遍。
首先,scrapy输出了当前的版本号以及正在启动的项目名称。接着输出了当前settings.py中一些重写后的配置。然后输出了当前所应用的middlewares和pipelines。middlewares默认是启用的,可以settings.py中修改。pipelines 默认是空,同样也可以在settings.py中配置。
settings.py:
# -*- coding: utf-8 -*- # scrapy settings for tutorial project # # for simplicity, this file contains only settings considered important or # commonly used. you can find more settings consulting the documentation: # # https://docs.scrapy.org/en/latest/topics/settings.html # https://docs.scrapy.org/en/latest/topics/downloader-middleware.html # https://docs.scrapy.org/en/latest/topics/spider-middleware.html bot_name = 'tutorial' spider_modules = ['tutorial.spiders'] newspider_module = 'tutorial.spiders' # crawl responsibly by identifying yourself (and your website) on the user-agent #user_agent = 'tutorial (+http://www.yourdomain.com)' user_agent = 'mozilla/5.0 (macintosh; intel mac os x 10_14_2) applewebkit/537.36 (khtml, like gecko) chrome/70.0.3538.110 safari/537.36' # obey robots.txt rules # robotstxt_obey = true robotstxt_obey = false # configure maximum concurrent requests performed by scrapy (default: 16) #concurrent_requests = 32 # configure a delay for requests for the same website (default: 0) # see https://docs.scrapy.org/en/latest/topics/settings.html#download-delay # see also autothrottle settings and docs #download_delay = 3 download_delay = 0.5 # the download delay setting will honor only one of: #concurrent_requests_per_domain = 16 #concurrent_requests_per_ip = 16 # disable cookies (enabled by default) #cookies_enabled = false # disable telnet console (enabled by default) #telnetconsole_enabled = false # override the default request headers: #default_request_headers = { # 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'accept-language': 'en', #} # enable or disable spider middlewares # see https://docs.scrapy.org/en/latest/topics/spider-middleware.html #spider_middlewares = { # 'tutorial.middlewares.tutorialspidermiddleware': 543, #} # enable or disable downloader middlewares # see https://docs.scrapy.org/en/latest/topics/downloader-middleware.html #downloader_middlewares = { # 'tutorial.middlewares.tutorialdownloadermiddleware': 543, #} # enable or disable extensions # see https://docs.scrapy.org/en/latest/topics/extensions.html #extensions = { # 'scrapy.extensions.telnet.telnetconsole': none, #} # configure item pipelines # see https://docs.scrapy.org/en/latest/topics/item-pipeline.html item_pipelines = { 'tutorial.pipelines.tutorialpipeline': 300, } # enable and configure the autothrottle extension (disabled by default) # see https://docs.scrapy.org/en/latest/topics/autothrottle.html #autothrottle_enabled = true # the initial download delay #autothrottle_start_delay = 5 # the maximum download delay to be set in case of high latencies #autothrottle_max_delay = 60 # the average number of requests scrapy should be sending in parallel to # each remote server #autothrottle_target_concurrency = 1.0 # enable showing throttling stats for every response received: #autothrottle_debug = false # enable and configure http caching (disabled by default) # see https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings #httpcache_enabled = true #httpcache_expiration_secs = 0 #httpcache_dir = 'httpcache' #httpcache_ignore_http_codes = [] #httpcache_storage = 'scrapy.extensions.httpcache.filesystemcachestorage'
接下来就是输出各个页面的抓取结果了,可以看到爬虫一边解析,一边翻页,直至将所有内容取完毕,然后终止。
最后,scrapy输出了整个抓取过程的统计信息,如请求的字节数、请求次数、响应次数、完成原因等。
整个scrapy程序成功运行。我们通过非常简单的代码就完成了一个网站内容的爬取,这样相比之前一步一步写程序简洁很多。
1.2.8 保存到文件
运行完scrapy后,我们只在控制台看到了输出结果。如果想保存结果该怎么办呢?
要完成这个任务其实不需要任何额外的代码,scrapy提供的feed exports可以轻松将抓取结果输出。例如,我们想将上面的结果保存成json文件,可以执行如下命令:
scrapy crawl top250 -o top250.json
命令运行后,项目内多了一个quotes.json文件(没有就刷新),文件包含了刚才抓取的所有内容,内容是json格式。
另外我们还可以每一个item输出一行json,输出后缀为jl,为jsonline的缩写:
scrapy crawl top250 -o top250.jl
或
scrapy crawl top250 -o top250.jsonlines
输出格式还支持很多种,例如csv、xml、pickle、marshal等,还支持ftp、s3等远程输出,另外还可以通过自定义itemexporter来实现其他的输出。
例如,下面命令对应的输出分别为csv、xml、pickle、marshal格式以及ftp远程输出:
scrapy crawl top250 -o top250.csv
scrapy crawl top250 -o top250.xml
scrapy crawl top250 -o top250.pickle
scrapy crawl top250 -o top250.marshal
scrapy crawl top250 -o ftp://user:pass@ftp.example.com/path/to/top250.csv
其中,ftp输出需要正确配置用户名、密码、地址、输出路径,否则会报错。
通过scrapy提供的feed exports,我们可以轻松地输出抓取结果到文件。对于一些小型项目来说, 这应该足够了。不过如果想要更复杂的输出,如输出到数据库等,我们可以使用item pileline来完成。
推荐阅读
-
爬虫(十四):Scrapy框架(一) 初识Scrapy、第一个案例
-
Python爬虫基础之简单说一下scrapy的框架结构
-
scrapy框架下第一只爬虫!
-
荐 一、scrapy爬虫框架——概念作用和工作流程 & scrapy的入门使用
-
python爬虫之scrapy 框架学习复习整理一
-
【Scrapy爬虫】(一)Scrapy框架的创建
-
爬虫(十四):Scrapy框架(一) 初识Scrapy、第一个案例
-
Python爬虫:scrapy框架请求参数meta、headers、cookies一探究竟
-
python学习(三)scrapy爬虫框架(二)——创建一个scrapy爬虫
-
scrapy爬虫框架(二):创建一个scrapy爬虫