This page looks best with JavaScript enabled

Scrapy从入门到弃坑(3):CrawlSpider与ItemLoader

 ·  ☕ 5 min read · 👀... views

想必各位一定知道scrapy集成了几个可用模板,其中的CrawlSpider模板是Scrapy提供的一个通用Spider模板,可以方便的通过规则抽取来完成类似站点的爬取,这样便可省去很多重复代码,在这一节中,笔者将会通过配置一个CrawlSpider来爬取交易猫网站,并通过ItemLoader来填充Item。

各位注意,不管是CrawlSpider,ItemLoader还是后面几节要介绍的Scrapy-Redis分布式爬取实现,都绝非是几篇博文能介绍清楚的,希望各位在阅读本文对这些功能有一定的了解后,务必去阅读官方文档或者Scrapy的源代码。安利一下笔者好友 @Liu 对Scrapy官方文档的翻译项目

https://github.com/v5yangzai/scrapy1.5-chinese-document.git

首先我们先通过命令来查看一下可用模板

1
2
3
4
5
6
root@kali:~# scrapy genspider -l
Available templates:
 basic
 crawl
 csvfeed
 xmlfeed

之前我们用的是默认的basic模板,现在我们通过-t参数指定模板生成spider

1
scrapy genspider -t crawl crawl_jiaoym www.jiaoyimao.com

然后我们就可以在spiders目录下看到crawl_jiaoym.py这个通过Crawl模板生成的spider

0X01 CrawlSpider

首先,先贴上CrawlSpider的官方文档地址:http://scrapy.readthedocs.io/en/latest/topics/spiders.html#crawlspider

CrawlSpider继承于Spider类它还提供了一个非常重要的属性:rules,它是爬取规则属性,是包含一个或多个Rule对象的列表。每个Rule对爬取网站的动作都做了定义,CrawlSpider会读取rules的每一个Rule并进行解析。

CrawlSpider其定义了一些规则(rule)来提供跟进link的方便的机制,通过这个机制可以为我们省去很多代码。Rule是一个特殊的数据结构,我们可以在其中定义提取和跟进页面的配置,Spider会根据Rule来确定当前页面中的哪些链接需要继续爬取、哪些页面的爬取结果需要用哪个方法解析等,它的定义与参数如下

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

鉴于Rule参数说明,网上已有很多详解,这里不再过多阐述,懒癌晚期的读者可以参考这篇节选自崔大爬虫教程的博文:http://:https://blog.csdn.net/liukuan73/article/details/80459435,本篇博文本着实战的原则只介绍爬虫的设计。

在前两篇博文中,我们已经分析过交易猫网站,我们知道了他们把下一页的链接保存在class属性为page-btn的a标签中,那么我们可以写一条规则

1
Rule(LinkExtractor(restrict_css='a[class="page-btn"]'),follow=True),

这里做两个说明,follow参数的功能是跟进,它指定根据该规则从response提取的链接是否需要跟进。如果callback参数为None,follow默认设置为True,否则默认为False。然后就是这个restrict_css/xpaths了,这两个参数曾经坑惨我了,通过一大番折腾后才发现,restrict_css/xpaths必须指定一个标签或者完整的url,框架会自动化的识别其中的url并发送request,但是如果你指定了一个属性值而且它是一个不完整的url,那么就会报错。

然后我们可以依样画葫芦,将商品链接也提取出来。与上条规则有所不同的是这里指定了callback回调函数,商品页面的response将由此函数处理。

1
Rule(LinkExtractor(allow=r'goods\\/[0-9]*\\.html',restrict_css='a[href]'),callback='parse_items'),

注意,在CrawlSpider中,要避免使用parse()作为回调函数。由于CrawlSpider使用parse()方法来实现其逻辑,如果parse()方法覆盖了,CrawlSpider将会运行失败。

0x02 ItemLoader

在上一篇博文中,我们通过response.css.extract()/response.xpath.extract()获取到各条数据并手动填充至Item,代码写完后,我们可以看到,代码非常乱,那么官方有没有提供什么方法能简便的填充Item?当然有!Item Loader提供了一种填充容器的机制,不仅能让数据提取更加规则化,还能极大的简化省略代码。

1
loader = ItemLoader(item=JiaoyimaoItem(),response=response)

这里我们先声明了一个Item,用JiaoyimaoItem和Response对象实例化ItemLoader,并用add_value方法填充字段值。

1
2
3
loader.add_value('name',name)
loader.add_value('url',url)
yield loader.load_item()

ItemLoader可以指定re参数来匹配值,还可通过输入/输出处理函数对值进行修改,如

1
2
3
from scrapy.loader.processors import Join, MapCompose, TakeFirst

loader.add_value('name',name,TakeFirst(),re='(.*)')

关于输入/输出处理器的使用详解请各位读者参考官方文档。ItemLoader甚至还提供了add_xapth,add_css等方法可以帮助我们直接从response中提取到信息,我们可以通过这些方法获取图片链接填充至image_urls字段并对接ImagePipeline实现图片下载。

1
2
3
4
img_loader = ItemLoader(item=JiaoyimaoItem(),response=response)
img_loader.add_css('image_urls','ul[class="slider-items"] li a::attr(data-url)')

yield img_loader.load_item()

然后我们修改一下MissPipeline,以剔除那些存有图片链接的Item

1
2
3
4
5
6
7
class MissPipeline(object):

 def process_item(self, item, spider):
if 'image_urls' in item or item['name'] == None:
return DropItem('Missing Item')
else:
return item

最后我们在settings.py中启动这两个pipeline后启动一下爬虫便可看到成功实现图片下载和Item保存。

好了,教程到此为止,希望各位去阅读官方文档或Scrapy源代码,以对其有更深入的了解与认识,最后附上完整Spider代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# -*- coding: utf-8 -*-
from scrapy import Request
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy.loader import ItemLoader
from jiaoyimao.items import JiaoyimaoItem
from scrapy.loader.processors import Join, MapCompose, TakeFirst

class CrawlJiaoymSpider(CrawlSpider):
name = 'crawl_jiaoym'
allowed_domains = ['www.jiaoyimao.com']
start_urls = ['https://www.jiaoyimao.com/g4514/']

 rules = (
Rule(LinkExtractor(restrict_css='a[class="page-btn"]'),follow=True),
Rule(LinkExtractor(allow=r'goods\\/[0-9]*\\.html',restrict_css='a[href]'),callback='parse_items'),
)

#**
#* @restrict_css/restrict_xpaths must select a element but not a value,but if value is a complete url it's okey.Scrapy will find and encode it automatically
#**

 def parse_items(self, response):
name = response.css('div[class="hd"] h1::text').extract_first()
#print(self.settings.get('KEYS'))
for key in self.settings.get('KEYS'):
if not key in name:
print("Miss miss miss--------")
return {'url':None,'name':None}
#print(name)
url = response.url
loader = ItemLoader(item=JiaoyimaoItem(),response=response)
loader.add_value('name',name,TakeFirst(),re='(.*)')
loader.add_value('url',url)
yield loader.load_item()

img_loader = ItemLoader(item=JiaoyimaoItem(),response=response)
img_loader.add_css('image_urls','ul[class="slider-items"] li a::attr(data-url)')
#img_loader.add_value('name',None) Invalid method:Scrapy will ignore the key if it's value is None
#print(img_loader.load_item())
yield img_loader.load_item()
Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer