3、编写提取数据的爬虫Spider
实现一个Spider子类就像完成一系列填空题,先介绍如何填空,再详细讨论每个细节。
爬虫逻辑
编写spider之前,先考虑以下问题:①给爬虫起一个名字 ②爬虫从哪个/哪些页面开始爬取?③对于一个已下载的页面,提取其中的哪些数据?④爬取完当前页面后,接下来爬取哪个/哪些页面?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import scrapy
from bookspider.items import BookItem
class BooksSpider(scrapy.Spider): # (1)继承scrapy.Spider
name = "books" #(2)为Spider取名,即每个爬虫的唯一标识
start_urls = ['http://books.toscrape.com/'] #(3)设定爬虫起始点,可以多个
def parse(self,response): #(4)实现页面解析函数,提取数据
for sel in response.css('article.product_pod'): #每本书的信息,是书本信息的大框架
book = BookItem()
book['name'] = sel.xpath('./h3/a/@title').extract_first() #提取书名信息,(5)xpath语法
book['price'] = sel.css('p.price_color::text').extract_first() #提取书价信息,(6)css语法
yield book
#(7)提取新的待爬取url
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) #(8)构造下载请求
(1)Spider基类提供的功能
供engine调用的接口,比如用来创建Spider实例的类方法from_crawler
供用户使用的工具函数,比如log方法可以将调试信息输出到日志
供用户访问的属性,比如通过settings属性可以访问配置文件中的配置
(2)为Spider命名
一个Scrapy项目可以实现多个Spider,所以应唯一标识每个Spider
执行scrapy crawl时就用到这个标识,告诉Scrapy用哪个Spider爬虫
(3)设定起始爬虫url
start_urls通常被实现成一个列表,放入所有起始爬取点的url。设定start_urls之后,Spider基类会自动构造并提交对应的Request对象,以下是Spider基类的源码:1
2
3
4
5
6
7
8
9
10class Spider(object_ref):
……
def start_requests(self): #构造并提交Request对象
for url in self.start_urls: #遍历起始爬取点(通过实例访问类属性)
yield self.make_requests_from_url(url)
def make_requests_from_url(self,url): #真正构造Request对象
return Request(url,dont_filter=True)
def parse(self,response)
raise NotImplementedError
……
【补充1】BooksSpider类没有实现start_requests方法,所以引擎会调用Spider基类的start_requests方法产生下载请求;我们也可以在BooksSpider类中自定义start_requests方法,覆盖基类的方法1
2
3
4
5
6
7
8def start_requests(self):
yield scrapy.Request('http://books.toscrape.com/',
callback=self.parse_book,
headers={'User-Agent':'...'},
dont_filter=True)
def parse_book(response):
改用parse_book作为回调函数
……
【补充2】Spider基类构造Request对象时,没有向callback传递页面解析函数,它的默认解析函数是parse方法。所以BooksSpider类必须实现parse方法,否则会调用Spider基类的parse方法,抛出NotImplementedError异常。
【补充3】起始爬取点可能有多个,经过start_requests方法实现后,会得到一个可迭代对象(每个元素都是Request对象),每次由yield语句返回一个Request对象。
(4)网页解析
网页解析就是从页面中提取数据。有两个python模块能处理这一问题 ①BeautifulSoup(API简洁易用,速度慢) ②lxml(由c语言编写的,速度快,API复杂)。而scrapy的Selector类综合了上述两者的优点(它基于lxml库构建,并简化了API接口),它在scrpay.selector模块→from scrapy.selector import Selector
在Scrapy中,是使用Selector对象来提取页面中的数据,使用步骤如下:
【第一步】创建Selector对象(有两种方法)1
2
3
4
5
6
7
8
9
10
11#方法一:用Selector构造器
Selector对象 = Selector(text='网页文本字符串')
#方法二:用HtmlResponse(在scrapy.html模块中)构造器 + Selector构造器
响应 = Response(url='网址',body='网页文本字符串',encoding='utf-8')
Selector对象 = Selector(response=响应)
#方法三:直接使用Response对象内置的Selector对象
#访问Response对象的selector属性时,Response对象会自动创建Selector对象,并缓存以便下次使用
response = HtmlResponse(...)
response.selector
【第二步】用Selector对象的XPath或CSS选择器,选中数据
为了方便使用,可以直接对Response对象调用xpath/css选择器(即可以跳过第一步构造selector对象)。比如response.xpath(‘…’),会返回一个SelectorList对象(它是一个列表,元素是Selector对象,可以用for循环迭代访问每一个Selector对象)。SelectorList对象也可以用XPath/CSS选择器,它会作用于每一个Selector对象。1
2
3
4
5
6
7
8# scrapy源码的相关实现
class TextResponse(Response):
……
def xpath(self,query,**kwargs):
return self.selector.xpath(query,**kwargs)
def css(self,query):
return self.selector.css(query)
【第三步】提取数据
extract() 返回Unicode字符串的列表
extract_first() 只能SelectorList对象使用,用于返回第一个Selector对象的Unicode字符串,相当于s[0].extract()或者s.extract()[0]
re() 用正则表达式提取选中内容的某部分
re_first() 只能SelectorList对象使用
(5)XPath
XPath是XML路径语言(XML Path Language),是一种用来确定xml文档中某部分位置的语言。xml文档(html属于xml)是有一些列节点构成的树。节点常见类型:①根节点:整个文档树的根;②元素节点:html、body、div、p、a;③属性节点:href;④文本节点:Hello word、Click here。节点之间的关系:①父子 ②兄弟 ③祖先、后裔
【基础语法】
/节点 从根开始搜索所有子节点
//节点 从根开始搜索所有后代节点
点’.’ 代表当前节点,’.//节点’会从当前节点开始搜索
text() 选中文本
@属性名 选中属性,比如选择中所有属性是某值的节点→节点[@属性=’值’]
【其他函数】
string(参数) 返回参数的字符串值,比如s.xpath(‘string(/html/body/a/strong)’).extract(),相当于s.xpath(‘/html/body/a/strong/text()’)。区别是:如果两个文本在不同的标签下,用string可以直接将不同地方的字符串连接起来,用text()会返回这些字符串组成的列表(不会连接起来)
contains(str1,str2) 判断str1是否包含str2,返回布尔值
(6)CSS
CSS是层叠样式表,其选择器可用来确定HTML文档中某部分的位置。与XPath比较:语法比XPath简单,但功能不如XPath强大(python内部会将CSS翻译成xpath标的调用xpath方法)
【基本语法】
.值 选中所有class为该值的元素
#值 选中所有id为该值的元素
节点 选中所有该节点
节点1,节点2 同时选中两节点
节点1 节点2 选中所有节点2(后代节点)
节点1>节点2 选中所有节点2(子节点)
[属性] 选中属性,有值→[属性=值],包含该值→[属性~=值]
节点::text 选中节点下的所有文本
节点::attr(属性) 选择节点下的属性文本
(7)提取新url
提取url有两种方法:①Selector:因为链接也是页面中的数据,所以可以用与提取数据相同的方法进行提取,适用于提取少量的url或者提取规则比较简单的情形;②LinkExtractor类:是一个专门用于提取url的类,适用于提取大量url或规则比较复杂的情形
本例用的是第一种方法提取下一页链接,先用CSS选择器选中包含下一页链接的a元素并获取其href属性,然后调用response.urljoin方法计算绝对url,最后构造Request对象并提交。接下来介绍一下如何用LinkExtractor提取链接。1
2
3
4
5
6
7
8
9
10from scrapy.linkextractors import LinkExtractor
class BooksSpider(scrapy.Spider):
……
def parse(self,response):
#提取链接,下一页的url在ul.page>li.next>a里面
le = LinkExtractor(restrict_css='ul.pager li.next') #创建一个LinkExtractor对象,输入提取规则。如果不传递任何参数,就提取页面所有链接
links = le.extract_links(response) #该方法根据提取规则,在response对象所包含的页面中提取链接,返回一个列表,元素是Link对象
if links:
next_url = links[0].url #用links[0]获取Link对象,它的url属性就是链接的绝对地址
yield scrapy.Request(next_url,callback=self.parse) #构造Request对象并提交
LinkExtractor构造器的各个参数:①allow=正则表达式(或列表),提取与正则表达式匹配的链接;②deny 与allow相反,排除与正则表达式匹配的链接;③allow_domains=域名(或列表),提取指定域名的链接;④deny_domains;⑤restrict_xpaths=XPath表达式(或列表),提取XPath表达式选中区域下的链接;⑥restrict_css;⑦tags=标签(或列表),提取指定标签内的链接,默认是[‘a’,’area’];⑧attrs=属性(或列表),提取指定属性内的链接,默认是[‘href’];⑨process_value=回调函数,用该函数处理提取到的每个链接,一般返回一个字符串。
(8)构造下载请求
【构造Resquest对象(下载请求)的参数】
url 必选,是请求页面下载的url地址
callback 回调→页面解析函数,页面下载完成后调用页面解析函数
method http请求的方法,默认为’GET’
headers http请求的头部字典(dict类型)
body http请求的正文
cookies Cookie信息字典(dict类型)
meta Request的元数据字典(dict类型),与其他模块互传信息
encoding url和body的默认编码是’utf-8’
priority 指定优先级,默认为0(值小的优先下载)
dont_filte 默认为False→避免重复下载,当页面随时间变化则改为true
errback 请求出现异常/出现http错误时的回调函数
【补充】Response对象(响应)
Response只是一个基类,根据响应内容的不同有如下子类:TextResponse、HtmlResponse、XmlResponse。当一个页面下载完成时,下载器根据http响应头部中的Content-Type信息创建子类对象(爬取网页的内容是html文本,创建的是HtmlResponse对象)
HtmlResponse对象的参数:
url 响应的url地址(str)
status 响应的状态码(int)
headers 响应的头部(dict),可用get/getlist方法访问
body 响应的正文(bytes)
text 响应的正文(str)=response.body.decode(response.encoding)
encoding 正文的编码,可能从headers/正文中解析出来
request 产生该响应的Request对象
meta response.request.meta=处理响应的函数,response.meta取信息
selector Selector对象用于在Response中提取数据
xpath/css 选择器,用来提取数据,是response.selector.xpath/css的快捷方式
urljoin 当url是相对地址,response.urljoin(url)得到绝对地址