链接提取LinkExtractor与全站爬取利器CrawlSpider

LinkExtractor

对于提取链接,之前提到过可以通过Selector来提取,但Selector比较适合于爬去的连接比较简单其模式比较固定的情况。scrapy提供了另一个链接提取的方法scrapy.linkextractors.LinkExtractor,这种方法比较适合于爬去整站链接,并且只需声明一次就可使用多次。先来看看LinkExtractor构造的参数:

LinkExtractor(allow=(), deny=(), allow_domains=(), deny_domains=(), deny_extensions=None, restrict_xpaths=(), restrict_css=(), tags=('a', 'area'), attrs=('href', ), canonicalize=False, unique=True, process_value=None, strip=True)

下面看看各个参数并用实例讲解:

body = """
<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <div class='scrapyweb'>
            <p>Scrapy主站</p>
            <a href="https://scrapy.org/download/">Download</a>
            <a href="https://scrapy.org/doc/">Doc</a>
            <a href="https://scrapy.org/resources/">Resources</a>
        </div>
        <div class='scrapydoc'>
            <p>Scrapy 开发文档</p>
            <a href="https://docs.scrapy.org/en/latest/intro/overview.html">Scrapy at a glance</a>
            <a href="https://docs.scrapy.org/en/latest/intro/install.html">Installation guide</a>
            <a href="https://docs.scrapy.org/en/latest/intro/tutorial.html">Scrapy Tutorial</a>
            <a href="https://github.com/scrapy/scrapy/blob/1.5/docs/topics/media-pipeline.rst">Docs in github</a>
        </div>
    </body>
</html>
""".encode('utf8')
response = scrapy.http.HtmlResponse(url='', body = body)

allow:一个正则表达式或正则表达式的列表,只有匹配正则表达式的才会被提取出来,如果没有提供,就会爬取所有链接。

>>> pattern = r'/intro/\w+$'
>>> link_extractor = LinkExtractor(allow = pattern)
>>>
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='https://docs.scrapy.org/en/latest/intro/overview.html', text='Scrapy at a glance', fragment='', nofollow=False), Link(url='https://docs.scrapy.org/en/latest/intro/install.html', text='Installation guide', fragment='', nofollow=False), Link(url='https://docs.scrapy.org/en/latest/intro/tutorial.html', text='Scrapy Tutorial', fragment='', nofollow=False)]

deny:一个正则表达式或正则表达式的列表,与allow相反,匹配该正则表达式的链接不会被提取。

>>> pattern = r'/intro/\w+$'
>>> link_extractor = LinkExtractor(deny = pattern)
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='https://scrapy.org/download/', text='Download', fragment='', nofollow=False), Link(url='https://scrapy.org/doc/', text='Doc', fragment='', nofollow=False), Link(url='https://scrapy.org/resources/', text='Resources', fragment='', nofollow=False), Link(url='https://docs.scrapy.org/en/latest/intro/overview.html', text='Scrapy at a glance', fragment='', nofollow=False), Link(url='https://docs.scrapy.org/en/latest/intro/install.html', text='Installation guide', fragment='', nofollow=False), Link(url='https://docs.scrapy.org/en/latest/intro/tutorial.html', text='Scrapy Tutorial', fragment='', nofollow=False)]

allow_domains:域名或域名列表,该域名下的链接会被爬取;

>>> allow_domain = 'docs.scrapy.org'
>>> link_extractor = LinkExtractor(allow_domains = allow_domain)
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='https://docs.scrapy.org/en/latest/intro/overview.html', text='Scrapy at a glance', fragment='', nofollow=False), Link(url='https://docs.scrapy.org/en/latest/intro/install.html', text='Installation guide', fragment='', nofollow=False), Link(url='https://docs.scrapy.org/en/latest/intro/tutorial.html', text='Scrapy Tutorial', fragment='', nofollow=False)]

deny_domains:域名或域名列表,该域名下的链接不会被爬取;

>>> deny_domain = 'docs.scrapy.org'
>>> link_extractor = LinkExtractor(deny_domains = deny_domain)
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='https://scrapy.org/download/', text='Download', fragment='', nofollow=False), Link(url='https://scrapy.org/doc/', text='Doc', fragment='', nofollow=False), Link(url='https://scrapy.org/resources/', text='Resources', fragment='', nofollow=False)]

deny_extensions:字符串或字符串的列表,属于该后缀名的链接不会被爬取,若不提供的话,会使用默认选项;

>>> deny_extensions = 'html'
>>> link_extractor = LinkExtractor(deny_extensions = deny_extensions)
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='https://scrapy.org/download/', text='Download', fragment='', nofollow=False), Link(url='https://scrapy.org/doc/', text='Doc', fragment='', nofollow=False), Link(url='https://scrapy.org/resources/', text='Resources', fragment='', nofollow=False), Link(url='https://github.com/scrapy/scrapy/blob/1.5/docs/topics/media-pipeline.rst', text='Docs in github', fragment='', nofollow=False)]

restrict_xpahs:xpath或xpath的列表,符合该xpath的列表才会被爬取;

>>> xpath = '//div[@class="scrapyweb"]'
>>> link_extractor = LinkExtractor(restrict_xpaths=xpath)
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='https://scrapy.org/download/', text='Download', fragment='', nofollow=False), Link(url='https://scrapy.org/doc/', text='Doc', fragment='', nofollow=False), Link(url='https://scrapy.org/resources/', text='Resources', fragment='', nofollow=False)]

restrict_css:同上;

