摘要:关于scrapy中rules规则的使用。

代码如下:

# -*- coding: utf-8 -*-
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor


class ToscrapeRuleSpider(CrawlSpider):
    name = 'toscrape-rule'
    allowed_domains = ['toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']
    custom_settings = {
        'FEED_FORMAT': 'json',
        'FEED_EXPORT_ENCODING': 'utf-8',
        'FEED_URI': 'rule1.json'
    }
    # 必须是列表
    rules = [
        # allow和deny 都是正则表达式,表示 提取到的 a标签里的href链接必须包含"page" 字符串 不包含"tag"字符串
        # follow=False(不跟进), 只提取首页符合规则的url,然后爬取这些url页面数据,callback解析
        # Follow=True(跟进链接), 在次级url页面中继续寻找符合规则的url,如此循环,直到把全站爬取完毕
        Rule(LinkExtractor(allow=(r'/page/'), deny=(r'/tag/')), callback='parse_item', follow=False),
        # 表示 提取到的 a标签里的href链接必须包含 "tag" 字符串
        Rule(LinkExtractor(allow=(r'/tag/')), callback='parse_item', follow=True)
    ]

    def parse_item(self, response):
        print('Hi, this is an item page! %s', response.url)
        for quote in response.xpath('//div[@class="quote"]'):
            yield {
                'text': quote.xpath('./span[@class="text"]/text()').extract_first(),
                'author': quote.xpath('.//small[@class="author"]/text()').extract_first(),
                'tags': quote.xpath('.//div[@class="tags"]/a/text()').extract()
            }

简单运行流程

ToscrapeRuleSpider会首先对start_urls发起请求,然后提取start_utrls里面的a标签的href链接。假如提取到的url列表是

https://www.keepnight.com/page/1.html
https://www.keepnight.com/page/tag/1.html

规则从上到下依次匹配,一旦匹配,最不会继续往下匹配。
先从第一条规则开始匹配

Rule(LinkExtractor(allow=(r'/page/'), deny=(r'/tag/')), callback='parse_item', follow=False)

取出第一个url,发现里面包含"page"字符串并且不包含"tag"字符串符合正则,就构造Request发送请求。因为有callback所以会把请求得到的response对象发送给callback指定的parse_item函数。

接着取出第二个url,发现里面包含 "page" 字符串 但是包含 "tag" 不满足正则表达式条件,进入第二条规则

Rule(LinkExtractor(allow=(r'/tag/')), callback='parse_item', follow=True)

发现里面包含"tag"字符串满足条件,就 构造Request 并发送请求。因为指定了callback所以会把请求得到的response对象发送给callback指定的parse_item函数。同时因为follow=True,会从第二个url里得到的response中从上到下匹配规则提取符合条件的链接。
然后用提取到的链接构造Request发送请求,把请求得到的response对象发送给callback指定的parse_item函数并且从response中提取符合条件的链接....经过N次上面的操作...完成了。

深入了解

  • LinkExtractor里面的deny优先于allow
    例如 https://www.keepnight.com/page/1.html 就无法匹配下面的这条规则

    Rule(LinkExtractor(allow=(r'/page/'), deny=(r'/page/')), callback='parse_item', follow=False)
  • CrawlSpider已经重写了parse函数, 所有自动创建新的请求返回的response, 都由parse函数解析, rule无论有无callback,都由同一个_parse_response函数处理。只不过他会判断是否有follow和callback,当有callback时额外把response对象发送给指定的callback回调函数。
    所以自己不能在定义一个parse函数,后果参考 Scrapy之奇葩坑你爹:Rule 不调用callback方法_Macocoa的专栏-CSDN博客
    官方的警告:

    当编写爬虫规则时,请避免使用parse作为回调函数。 由于CrawlSpider使用parse方法来实现其逻辑,如果您覆盖了parse方法,crawl spider 将会运行失败。
  • 参数的含义

    class scrapy.linkextractors.lxmlhtml.LxmlLinkExtractor(allow=(), deny=(), allow_domains=(), deny_domains=(), deny_extensions=None, restrict_xpaths=(), restrict_css=(), tags=('a', 'area'), attrs=('href', ), canonicalize=True, unique=True, process_value=None)

    allow(正则表达式(或的列表))- 一个单一的正则表达式(或正则表达式列表),(绝对)urls必须匹配才能提取。如果没有给出(或为空),它将匹配所有链接。
    deny(正则表达式或正则表达式列表) - 一个正则表达式(或正则表达式列表),(绝对)urls必须匹配才能排除(即不提取)。它优先于allow参数。如果没有给出(或为空),它不会排除任何链接。
    allow_domains(str或list) - 单个值或包含将被考虑用于提取链接的域的字符串列表
    deny_domains(str或list) - 单个值或包含不会被考虑用于提取链接的域的字符串列表
    deny_extensions(list) - 包含在提取链接时应该忽略的扩展的单个值或字符串列表。如果没有给出,它将默认为IGNORED_EXTENSIONS在scrapy.linkextractors包中定义的列表,参考链接:https://github.com/scrapy/scrapy/blob/master/scrapy/linkextractors/__init__.py
    restrict_xpaths(str或list) - 是一个XPath(或XPath的列表),它定义响应中应从中提取链接的区域。如果给出,只有那些XPath选择的文本将被扫描链接。参见下面的例子。
    restrict_css(str或list) - 一个CSS选择器(或选择器列表),用于定义响应中应提取链接的区域。有相同的行为restrict_xpaths。
    标签(str或list) - 标签或在提取链接时要考虑的标签列表。默认为。('a', 'area')
    attrs(list) - 在查找要提取的链接时应该考虑的属性或属性列表(仅适用于参数中指定的那些标签tags )。默认为('href',)
    canonicalize(boolean) - 规范化每个提取的url(使用w3lib.url.canonicalize_url)。默认为True。
    unique(boolean) - 是否应对提取的链接应用重复过滤。
    process_value(callable) - 接收从标签提取的每个值和扫描的属性并且可以修改值并返回新值的函数,或者返回None以完全忽略链接。如果没有给出,process_value默认为。lambda x: x

  • process_value参数说明
    官方的说明:
    例如,要从此代码中提取链接:

    <a href="javascript:goToPage('../other/page.html'); return false">Link text</a>

    您可以使用以下功能process_value:

    def process_value(value):
        m = re.search("javascript:goToPage\('(.*?)'", value)
        if m:
            return m.group(1)

    具体点就是会将提取到的url传给process_value。process_value会对url进行修改之后,查找符合allow或者deny规则的url.然后利用这些url 构造Request 并发送请求。流程如下:

    从页面上提取a标签的href --> process_value -->allow或者deny规则

    代码如下:

    # -- coding utf-8 --
    from scrapy.spiders import CrawlSpider, Rule
    from scrapy.linkextractors import LinkExtractor
    import re
    
    def process_value(value):
        print("222didi")
        m = re.search("javascript:goToPage\('(.*?)'", value)
        if m:
            return "https://www.keepnight.com/gongneng3.html"
            
    class KeepNight(CrawlSpider):
        name = 'keepnight'
        allowed_domains = ['keepnight.com']
        start_urls = ['https://www.keepnight.com/zongmulu.html']
        custom_settings = {
            'FEED_FORMAT':'json',
            'FEED_EXPORT_ENCODING':'utf-8',
            'FEED_URI':'rule1.json'
        }
        # 必须是列表
        rules = [
            Rule(LinkExtractor(allow=(r'm(.*?)u'),process_value = process_value),callback='parse_item2', follow= False),
        ]
        def parse_item2(self, response):
            print('zeze %s', response.url)
            for quote in response.xpath('div[@class=quote]'):
                yield {
                    'text': quote.xpath('.span[@class=text]text()').extract_first(),
                    'author': quote.xpath('.small[@class=author]text()').extract_first(),
                    'tags': quote.xpath('.div[@class=tags]atext()').extract()
                }

    可以在scrapy crawl keepnight 输出的日志中看到 所有的请求都被替换成 https://www.keepnight.com/gongneng3.html

  • 关于LinkExtractor规则的三种形式

    # 会提取符合r'm(.*?)u'正则的链接
    Rule(LinkExtractor(allow=(r'm(.*?)u'),process_value = process_value),callback='parse_item2', follow= False)
    # 会提取id="12"的盒子里的a标签的href链接
    Rule(LinkExtractor(restrict_xpaths=("//div[@id='12']"),),callback='parse_item2', follow= False)
    # 会提取id="12"的盒子里的a标签的href链接,和上面的功能一样
    Rule(LinkExtractor(restrict_css=("#1234"),),callback='parse_item2', follow= False)
  • deny_extensions参数说明
    当链接的最后的文件名后缀为deny_extensios时,即使符合正则表达式等规则ye'ya排除这些链接。
    默认值:

    IGNORED_EXTENSIONS = [
        # archives
        '7z', '7zip', 'bz2', 'rar', 'tar', 'tar.gz', 'xz', 'zip',
    
        # images
        'mng', 'pct', 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'pst', 'psp', 'tif',
        'tiff', 'ai', 'drw', 'dxf', 'eps', 'ps', 'svg', 'cdr', 'ico',
    
        # audio
        'mp3', 'wma', 'ogg', 'wav', 'ra', 'aac', 'mid', 'au', 'aiff',
    
        # video
        '3gp', 'asf', 'asx', 'avi', 'mov', 'mp4', 'mpg', 'qt', 'rm', 'swf', 'wmv',
        'm4a', 'm4v', 'flv', 'webm',
    
        # office suites
        'xls', 'xlsx', 'ppt', 'pptx', 'pps', 'doc', 'docx', 'odt', 'ods', 'odg',
        'odp',
    
        # other
        'css', 'pdf', 'exe', 'bin', 'rss', 'dmg', 'iso', 'apk'
    ]

    例如:
    https://www.baidu.com/moou/123.rar 即使符合下面的规则

    Rule(LinkExtractor(allow=(r'm(.*?)u'),process_value = process_value),callback='parse_item2', follow= False)

    也不会被用来构造Request请求。

