想必各位一定知道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()
|