>>> css = 'div.scrapyweb'
>>> link_extractor = LinkExtractor(restrict_css=css)
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='https://scrapy.org/download/', text='Download', fragment='', nofollow=False), Link(url='https://scrapy.org/doc/', text='Doc', fragment='', nofollow=False), Link(url='https://scrapy.org/resources/', text='Resources', fragment='', nofollow=False)]

tags:tag或tag的list,提取指定标签中的链接,默认为[a,area]
attrs:属性或属性的列表,提取指定属性内的链接,默认为'href';

>>> body = b"""<img src="http://p0.so.qhmsg.com/bdr/326__/t010ebf2ec5ab7eed55.jpg"/>"""
>>> response = scrapy.http.HtmlResponse(url='', body = body)
>>> tag = 'img'
>>> attr='src'
>>> link_extractor = LinkExtractor(tags = tag, attrs=attr, deny_extensions='') #默认jpg是不会爬到的
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='http://p0.so.qhmsg.com/bdr/326__/t010ebf2ec5ab7eed55.jpg', text='', fragment='', nofollow=False)]

process_value回调函数,该函数会对每一个链接进行处理,回调函数要么返回一个处理后的链接,要么返回None表示忽略该链接,默认函数为lambda x:x

>>> from urllib.parse import urljoin
>>> def process(href):
...     return urljoin('http://example.com', href)
...
>>> body = b"""<a href="example.html"/>"""
>>> response = scrapy.http.HtmlResponse(url='', body = body)
>>> link_extractor = LinkExtractor(process_value = process)
>>> links = link_extractor.extract_links(response)
>>> links
[Link(url='http://example.com/example.html', text='', fragment='', nofollow=False)]

下面我们用LinkExtractor来提取第二篇博客如何编写一个Spider的下一页链接为例,看怎么在scrapy中应用LinkExtractor

修改后的quotes如下:

# -*- coding: utf-8 -*-
import scrapy
from ..items import QuoteItem
from scrapy.linkextractors import LinkExtractor

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']
    link_extractor = LinkExtractor(allow=r'/page/\d+/',restrict_css='li.next')  #声明一个LinkExtractor对象
#    def start_requests(self):
 #       url = "http://quotes.toscrape.com/"
  #      yield scrapy.Request(url, callback = self.parse)

    def parse(self, response):
        quote_selector_list = response.css('body > div > div:nth-child(2) > div.col-md-8 div.quote')

        for quote_selector in quote_selector_list:
            quote = quote_selector.css('span.text::text').extract_first()
            author = quote_selector.css('span small.author::text').extract_first()
            tags = quote_selector.css('div.tags a.tag::text').extract()

            yield QuoteItem({'quote':quote, 'author':author, 'tags':tags})
        links = self.link_extractor.extract_links(response) #爬取链接
        
        if links:
            yield scrapy.Request(links[0].url, callback = self.parse)

LinkExtratorCrawlSpider结合用的比较多,后面提到CrawlSpider的时候回讲到如何应用。

CrawlSpider

scrapy除了提供基础的spider类,还提供了一个更为强大的类CrawlSpiderCrawlSpider是基于Spider改造的,是为全站爬取而生的,非常适合爬取京东、知乎这张有规律的网站。CrawlSpider基于ExtractorLink制定了跟进url的规则,如果打算从网页中获得url之后继续爬取,非常适合使用 CrawlSpider

先来看下scrapy.spiders.CrawlSpiderCrawlSpider有2个新的属性:

  1. rulesRule对象的列表,定义了爬取link的规则及处理方式;
  2. parse_start_url(response):用来爬取起始相应,默认返回空列表,子类可重写,可返回Item对象或Request对象,或者它们的可迭代对象。

在来看下Rule

scrapy.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)
  1. link_extractor:LinkExtractor对象;
  2. callback:爬取后连接的回调函数,该回调函数接收Response对象,并返回Item/Response()或它们的子类(不要使用parse作为其回调,CrawlSpider使用parse方法实现了自己的逻辑);
  3. cb_kwargs:字典,用于作为**kwargs参数,传递给callback;
  4. follow:是否跟进,若callback=None,则follow默认为True,否则默认为False
  5. process_links:可调用对象,针对每一个link_extractor提取的链接会调用该对象,通常作为链接的预处理用;
  6. process_request:可调用对象,针对每一个链接构成的Request对象会调用,返回一个Request对象或None

下面以爬取http://books.toscrape.com/的网站获取书名和对象的价格来看下怎么使用CrawlSpider;代码如下:

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


class BookToscrapeSpider(CrawlSpider):
    name = 'book_toscrape'
    allowed_domains = ['books.toscrape.com']
    start_urls = ['http://books.toscrape.com/']

    rules = (
        Rule(LinkExtractor(allow=r'catalogue/[\w\-\d]+/index.html'), callback='parse_item', follow=False),  #爬取详情页,不follow
        Rule(LinkExtractor(allow=r'page-\d+.html')), #爬取下一页,默认follow
    )

    def parse_item(self, response):
        title = response.css('div.product_main h1::text').extract_first()
        price = response.css('div.product_main p.price_color::text').extract_first()
        #简单返回
        yield {
            'title':title,
            'price':price,
        }

运行scrapy crawl book_toscrape -o sell.csv,可以获取到该网站的所有书名与对应价格。

总结

本篇简单介绍了用于爬取固定模板链接的LinkExtractor,然后讲了与之经常使用的爬虫类CrawlSpider,使用CrawlSpider可以用来爬取模式比较固定的网站。下篇我们来看看scrapy中间件的使用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,227评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,755评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,899评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,257评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,617评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,757评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,982评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,715评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,454评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,666评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,148评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,512评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,156评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,112评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,896评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,809评论 2 279
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,691评论 2 272

推荐阅读更多精彩内容