[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 国际许可协议 进行许可。