[CrawlSpider] - Scrapy爬虫详解
摘要:对CrawlSpider类的用法进行详解,重点关注其中的 Rule 和 LinkExtractor的用法以及process_links和process_req方法的使用。摘抄目的是对scrapy中的LinkExtractor的使用(一)的补充和说明。
1 绪论
在Scrapy模块中有4个现成的spider类,分别是:
- Spider
- CrawlSpider
- XMLFeedSpider
- CSVFeedSpider
Spider是最简单的爬虫也是最基础的爬虫类,其他所有的爬虫类包括自定义的爬虫类必须继承它。这一节主要讲Scrapy写爬虫最核心的内容,并从CrawlSpider类展开并开始学习如何构建最简单的爬虫程序。
在我写的第一篇文章中,我说一个爬虫无非是做下面几样事情:
- 请求(requests)目标站点的网页(文本);
- 利用正则表达式、Beautiful Soup、LXML、CSS提取数据;
- 制定爬取规则(如“下一页”等)、爬取方法(异步爬取、设置代理等);
- 存储数据。
至于如何存储数据我们可以暂时不用关心,因为在Scrapy的命令行中可以使用参数直接将数据存储到文件,可以参见Command line tool - 命令行工具。
从我以往的经验中总结,用Scrapy写爬虫再只要理清下面两点就能写成一个爬虫:
- 知道要爬哪些页面,爬完这个页面我还要再爬哪些页面,入口在哪;
- 如何从这些页面中提取数据
第2点我们在上3次的文章做了细致的介绍了,这里就不多说,在你学习完本节内容后再多多学习提取数据的方法即可:
所以,本节将带你从实例中理清写爬虫的思路及方法。
2 需求分析
假设现在你对如何用Scrapy写爬虫暂时还不清楚,手头上只有接到的任务,即你要爬取哪个网站的哪些数据。所以第一步要做的就是需求分析,理清爬取顺序,不要管其他。
任务:
爬取 -> 网站 热门标签下的所有的quotes及其作者
来看下这个页面:
scrapy view http://quotes.toscrape.com/
如图右侧红色方框即为热门标签,我们随机点一个标签进去看看:
http://quotes.toscrape.com/tag/love/
我们定义上图为标签主页,上图中红色方框表示我们需要提取的数据项。在数据的爬取中我们要最大限度地保证数据的完整性,换句话说:获取网站上存在的所有目标数据。除了标签主页的数据,那剩余的数据在哪?入口在哪里?
往下我们发现Next按钮,同时打开调试器看看它的地址:
http://quotes.toscrape.com/tag/love/page/2/
点击第二页,再找不到Next按钮了,也就是love标签下的数据只存在两页面上:
http://quotes.toscrape.com/tag/love/
http://quotes.toscrape.com/tag/love/page/2/
总结两步:
① 从主页开始获取所有右侧热门标签对应的地址:
http://quotes.toscrape.com/tag/love/
http://quotes.toscrape.com/tag/inspirational/
……
② 访问每一个标签主页,在每个标签主页中点击“Next”获取剩余数据,直到找不到Next按钮
最后补充一点:
love标签主页上的数据,分析上来讲就是第一页,那是不是说它等价于:
http://quotes.toscrape.com/tag/love/page/1/
我们发现这个链接在标签主页上有:
因此每个标签下的数据都可以按照如下顺序获取:
http://quotes.toscrape.com/tag/love/page/1/
http://quotes.toscrape.com/tag/love/page/2/
……
这里就涉及到第一重点:
下面两个网站指向的是同一页面:
http://quotes.toscrape.com/tag/love/
http://quotes.toscrape.com/tag/love/page/1/
如果我们按照上面的page页数来爬数据,那我们的数据就是重复,这个问题我们需要在下面进一步解决。
3 CrawlSpider类用法详解
先一通气将完它特有的属性和方法,然后再从仅完成上面任务给出爬虫代码、为CrawlSpider类中每个参数用法写例子。
① parse_start_url(response)
用于处理start_urls的response,它的用处是:如果需要模拟登录等操作可以重写该方法。
② Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)
Rule用于:
- 提取指定格式的链接(link_extractor);
- 过滤提取的链接(process_links);
- 对指定页面指定相应的处理方法(process_request);
- 指定页面的处理方法(callback);
- 为不同的提取链接的方法指定跟进的规则(follow);
- 给回调函数传参(cb_kwargs)。
避免使用 parse 作为回调函数(callback)
在PyCharm下按如下目录创建文件:
env:虚拟环境
simple:爬虫文件夹
Quotes_CrawlSpider.py:爬虫
run.py:用于启动爬虫,方便调试
run.py代码如下:
from scrapy import cmdline
cmdline.execute("scrapy runspider Quotes_CrawlSpider.py -o quotes.json".split())
解释:
相当与从命令行启动爬虫文件, -o quotes.json 将爬虫yield出来的item存到json文件。
3.1 完成上面爬虫任务所需的爬虫代码
Quotes_CrawlSpider.py代码如下:
# -*- coding: utf-8 -*-
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name = 'toscrape.com'
allowed_domains = ['toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
rules = (
Rule(LinkExtractor(allow=('/tag/\w+/$',)),
follow=True, # 如果有指定回调函数,默认不跟进
callback='parse_item',
process_links='process_links',),
Rule(LinkExtractor(allow=('/tag/\w+/page/\d+/',), deny=('/tag/\w+/page/1/',)),
callback='parse_item',
follow=True,),
)
@staticmethod
def process_links(links): # 对提取到的链接进行处理
for link in links:
link.url = link.url + 'page/1/'
yield link
@staticmethod
def parse_item(response): # 解析网页数据并返回数据字典
quote_block = response.css('div.quote')
for quote in quote_block:
text = quote.css('span.text::text').extract_first()
author = quote.xpath('span/small/text()').extract_first()
item = dict(text=text, author=author)
yield item
流程图:
代码详解:
提取规则1:(① - ④)
Rule(LinkExtractor(allow=('/tag/\w+/$',)), # 从主页提取标签主页的地址,利用正则表达式
follow=True, # request标签主页得到内容,继续在该内容上上应用规则提取链接
callback='parse_item', # request标签主页得到内容,对该内容应用parse_item函数提取数据
process_links='process_links',), # 用process_links方法对提取到的链接做处理,将标签主页
提取规则2:(⑤、⑥)
Rule(LinkExtractor(allow=('/tag/\w+/page/\d+/',), deny=('/tag/\w+/page/1/',)),
# 提取链接格式满足“/tag/英文字母/page/”数字/形式的,并拒绝第一页
callback='parse_item', # 指定parse_item作为页面的处理方法
follow=True,), # 需要在得到的页面继续搜索满足规则的链接
parse_start_url(response)
from scrapy.spiders import CrawlSpider
class QuotesSpider(CrawlSpider):
name = "quotes"
custom_settings = {
'LOG_LEVEL': 'INFO',
}
start_urls = ['http://quotes.toscrape.com/tag/love/']
def parse_start_url(self, response):
self.logger.info('parse_start_url %s', response.url)
next_page = response.css('li.next a::attr("href")').extract_first()
if next_page is not None:
yield response.follow(next_page, self.next_parse)
def next_parse(self, response):
self.logger.info('next_pares %s', response.url)
rule的几个参数用法示例:
# -*- coding: utf-8 -*-
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name = 'toscrape.com'
custom_settings = {
'LOG_LEVEL': 'INFO', # 设置日志级别
}
allowed_domains = ['toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
rules = (
Rule(LinkExtractor(allow=('/tag/\w+/$',)),
follow=False, # 为了测试几个参数的用法简单设定
callback='parse_item',
cb_kwargs={'tag': 'love'}, # 以key名tag作为变量名传给回调函数parse_item
process_links='process_links', # 对提取的链接做处理
process_request='process_req' # 对每个请求做处理),
)
@staticmethod
def process_links(links):
for link in links:
link.url = link.url + 'page/1/'
yield link
'''
# rep是一个reponse对象。从一个response提取url,然后用这个url构造request,req就表示这个req的来源,有兴趣可以使用rep.request.url查看一下这个值,这是我从源码里面找到的。
def process_req(self, req,rep):
if 'love' in req.url: # 我们测试当链接中包含love是转给parse_love处理response
return req.replace(callback=self.parse_love)
elif 'humor' in req.url:
return req # 如果链接中包含humor则正常用回调函数parse_item处理response
'''
def process_req(self, req):
if 'love' in req.url: # 我们测试当链接中包含love是转给parse_love处理response
return req.replace(callback=self.parse_love)
elif 'humor' in req.url:
return req # 如果链接中包含humor则正常用回调函数parse_item处理response
def parse_love(self, response):
self.logger.info('parse_love %s' % response.url)
def parse_item(self, response, tag):
self.logger.info('parse_item %s' % response.url)
self.logger.info('not %s' % tag)
运行结果:
运行流程:
当带有process_value时的运行流程
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。