Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。
框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。
Scrapy 使用了 Twisted’twɪstɪd异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。
简单来说就是一个爬虫的应用框架,更方便爬取。爬取效率高,可配置和可拓展程度非常高,几乎可以应用所有的反爬网站。
每当我们谈到scrapy框架的时候,都会用这个图来进行讲解:
它可以分为如下的几个部分。Engine。引擎,处理整个系统的数据流处理、触发事务,是整个框架的核心。
Item。项目,它定义了爬取结果的数据结构,爬取的数据会被赋值成该Item对象。Scheduler。调度器,接受引擎发过来的请求并将其加入队列中,在引擎再次请求的时候将请求提供给引擎。
Downloader。下载器,下载网页内容,并将网页内容返回给蜘蛛。Spiders。蜘蛛,其内定义了爬取的逻辑和网页的解析规则,它主要负责解析响应并生成提取结果和新的请求。
Item Pipeline。项目管道,负责处理由蜘蛛从网页中抽取的项目,它的主要任务是清洗、验证和存储数据。
Downloader Middlewares。下载器中间件,位于引擎和下载器之间的钩子框架,主要处理引擎与下载器之间的请求及响应。
Spider Middlewares。蜘蛛中间件,位于引擎和蜘蛛之间的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求。
这些组件相互协作,不同不同组件完成工作的不同、组件对异步处理的支持,Scrapy最大限度地利用了网络带宽,大大提高了数据爬取和处理的效率。
scrapy是通过命令行来创建的,创建之后文件结构如下图所示:
下面这个以我自己创建的第一个scrapy项目来进行分级图解的:大致也是这种结构
其中, mySpider 为项目名称,可以看到将会创建一个 mySpider 文件夹,目录结构大致如下:
下面来简单介绍一下各个主要文件的作用:
scrapy.cfg :项目的配置文件,定义了项目的配置文件路径。部署相关信息的内容
items.py :项目的目标文件,所有的Item Pipeline的定义都可以放在这里面。
pipelines.py :项目的管道文件,定义了Item Pipeline的实现都可以放在这里。
settings.py :项目的设置文件,定义了项目的全局配置
spiders/ :存储爬虫代码目录,其中包含了一个个Spider的实现,每个Spider都有一个文件。
上面这些都是理论基础,下面我们就开始动手实践,慢慢去理解怎么用Scrapy爬取,以及怎么搭建Scrapy框架。
我将简单介绍一下项目的构建,完成一遍Scrapy抓取流程,通过这个流程,我们可以对Scrapy的基本用法和原理有大体的了解。这次我们要采集的是光明网的新标题,时间,网址信息。
首先我们需要安装Scrapy框架
安装还是类似其他第三方库的方法是一样的
pip install scrapy
创建一个scrapy项目,项目文件可以直接用scrapy命令生成,命令如下所示:
scrapy startproject news
这个命令可以在任何文件夹运行,如果提示权限问题,可以加一个sudo运行该命令。这个命令会创建一个news的文件夹,文件夹结构就是我上面那个图。
Spider是我们自己定义的一个类,Scrapy用它来从网页抓取内容,并解析抓取的结果,不过这个类继承Scrapy提供的Spider类scrapy,还要定义Spider的名称和起始请求,以及怎样处理爬取后的结果。
我们也可以通过命令来创建一个Spider,比如要生成news_1这个Spider,可以执行如下命令:
cd C:\Users\lixue\Desktop\news\news
scrapy genspider news_1 news.gmw.cn
我们在进入刚才创建的news文件夹下面,然后执行genspider命令。第一个参数是Spider的名称,第二个参数是网站域名。执行完毕后,spider文件夹就多了一个news_1.py,我们可以看看刚刚创建的Spider:
import scrapy
class News1Spider(scrapy.Spider):
name = 'news_1'
allowed_domains = ['news.gmw.cn']
start_urls = ['http://news.gmw.cn']
def parse(self, response):
pass
这里面有三个属性-name、allowed_domains、还有一个方法parse.
name :这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。
allow_domains 是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。
start_urls 爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
parse(self, response) :解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下:
负责解析返回的网页数据(response.body),提取结构化数据(生成item)
生成需要下一页的URL请求。将start_urls的值修改为需要爬取的第一个url.
我们采集的是光明网上的几大新闻版块的新闻,所以我设置的start_urls包含多个url的列表。(前提是这些新闻版块的网页结构相同)
start_urls = ['http://news.gmw.cn/node_23548.htm','http://news.gmw.cn/node_23707.htm','http://news.gmw.cn/node_23547.htm','http://news.gmw.cn/node_23545.htm','http://news.gmw.cn/node_23708.htm','http://news.gmw.cn/node_23709.htm']
Item是保存爬取数据的容器,它的使用方法和字典类似,不过相比字典,Item多了保护机制,防止拼写错误或者自定义字段错误。
创建Item需要继承scrapy.Item类,并且定义类型为scrapy.Field字段,我们采集信息有title,url ,time
定义Item,此时将items.py修改如下:
import scrapy
class NewsItem(scrapy.Item):
title =scrapy.Field()
url =scrapy.Field()
time =scrapy.Field()
这里定义了三个字段,我们爬取会使用这个Item.
上文中我们看到,parse()方法的参数resposne的英文start_urls里面的链接爬取后的查询查询结果。在所以parse方法中,可以我们直接对response变量所有游戏的内容进行解析,比如浏览请求结果的网页源代码,或者进一步分析源代码内容,或者找出结果中的链接而得到下一个请求。我们可以看到网页中既有我们想要的结果,又有下一页的链接,这两部分内容我们都要进行处理。首先看看网页结构,如下图所示。
我们观察网页结构发现新闻版块是多条结合的,它们的结构相似,每条新闻区块包含上面的三个字段,我们接下来用xpath解析器来进行选择,parse方法的改写如下所示:
def parse(self, response):
selecter = Selector(text = response.body.decode('utf-8'))
url =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[1]/a[1]/@href').extract()
title =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[1]/a/text()').extract()
news_time =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[2]/text()').extract()
scrapy中包含了一个Selector这个独立的模块。我们可以直接用Selector这个类来构建一个选择器对象,然后调用它的相关方法如xpath(),css()来提取数据,这些和之前的xpath,css一样的用法。
由于scrapy是一个框架型的,我们在提取数据的时候少不了测试,但我们单独测试又太麻烦,所以scrapy提供一个shell命令,来进行测试。
Scrapy终端是一个交互终端,供您在未启动spider的情况下尝试及调试您的爬取代码。 其本意是用来测试提取数据的代码,不过您可以将其作为正常的Python终端,在上面测试任何的Python代码。
该终端是用来测试XPath或CSS表达式,查看他们的工作方式及从爬取的网页中提取的数据。 在编写您的spider时,该终端提供了交互性测试您的表达式代码的功能,免去了每次修改后运行spider的麻烦。
启动方式:
scrapy shell <url>
这里我们测试采用一个新闻网址就可以:
scrapy shell http://news.gmw.cn/node_23548.htm
这样运行之后,便能看到那个shell 命令窗口。
然后我们需要运行调用的第三库
然后再来测试我们的解析提取部分:
最后我们可以看看提取的东西:
最后我们发现我们都是正确提取到的标签的。
上面定义了Item,接下来就要使用它了。Item可以理解为一个字典,不过在声明的时候需要实例化,然后依次用刚才解析的结果赋值给Item的每一个字段,最后将Item返回即可。
我们改写那个Spider如下:
import scrapy
from scrapy import Request,Selector
from news.items import NewsItem
class News1Spider(scrapy.Spider):
name = 'news_1'
# allowed_domains = ['http://news.gmw.cn']
start_urls = ['http://news.gmw.cn/node_23548.htm','http://news.gmw.cn/node_23707.htm','http://news.gmw.cn/node_23547.htm','http://news.gmw.cn/node_23545.htm','http://news.gmw.cn/node_23708.htm','http://news.gmw.cn/node_23709.htm']
def parse(self, response):
selecter = Selector(text = response.body.decode('utf-8'))
url =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[1]/a[1]/@href').extract()
title =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[1]/a/text()').extract()
news_time =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[2]/text()').extract()
for i in range(len(url)):
item =NewsItem()
item['url'] =url[i]
item['title'] =title[i]
item['time'] = news_time[i]
yield item
上面这些操作从初始页面抓取内容,但是下一页改怎么抓取,这样我们需要从当前页面找到信息生成下一页的请求的链接,从而来构造下一个请求,这样循环迭代,从而实现整站的爬取。
将刚才的新闻页面拉到最低部,我们可以看到下一页的链接:
发现链接是node_23548_2.htm
,全链接是http://news.gmw.cn/node_23548_2.htm
构造请求时需要用到scrapy.Request,这里我们需要传入两个参数----url和callback.
url:它是请求链接。
callback:它是回调函数当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数回调函数进行解析或生成下一个请求,函数回调文如上的parse()所示。
下面我们来编写一下翻页请求的代码:
next =selecter.xpath('//div[(@class = "channelLeftPart")]/div/div/center/a[last()-1]/@href').extract()
if next:
print(111111111111)
url_nextpage ='http://news.gmw.cn/'+next[0]
yield scrapy.Request(url= url_nextpage,callback=self.parse)
这个判断条件是起到停止翻页的作用,一旦没有找到下一页的链接,就停止下面,下面我们可以看看完整的代码:
import scrapy
from scrapy import Request,Selector
from news.items import NewsItem
class News1Spider(scrapy.Spider):
name = 'news_1'
# allowed_domains = ['http://news.gmw.cn']
start_urls = ['http://news.gmw.cn/node_23548.htm','http://news.gmw.cn/node_23707.htm','http://news.gmw.cn/node_23547.htm','http://news.gmw.cn/node_23545.htm','http://news.gmw.cn/node_23708.htm','http://news.gmw.cn/node_23709.htm']
def parse(self, response):
selecter = Selector(text = response.body.decode('utf-8'))
url =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[1]/a[1]/@href').extract()
title =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[1]/a/text()').extract()
news_time =selecter.xpath('//div[(@class = "channelLeftPart")]/div/ul/li/span[2]/text()').extract()
for i in range(len(url)):
item =NewsItem()
item['url'] =url[i]
item['title'] =title[i]
item['time'] = news_time[i]
yield item
next =selecter.xpath('//div[(@class = "channelLeftPart")]/div/div/center/a[last()-1]/@href').extract()
if next:
print(111111111111)
url_nextpage ='http://news.gmw.cn/'+next[0]
yield scrapy.Request(url= url_nextpage,callback=self.parse)
当我们进行完上述代码修改,我们的scrapy也就写好了,接下来就是运行。
这里我们首先要进入scrapy项目的文件夹
接下来进入目录,运行如下命令:
cd C:\Users\lixue\Desktop\news\news
scrapy crawl news_1
运行完这个,我们就可以看到Scrapy的运行结果了。
当我们就按上面运行完scrapy后,我们只在控制台看到输出结果。如果想保存结果该怎么办呢?
其实要完成这个命令我们不需要额外的命令,我们只需要看需要保存文件的要求,然后选择对应的方法进行保存,比如我们想要将其保存为JSON 格式的文件,我们应该这样做:
scrapy crawl news_1 -o news_1.json
运行完这个,在我们刚才cd的路径下面就有一个news_1.json 文件了json格式,默认为Unicode编码。我们打开那个文件编码需要进行转换。
如果我们想要一个Item 输出一一行JSON,后缀名为jl,为jsonline的缩写,命令如下:
scrapy crawl news_1 -o news_1.jsonlines
或者:
scrapy crawl news_1 -o teachers.jl
scrapy还支持保存输出保存为csv,xml文件,命令分别是:
保存为csv文件:
scrapy crawl news_1 -o news_1.csv
保存为xml文件:
scrapy crawl news_1 -o news_1.xml
我们可以分别看看运行的结果:
最后我们采集的数据大概有4800条,我们大概不用一分钟就采集到4800条新闻,是不是很快,大家可以一起来使用一下,我这次也大致介绍这些,其实这个爬虫还存在一些问题,你们可以去查看一下数据,下篇博客我会深入讲解scrapy,然后完善这个爬虫。有不懂的地方可以查阅官方文档。