一.Items

抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据。Scrapy蜘蛛可以像Python一样返回提取的数据。虽然方便和熟悉,但Python缺乏结构:很容易在字段名称中输入拼写错误或返回不一致的数据,尤其是在具有许多蜘蛛的较大项目中。

为了定义通用输出数据格式,Scrapy提供了Item类。 Item对象是用于收集数据的简单容器。它们提供类似字典的 API,并具有用于声明其可用字段的方便语法。

1.定义Items

./items.py

import scrapy

class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
tags = scrapy.Field()
last_updated = scrapy.Field(serializer=str)

类似于django的model,scrapy定义items也是继承scrapy.Item类,然后设置需要的字段,但是Item没有像django那样有许多不同类型的Field

注意:Field用于声明项目的对象不会保留为类属性,所以不能用item.attr去访问,但可以通过Item.fields属性访问它们

>>> item.fields
{'last_updated': {'serializer': <class 'str'>}, 'name': {}, 'price': {}, 'stock': {}, 'tags': {}}

2.Field()类

Field对象指明了每个对象的元数据(metadata)。可以为每个字段指明任何类型的元数据。需要注意的是,用来声明item的Field对象并没有被赋值class的属性。不过可以通过Item.fields属性进行访问

查看Field()源码

class Field(dict):
"""Container of field metadata"""

所以实际上Field等同于dict,可以像last_updated那样在定义Items的时候,给字段一个元数据(这个元数据的作用主要是通过中间件时,给予特定的功能或处理,例如上面last_updated的序列化函数指定为str,可任意指定元数据,不过每种元数据对于不同的组件意义不一样)。并且只能Item.fields['serializer']这样访问,不能Item['last_updated']['serializer'],并且Item['last_updated']的赋值不影响元数据serializer值

总结就是:

1.Field对象指明了每个字段的元数据(任何元数据),Field对象接受的值没有任何限制

2.设置Field对象的主要目就是在一个地方定义好所有的元数据

3.声明item的Field对象,并没有被赋值成class属性。(可通过item.fields进行访问)

4.Field类仅是内置字典类(dict)的一个别名,并没有提供额外的方法和属性。被用来基于类属性的方法来支持item生命语法。

3.使用Items

创建对象

>>> product = Product(name='Desktop PC', price=1000)
>>> print(product)
Product(name='Desktop PC', price=1000)

从项目创建dicts

>>> dict(product) # create a dict from all populated values
{'price': 1000, 'name': 'Desktop PC'}

从dicts创建项目

>>> Product({'name': 'Laptop PC', 'price': 1500})
Product(price=1500, name='Laptop PC') >>> Product({'name': 'Laptop PC', 'lala': 1500}) # warning: unknown field in dict
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'

获取字段值

#想字典一样取值
>>> product['name']
Desktop PC
>>> product.get('name')
Desktop PC
>>> product['price']
1000
#没有设置的字段无法取值
>>> product['last_updated']
Traceback (most recent call last):
...
KeyError: 'last_updated'
#没有获取到值可以设置个默认返回值
>>> product.get('last_updated', 'not set')
not set
#不能获取没有定义的字段
>>> product['lala'] # getting unknown field
Traceback (most recent call last):
...
KeyError: 'lala' #判断key是否在item中
>>> 'name' in product # is name field populated?
True >>> 'last_updated' in product # is last_updated populated?
False >>> 'last_updated' in product.fields # is last_updated a declared field?
True >>> 'lala' in product.fields # is lala a declared field?
False

设置字段值

>>> product['last_updated'] = 'today'
>>> product['last_updated']
today >>> product['lala'] = 'test' # setting unknown field
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'

字典API

>>> product.keys()
['price', 'name'] >>> product.items()
[('price', 1000), ('name', 'Desktop PC')]

子类拓展