登陆问题

如果起始的url解析方式有所不同,那么可以重写CrawlSpider中的另一个函数parse_start_url(self, response)用来解析第一个url返回的Response。
可以重写parse_start_url,然后在里面实现登陆,然后传递cookie就行了。
参考代码:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import scrapy
from tutorial01.items import MovieItem
from scrapy.spiders.crawl import Rule, CrawlSpider
from scrapy.linkextractors import LinkExtractor


class DoubanmoviesSpider(CrawlSpider):
    name = "doubanmovies"
    allowed_domains = ["douban.com"]
    start_urls = ['https://movie.douban.com/tag/']
#     http_user='username' #http协议的基本认证功能 ;http_user和http_pass
#     http_pass='password'
    rules = ( #自动从response中根据正则表达式提取url,再根据这个url再次发起请求,并用callback解析返回的结果
        Rule(LinkExtractor(allow=(r'https://movie.douban.com/subject/\d+/')), callback="parse_item"),
        #Rule(LinkExtractor(allow=(r'https://movie.douban.com/tag/\[wW]+'))), # 从网页中提取http链接

    )


    def parse_item(self, response):
        movie = MovieItem()
        movie['name'] = response.xpath('//*[@id="content"]/h1/span[1]/text()').extract()[0]
        movie['director'] = '/'.join(response.xpath('//a[@rel="v:directedBy"]/text()').extract())
        movie['writer'] = '/'.join(response.xpath('//*[@id="info"]/span[2]/span[2]/a/text()').extract())
        movie['url'] = response.url
        movie['score'] = response.xpath('//*[@class="ll rating_num"]/text()').extract()[0]
        movie['collections'] = response.xpath('//span[@property="v:votes"]/text()').extract()[0] #评价人数
        movie['pub_date'] = response.xpath('//span[@property="v:initialReleaseDate"]/text()').extract()[0]
        movie['actor'] = '/'.join(response.css('span.actor span.attrs').xpath('.//a[@href]/text()').extract())
        movie['classification'] = '/'.join(response.xpath('//span[@property="v:genre"]/text()').extract())
        print('movie:%s  |url:%s'%(movie['name'],movie['url']))
        return movie

    def parse_start_url(self, response):
        urls = response.xpath('//div[@class="article"]//a/@href').extract()
        for url in urls:
            if 'https' not in url: # 去除多余的链接
                url = response.urljoin(url) # 补全
                print(url)
                print('*'*30)
                yield scrapy.Request(url)

参考:Scrapy笔记:CrawSpider中rules中的使用 - zhangjpn - 开发者的网上家园

参考链接

关于Scrapy crawlspider rules的规则——翻页_joe8910的博客-CSDN博客
Scrapy爬取规则(Crawling rules)如何应用 - 简书
Scrapy爬虫入门教程十二 Link Extractors(链接提取器) - 简书
Scrapy之奇葩坑你爹:Rule 不调用callback方法_Macocoa的专栏-CSDN博客
scrapy rules 规则的使用 - 简书
Scrapy笔记:CrawSpider中rules中的使用 - zhangjpn - 开发者的网上家园

文章目录