Scrapy框架(三)——Item
上一篇博客的例子中,提到了parse方法一般会返回一个request对象或item对象。那么这篇博客就记录下item,及处理item的管道pipelines。
引入
在抓取数据的过程中,主要要做的事就是从杂乱的数据中提取出结构化的数据。Scrapy
的Spider
可以把数据提取为一个Python中的字典,虽然字典使用起来非常方便,对我们来说也很熟悉,但是字典有一个缺点:缺少固定结构。在一个拥有许多爬虫的大项目中,字典非常容易造成字段名称上的语法错误,或者是返回不一致的数据。
在上一篇博客中,我们可以发现,保存在job.json文件的每条数据没有一致对应。如下:
{"job_label": "技术类", "num": "1", "time": "2019-03-26", "addr": "深圳", "job_name": "18796-数据分析师(深圳)"}
{"addr": "深圳", "num": "1", "job_name": "32032-互娱数据经营分析师(深圳)", "time": "2019-03-26", "job_label": "产品/项目类"}
所以Scrapy
中,定义了一个专门的通用数据结构:Item
。这个Item
对象提供了跟字典相似的API,并且有一个非常方便的语法来声明可用的字段。
Item
-
定义item
item对象结构的定义在项目录下的items.py文件中定义。以类的方式定义item对象的各个字段,这个类继承与scrapy.Item。如下所示:
class MyItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
job_name = scrapy.Field()
job_label = scrapy.Field()
job_time = scrapy.Field(serializer=str)
- 使用item
在spiders文件下,我们使用的parse方法将解析的数据存放于item对象中。首先实例化item,然后以字典的方式进行传值。
item = MyItem()
item['job_name'] = '语文老师'
item['job_label'] = '教师'
-
Field对象
可以看到,在声明Item
时,声明字段使用的是Field
对象。这个Field
对象其实完全继承自Python的字典,并且没有做任何改动,所以在使用Field
声明字段时,可以传入数据作为这个字段的元数据(metadata
),上方的serializer=str
其实就是一个指定序列化函数的元数据。
字段的元数据与字段的值之间没有必然的联系。如果我们直接查看Item
对象,那么获取的是字段的值;
In [3]: item
Out[3]: {'job_name': '语文老师', 'job_label':'教师'}
如果使用.fields
,获取的就是字段的元数据了:
In [3]: item
Out[3]: {'job_time':{}'job_name': {'serializer':'str'}, 'job_label':{}}
- 注意点
如果获取没有声名的字段或给其赋值,将会报错。但可以通过get方法给没有声名的字段设置一个默认值。
实例化item时,可以直接传递一个字典作为参数,但要注意传递字典的key值是已声明的,否则会报错。
定义的item,还可以给其他item所继承。
Item Pipeline
在Spider
中返回一个Item
后,这个Item
将会被发送给Item Pipeline
,每个Item Pipeline
都是一个Python类,实现了几个简单的方法。其主要有以下几种作用:
- 清洗数据
- 验证抓取下来的数据(检查是否含有某些字段)
- 检查去重
- 存储数据
几个简单的方法:
- process_item(self, item, spider)
每一个Item Pipeline
都会调用这个方法,用来处理Item,返回值为item或dict。
这个方法还可以抛出一个
DropItem异常,这样将会不再继续调用接下来的Item Pipeline
。
参数item(Item
对象或者Dict
) 是parse方法传来的。
参数spider(Spider
对象) - 抓取这个Item
的Spider
。
- open_spider(self, spider)
这个方法将会在Spider
打开时调用。
- close_spider(self, spider)
这个方法将会在Spider
关闭时调用。
启用pipeline:在setting文件中配置,demo是项目名。
ITEM_PIPELINES = {
'demo.pipelines.TanzhouPipeline': 300,
}
小例子
下面我们增加item、pipelines功能来实现腾讯python招聘信息的抓取。
class Job2_Tenxun(scrapy.Spider):
name = 'job2'
start_urls = ['https://hr.tencent.com/position.php?keywords=python',]
def parse(self, response):
item = MyItem()
res = response.xpath("//tr[@class='even']|//tr[@class='odd']")
for tr in res:
item['job_name'] = tr.xpath("./td[1]/a/text()").extract_first()
item['job_label'] = tr.xpath("./td[2]/text()").extract_first()
item['num'] = tr.xpath("./td[3]/text()").extract_first()
item['addr'] = tr.xpath("./td[4]/text()").extract_first()
item['time'] = tr.xpath("./td[5]/text()").extract_first()
yield item
next_url = response.xpath("//a[@id='next']/@href").extract_first()
yield scrapy.Request('https://hr.tencent.com/%s'%next_url)
class MyItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
job_name = scrapy.Field()
job_label = scrapy.Field()
num = scrapy.Field()
addr = scrapy.Field()
time = scrapy.Field()
import json
class MyPipeline(object):
def open_spider(self,spider):
self.f = open('job2.json','w',encoding='utf-8')
def process_item(self, item, spider):
self.f.write(json.dumps(dict(item),ensure_ascii=False))
self.f.write('\n')
return item
def close_spider(self,spider):
self.f.close()
这里要注意的是,item对象不能直接转成json数据,故需要先转成dict。