#可以通过声明原始Item的子类来扩展Items
class DiscountedProduct(Product):
discount_percent = scrapy.Field(serializer=str)
discount_expiration_date = scrapy.Field() #可以使用先前的字段元数据扩展字段元数据,并附加更多值或更改现有值
class SpecificProduct(Product):
name = scrapy.Field(Product.fields['name'], serializer=my_serializer)

二.Item Loaders

定义了Item,我们在爬虫的时候,可以使用字典的方式来填充我们抓取的数据(通过css xpath等方法)。同时scrap有也提供了一种方法,结合.css .xpath方法与item字典API来给Item输入数据,这就是item Loaders

从另一方面来说, Items 提供保存抓取数据的 容器 , 而 Item Loaders提供的是 填充 容器的机制。

from scrapy.loader import ItemLoader
from myproject.items import Product def parse(self, response):
l = ItemLoader(item=Product(), response=response)
l.add_xpath('name', '//div[@class="product_name"]')
l.add_xpath('name', '//div[@class="product_title"]')
l.add_xpath('price', '//p[@id="price"]')
l.add_css('stock', 'p#stock]')
l.add_value('last_updated', 'today') # you can also use literal values
return l.load_item()

这是官网上的示例,实际上ItemLoader就是item字典api与response.css() /.xpath()方法的集合,不过item Loaders更加简便与统一。

输入/输出处理器

每个Item Loader对每个Field都有一个输入处理器和一个输出处理器。输入处理器在数据被接受到时执行,当数据收集完后调用ItemLoader.load_item()时再执行输出处理器,返回最终结果。

l = ItemLoader(Product(), some_selector)
l.add_xpath('name', xpath1) # (1)
l.add_xpath('name', xpath2) # (2)
l.add_css('name', css) # (3)
l.add_value('name', 'test') # (4)
return l.load_item() # (5)

流程如下

1.xpath1中的数据被提取出来,然后传输到name字段的输入处理器中,在输入处理器处理完后生成结果放在Item Loader里面(这时候没有赋值给item)

2.xpath2数据被提取出来,然后传输给(1)中同样的输入处理器,因为它们都是name字段的处理器,然后处理结果被附加到(1)的结果后面

3.跟2一样

4.跟3一样,不过这次是直接的字面字符串值,先转换成一个单元素的可迭代对象再传给输入处理器

5.上面4步的数据被传输给name的输出处理器,将最终的结果赋值给name字段

注意:add_xpath(),add_css()或 add_value()方法不是输入处理器,而是提取数据传入到输入处理器的方法。如果没有定义输入处理器就是传入原值

定义输入输出处理器

1.自定义类:示例如下,通过对ItemLoader继承来重写Loader,其中name_in与name_out表示name字段的输入与输出处理器,default_input_processor和default_input_processor.则是指定默认的处理器(而在ItemLoader源码中实际就是使用Identity()处理器)

from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join class ProductLoader(ItemLoader): default_output_processor = TakeFirst() name_in = MapCompose(unicode.title)
name_out = Join() price_in = MapCompose(unicode.strip) # ...

处理器也可以使用自定义的函数,如果要将普通函数用作处理器,请确保将其self作为第一个参数接收

def lowercase_processor(self, values):
for v in values:
yield v.lower() class MyItemLoader(ItemLoader):
name_in = lowercase_processor

2.在Field定义中声明输入/输出处理器

import scrapy
from scrapy.loader.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags def filter_price(value):
if value.isdigit():
return value class Product(scrapy.Item):
name = scrapy.Field(
input_processor=MapCompose(remove_tags),
output_processor=Join(),
)
price = scrapy.Field(
input_processor=MapCompose(remove_tags, filter_price),
output_processor=TakeFirst(),
)

3.处理器的优先级

(1)在Item Loader中定义的field_in和field_out

(2)Filed元数据(input_processor和output_processor关键字)

(3)Item Loader中的默认的

