Scrapy CrawlSpider

18 篇文章 0 订阅
11 篇文章 0 订阅

CrawlSpider

CrawlSpider 是 Scrapy 提供的一个通用 Spider。在 Spider 里,我们可以指定一些爬取规则来实现页面的提取,这些爬取规则有一个专门的数据结构 Rule 表示。Rule 里包含提取和跟进页面的配置,Spider 会根据Rule来确定当前页面中的哪些链接需要继续爬取、哪些页面的爬取结果需要用到哪个方法进行解析等。

Rule

CrawlSpider 里最重要的就是Rule的定义了,它的定义和参数如下所示:

class scrapy.spiders.Rule(
    link_extractor, 
    callback = None, 
    cb_kwargs = None, 
    follow = None, 
    process_links = None, 
    process_request = None
)

link_extractor:一个Link Extractor对象,用于定义爬取规则。通过它,Spider可以知道从爬取的页面中提取出哪些链接。提取出的链接会自动生成Request请求对象。
callback:即回调函数,和之前定义Request的callback有相同的意义。每次从link_extrcctor中获取到链接后,该函数将会被调用。该回调函数接收一个response作为其第一个参数。注意,避免使用parse()作为回调函数,否则CrawlSpider将会运行失败。
cb_kwargs:字典,它包含传递给回调函数的参数。
follow:布尔值,及True或False,它指定根据该规则从response中提取的链接是否需要跟进。
process_links:从link_extractor中获取到链接后会传递给这个函数,用来过滤不需要爬取的链接。
process_request:根据该Rule提取到的每个request时,该函数都会调用,对Request进行处理,该函数必须返回Request或者None。

案例实现

下面我们以中华网科技类新闻为例,来了解CrawlSpider的用法。官网链接为:http://tech.china.com/。我们需要爬取它的科技类新闻内容,链接为http://tech.china.com.articles/,页面如下图所示:
在这里插入图片描述
我们要抓取新闻列表内所有分页的新闻详情,包括标题、正文、时间、来源等信息。

新建项目

首先新建一个Scrapy项目,名为technology,如下所示:

scrapy startproject technology

接下来创建一个CrawlSpider,需要先制定一个模板。我们可以先看看有哪些可用模板,命令如下所示:

scrapy genspider -l

运行结果如下:

Available templates:
  basic  
  crawl  
  csvfeed
  xmlfeed

之前创建普通Spider的时候,我们默认使用了第一个模板basic。这次要创建CrawlSpider,就需要使用第二个模板crawl,创建命令如下:

scrapy genspider -t crawl china tech.china.com

运行之后便会生成一个CrawlSpider,其内容如下所示:

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class ChinaSpider(CrawlSpider):
    name = 'china'
    allowed_domains = ['tech.china.com']
    start_urls = ['http://tech.china.com/']

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

    def parse_item(self, response):
        item = {}
        #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
        #item['name'] = response.xpath('//div[@id="name"]').get()
        #item['description'] = response.xpath('//div[@id="description"]').get()
        return item

这次生成的Spider内容多了一个rules属性的定义。Rule的第一个参数是LinkExtractor,就是之前说的爬取规则,同时,默认的回调函数也不再是parse,而是parse_item。

定义Rule

要实现网页的爬取,我们需要做的就是定义好相应的Rule,然后再实现解析函数。

首先将start_urls修改为起始链接,代码如下所示:

start_url = ['http://tech.china.com/articles/']

之后,Spider爬取start_urls里面的每一个链接。所以这里第一个爬取的页面就是我们刚才所定义的链接。得到Response之后,Spider就会根据每一个Rule来提取这个页面内的超链接,去生成进一步的Request。接下来就要分析页面以此来定义Rule。
当前页面如下所示:
在这里插入图片描述
这里是新闻的列表页,下一步自然就是将其中的每个新闻详情链接提取出来。查看源代码,所有的链接都在id为rank-defList的节点内,具体来说是它内部的class为wntjItem item_defaultView clearfix的节点,如下图所示:
在这里插入图片描述
此处,我们可以通过LinkExtractor的restrict_xpath属性来指定,之后Spider就会从这个区域提取到相应的超链接并生成Request。另外,这些链接对应的页面其实就是新闻详情页,而我们需要解析的就是新闻的详情信息,所有此处还需要指定一个回调函数callback。

到现在,我们就可以构造出一个Rule了,代码如下所示:

Rule(LinkExtractor(restrict_xpaths='//div[@id="rank-defList"]/div[@class="wntjItem item_defaultView clearfix"]//h3/a'), callback='parse_item')

接下来,我们还要让当前页面实现分页功能,所以需要提取下一页的链接。分析可以发现下一页的链接是在class为pages的div标签下的a标签内,如下图:
在这里插入图片描述
但是,下一页节点和其他分页链接区分度不高,要取出此链接我们可以直接用XPath的文本匹配方式,所以这里我们直接用LinkExtractor的restrict_xpath属性来指定提取的链接即可。另外,我们不需要像新闻详情页一样去提取对应的页面详细信息,也就是不需要生成Item,所以不需要加callback参数。但是我们需要加一个follow参数为True,代表继续跟进匹配分析。此处的Rule定义如下:

Rule(LinkExtractor(restrict_xpaths='//div[@class="pages"]/a[contains("下一页")]'), follow=True)
解析页面

接下来我们需要做的就是解析页面内容了,将想要爬取的数据提取出来即可,可以定义一个Item,如下所示:

import scrapy


class TechnologyItem(scrapy.Item):
    title = scrapy.Field()
    url = scrapy.Field()
    text = scrapy.Field()
    datetime = scrapy.Field()
    source = scrapy.Field()
    pass

