摘要:对CrawlSpider类的用法进行详解,重点关注其中的 Rule 和 LinkExtractor的用法以及process_links和process_req方法的使用。摘抄目的是对scrapy中的LinkExtractor的使用(一)的补充和说明。

1 绪论

在Scrapy模块中有4个现成的spider类,分别是:

  1. Spider
  2. CrawlSpider
  3. XMLFeedSpider
  4. CSVFeedSpider

Spider是最简单的爬虫也是最基础的爬虫类,其他所有的爬虫类包括自定义的爬虫类必须继承它。这一节主要讲Scrapy写爬虫最核心的内容,并从CrawlSpider类展开并开始学习如何构建最简单的爬虫程序。

在我写的第一篇文章中,我说一个爬虫无非是做下面几样事情:

  1. 请求(requests)目标站点的网页(文本);
  2. 利用正则表达式Beautiful SoupLXMLCSS提取数据;
  3. 制定爬取规则(如“下一页”等)、爬取方法(异步爬取、设置代理等);
  4. 存储数据。

至于如何存储数据我们可以暂时不用关心,因为在Scrapy的命令行中可以使用参数直接将数据存储到文件,可以参见Command line tool - 命令行工具

从我以往的经验中总结,用Scrapy写爬虫再只要理清下面两点就能写成一个爬虫:

  1. 知道要爬哪些页面,爬完这个页面我还要再爬哪些页面,入口在哪;
  2. 如何从这些页面中提取数据

第2点我们在上3次的文章做了细致的介绍了,这里就不多说,在你学习完本节内容后再多多学习提取数据的方法即可:

Easy XPath - 开始使用XPath

RegEx - 正则表达式

Selectors in Scrapy - 数据匹配的方法

所以,本节将带你从实例中理清写爬虫的思路及方法。

2 需求分析

假设现在你对如何用Scrapy写爬虫暂时还不清楚,手头上只有接到的任务,即你要爬取哪个网站的哪些数据。所以第一步要做的就是需求分析,理清爬取顺序,不要管其他。

任务:

爬取 -> 网站 热门标签下的所有quotes及其作者

来看下这个页面:

scrapy view http://quotes.toscrape.com/

CrawlSpider类用法1.png

如图右侧红色方框即为热门标签,我们随机点一个标签进去看看:

http://quotes.toscrape.com/tag/love/

CrawlSpider类用法2.png

我们定义上图为标签主页,上图中红色方框表示我们需要提取的数据项。在数据的爬取中我们要最大限度地保证数据的完整性,换句话说:获取网站上存在的所有目标数据。除了标签主页的数据,那剩余的数据在哪?入口在哪里?

往下我们发现Next按钮,同时打开调试器看看它的地址:

http://quotes.toscrape.com/tag/love/page/2/

CrawlSpider类用法3.png

点击第二页,再找不到Next按钮了,也就是love标签下的数据只存在两页面上

http://quotes.toscrape.com/tag/love/
http://quotes.toscrape.com/tag/love/page/2/

CrawlSpider类用法4.png

总结两步:

① 从主页开始获取所有右侧热门标签对应的地址:

http://quotes.toscrape.com/tag/love/
http://quotes.toscrape.com/tag/inspirational/
……

② 访问每一个标签主页,在每个标签主页中点击“Next”获取剩余数据,直到找不到Next按钮

最后补充一点:

love标签主页上的数据,分析上来讲就是第一页,那是不是说它等价于:

http://quotes.toscrape.com/tag/love/page/1/
我们发现这个链接在标签主页上有:

CrawlSpider类用法5.png

因此每个标签下的数据都可以按照如下顺序获取:

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用于:

  1. 提取指定格式的链接(link_extractor);
  2. 过滤提取的链接(process_links);
  3. 对指定页面指定相应的处理方法(process_request);
  4. 指定页面的处理方法(callback);
  5. 为不同的提取链接的方法指定跟进的规则(follow);
  6. 回调函数传参(cb_kwargs)。

避免使用 parse 作为回调函数(callback)

在PyCharm下按如下目录创建文件:

CrawlSpider类用法6.png

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

流程图:

CrawlSpider类用法7.png

代码详解:

提取规则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)

运行结果:

CrawlSpider类用法8.png

运行流程:

CrawlSpider类用法9.png

当带有process_value时的运行流程

CrawlSpider类用法10.png

文章目录