4.内置的处理器

  • Identity() 最简单的处理器,它什么都不做。它返回原始值不变。它不接收任何构造函数参数,也不接受Loader上下文。
  • TakeFirst() 从接收的值返回第一个非null /非空值,因此它通常用作单值字段的输出处理器。它不接收任何构造函数参数,也不接受Loader上下文。
  • Join(separator = u' ' ) 将结果连起来,默认使用空格' ',等同于u' '.join
  • Compose(* functions** default_loader_context ) 将函数链接起来形成管道流,产生最后的输出
  • MapCompose(* functions** default_loader_context ) 跟上面的Compose类似,区别在于内部结果在函数中的传递方式.它的输入值是可迭代的,首先将第一个函数依次作用于所有值,产生新的可迭代输入,作为第二个函数的输入,最后生成的结果连起来返回最终值,一般用在输入处理器中
  • SelectJmes(json_path ) 使用json路径来查询值并返回结果

Item Loader上下文

Item Loader Context是任意键/值的dict,它在Item Loader中的所有输入和输出处理器之间共享。它可以在声明,实例化或使用Item Loader时传递。它们用于修改输入/输出处理器的行为。

以上官网说的不是很清楚,查看源码其实可以知道,context是Item Loader类的一个属性(字典形式),其记录传入Item Loader类任何数据,包括selector ,response ,item。可以通过loader.context访问,源码如下

class ItemLoader(object):

     ...

    def __init__(self, item=None, selector=None, response=None, parent=None, **context):
if selector is None and response is not None:
selector = self.default_selector_class(response)
self.selector = selector
#将selector与response传入context字典
context.update(selector=selector, response=response)
if item is None:
item = self.default_item_class()
self.context = context
self.parent = parent
#context添加item
self._local_item = context['item'] = item
self._local_values = defaultdict(list) ....

比如继续使用上面Product Item类来实验

>>> from scrapy.loader import ItemLoader
>>> l = ItemLoader(item=Product(),tmp='tmp')
>>> l
<scrapy.loader.ItemLoader object at 0x000000000388DD68>
>>> l.context
{'tmp': 'tmp', 'selector': None, 'response': None, 'item': {}}
>>> l.add_value('name','admin')
>>> l.add_value('price','')
>>> l.context
{'tmp': 'tmp', 'selector': None, 'response': None, 'item': {}}
>>> l.load_item()
{'name': ['admin'], 'price': ['']}
>>> l.context
{'tmp': 'tmp', 'selector': None, 'response': None, 'item': {'name': ['admin'], 'price': ['']}}

context的用途,比如一个接收文本值并从中提取长度的函数

def parse_length(text, loader_context):
unit = loader_context.get('unit', 'm')
# ... length parsing code goes here ...
return parsed_length

通过接收一个loader_context参数,这个函数告诉Item Loader它能够接收Item Loader context。于是当函数被调用的时候Item Loader传递当前活动的context给它。并且处理器函数(这里是parse_length)可以使用它们。

然后可以在定义处理器时使用

class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length)

这个相当于在给length字段赋值时,通过处理器返回的是赋值的长度。注意的是只有在定义函数时接收loader_context这个特定的参数才会接收context

参考官方文档https://docs.scrapy.org/en/latest/topics/loaders.html