这里的字段分别指新闻标题、链接、正文、发布时间、来源。详情页预览图如下所示:
在这里插入图片描述
如果像之前一样提取内容,就直接调用response对象的xpath()、css()等方法即可。这里parse_item()方法的实现如下所示:

def parse_item(self, response):
        item = TechnologyItem()
        item['title'] = response.xpath('//h1[@id="chan_newsTitle"]/text()').extract_first()
        item['url'] = response.url
        item['text'] = response.xpath('//div[@id="chan_newsDetail"]/p/text()').extract()
        item['datetime'] = response.xpath('//div[@class="chan_newsInfo_source"]/span/text()').extract()[0]
        item['source'] = response.xpath('//div[@class="chan_newsInfo_source"]/span/text()').extract()[1]
        yield item

这样我们就把每条新闻的信息提取出来了。
此时运行一下Spider,输出内容如下图所示:
在这里插入图片描述
现在我们就成功的把每条新闻的信息提取出来了。

Item Loader

Scrapy还提供了一个Item Loader模块来帮助我们方便的提取Item。我们可以利用它对Item进行赋值,Item提供的是保存抓取数据的容器,而Item Loader提供的是填充容器的机制。

Item Loader的API如下所示:

class scrapy.loader.ItemLoader([item, selector, response, ] **kwargs)

item:它是Item对象,可以调用add_xpath()、add_css()或add_value()等方法来填充Item对象。
selector:它是Selector对象,是用来提取数据的选择器。
response:Response对象,用于使用构造选择器的Response。

一个比较典型的Item Loader实例如下所示:

from scrapy.loader import ItemLoader
from project.items import Product

def parse(self, response):
    loader = ItemLoader(item=Product(), response=response)
    loader.add_xpath('name', '//div[@id="title"]')
    loader.add_xpath('price', '//p[@id="price"]')
    loader.add_css('stock', 'p.stock')
    loader.add_value('date', 'today')
    return loader.load_item()

这里首先声明一个Product Item,用该Item和Response对象实例化ItemLoader,然后调用一系列方法对不同的属性进行赋值,最后调用load_item()方法实现Item的解析。

另外,其中有一些内置的Processor,我们可以自己定义一些Processor来处理数据,在调用load_item()方法时,会先调用Output Processor来处理收集到的数据,然后再存入Item中,这样才会生成最终的Item。

下面介绍一些内置的Processor。

Identity
Identity是最简单的Processor,不进行任何的处理,直接返回原来的数据。

TakeFirst
TakeFirst返回列表的第一个非空值,类似于extract_first()的功能,常用作Output Processor,如下所示:

 from itemloaders import processors
 processor = processors.TakeFirst()
 print(processor(['',1,2,3])) 

输出结果如下所示:

1

经过此Processor处理后的结果返回了第一个不为空的值

Join
Join方法相当于字符串的join()方法,可以把列表拼接为字符串,字符串默认使用空格分隔,如下所示:

 from itemloaders import processors
 processor = processors.Join()
 print(processor([0,1,2,3])) 

输出结果如下所示:

0 1 2 3

也可以通过参数更改默认的分隔符,例如改成逗号:

 from itemloaders import processors
 processor = processors.Join(',')
 print(processor([0,1,2,3])) 

结果如下:

0,1,2,3

Compose
Compose是用给定的多个函数组合而成的Processor,每个输入值会被传递到第一个参数,其输出结果再传递到第二个函数,以此类推,直到最后一个函数返回整个处理器的输出,如下所示:

 from itemloaders import processors
 processor = processors.Compose(str.upper, str.strip)
 print(processor(' hello world')) 

运行结果如下:

HELLO WORLD

在这里我们构造了一个Compose Processor,传入一个开头带有空格的字符串。它的参数有两个:第一个是str.upper,就是将字母全部转为大写;第二个是str.strip,就是取出头尾空白字符。Compose会依次调用两个参数,最终的结果就是字符串全部转化为大写且去除了开头的空格。

MapCompose
与Compose类似,不同之处在于它可以迭代处理一个列表输入值,如下所示:

 from itemloaders import processors
 processor = processors.Compose(str.upper, str.strip)
 print(processor(['hello','world'])) 

运行结果如下:

['HELLO','WORLD']

被处理的内容是一个可迭代对象,MapCompose会将该对象遍历然后依次处理。

案例

在上述案例中,如果我们要使用Item Loader进行Item的操作,我们可以改写parse_item(),如下所示:

 def parse_item(self,response):
        loader = ChinaLoader(item=TechnologyItem(),response=response)
        loader.add_xpath('title','//h1[@id="chan_newsTitle"]/text()')
        loader.add_value('url',response.url) 
        loader.add_xpath('text','//div[@id="chan_newsDetail"]/p/text()')
        loader.add_xpath('datetime','//div[@class="chan_newsInfo_source"]/span/text()')
        loader.add_xpath('source','//div[@class="chan_newsInfo_source"]/span[position()=2]/text()')
        yield loader.load_item()

这里我们定义了一个ItemLoader的子类,名为ChinaLoader,其实现如下所示:

from scrapy.loader import ItemLoader
from itemloaders import processors

class NewsLoader(ItemLoader):
    default_output_processor = processors.TakeFirst()

class ChinaLoader(NewsLoader):
    text_out = processors.Compose(processors.Join(),lambda s:s.strip())
    source_out = processors.Compose(lambda a:a[0].split(':')[1],lambda s:s.strip())

ChinaLoader继承了NewsLoader类,其内部定义了一个默认的Output Processor为TakeFirst,这相当于之前定义的extract_first()方法的功能。我们在ChinaLoader中定义了text_out和source_out字段,分别实现了相应的功能。

至此,将代码重新运行,提取效果是一样的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值