Scrapy进阶知识点总结(三)——Items与Item Loaders的更多相关文章

  1. Scrapy进阶知识点总结(四)——Item Pipeline

    Item Pipeline Item Pipeline调用发生在Spider产生Item之后.当Spider解析完Response之后,Item就会传递到Item Pipeline,被定义的Item ...

  2. Scrapy进阶知识点总结(一)——基本命令与基本类(spider,request,response)

    一.常见命令 scrapy全局命令可以在任何地方用,项目命令只能在项目路径下用 全局命令: 项目命令: startproject crawl genspider check settings list ...

  3. Scrapy进阶知识点总结(六)——中间件详解

    概述 查看scrapy官网的框架图,可以看出中间件处于几大主要组件之间,类似于生产流水线上的加工过程,将原料按照不同需求与功能加工成成品 其中4,5处于下载器与引擎之间的就是下载中间件,而spider ...

  4. Scrapy进阶知识点总结(五)——Settings

    1.设置优先级 Scrapy中有不同层次的设置,其类型以及优先级如下(从高到低): 1.命令行命令中指定的设置 2.每个spider中的设置 3.scrapy项目中settings.py设置 4.命令 ...

  5. Scrapy进阶知识点总结(二)——选择器Selectors

    1. Selectors选择器 在抓取网页时,您需要执行的最常见任务是从HTML源提取数据.有几个库可用于实现此目的,例如: BeautifulSoup是Python程序员中非常流行的Web抓取库,它 ...

  6. scrapy学习笔记(三):使用item与pipeline保存数据

    scrapy下使用item才是正经方法.在item中定义需要保存的内容,然后在pipeline处理item,爬虫流程就成了这样: 抓取 --> 按item规则收集需要数据 -->使用pip ...

  7. 网页爬虫--scrapy进阶

    本篇将谈一些scrapy的进阶内容,帮助大家能更熟悉这个框架. 1. 站点选取 现在的大网站基本除了pc端都会有移动端,所以需要先确定爬哪个. 比如爬新浪微博,有以下几个选择: www.weibo.c ...

  8. Scrapy学习篇(三)之创建项目

    创建项目 创建项目是爬取内容的第一步,之前已经讲过,Scrapy通过scrapy startproject <project_name>命令来在当前目录下创建一个新的项目. 下面我们创建一 ...

  9. scrapy爬虫学习系列三:scrapy部署到scrapyhub上

    系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备:      http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...

随机推荐

  1. python编程基础之十五

    二维列表 l1 = [[1, 2, 3], [4, 5, 6]] print(l1[0][0]) 列表负值 列表复制为两种:深复制,浅复制 浅复制:只复制容器,容器里的元素不产生副本,只是技术引用增加 ...

  2. 将自定义功能添加到Spring Data Repository

    Spring Data非常方便,可以加快开发速度,避免使用样板代码. 但是,在某些情况下,注释查询不足,而无法达到您可能希望实现的自定义功能. 因此,Spring Data允许我们向Spring Da ...

  3. Uber Go 语言编程规范

    目录 Uber Go 语言编程规范 1. 介绍 2. 编程指南 3. 性能相关 4. 编程风格 5. 编程模式(Patterns) 6. 总结 Uber Go 语言编程规范 相信很多人前两天都看到 U ...

  4. Cobalt Strike之信息收集、木马钓鱼

    System Profiler使用 System Profiler 模块,搜集目标的各类机器信息(操作系统版本,浏览器版本等) Attacks->web drive-by->System ...

  5. 小白学 Python(6):基础运算符(下)

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  6. Vue中Class与Style如何动态绑定

    Class 可以通过对象语法和数组语法进行动态绑定: 对象语法: <div v-bind:class="{ active: isActive, 'text-danger': hasEr ...

  7. 简单理解TCP通信的三次握手

    TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接. 位码(可以理解为请求状态): 有6种标示:SYN(synchronous建立联机) ACK(acknowledg ...

  8. 为什么重写equals必须重写hoshCode的基础分析

    为什么重写equals必须重写hoshCode的基础分析 1.我们先来了解下原生的equals和hashCode代码 原生equals:它判断的是两个对象是否相等 原生hashCode值:它是根据内存 ...

  9. 基于深度学习方法的dota2游戏数据分析与胜率预测(python3.6+keras框架实现)

    很久以前就有想过使用深度学习模型来对dota2的对局数据进行建模分析,以便在英雄选择,出装方面有所指导,帮助自己提升天梯等级,但苦于找不到数据源,该计划搁置了很长时间.直到前些日子,看到社区有老哥提到 ...

  10. Ubuntu svn 安装 Rabbitvcs

    先添加源 sudo add-apt-repository ppa:rabbitvcs/ppa 必要的话在源清单里面也添加一下 sudo gedit /etc/apt/sources.list 内容是 ...