Item Loaders 对象加载器
Item Loaders 为当下流行的爬取 item 提供一个便捷的机制,也就是说,Items 提供抓取数据的容器,而 Item Loaders 提供了填充容器的机制。
Item Loaders 提供灵活的、高效的和简单的机制,用于扩展和重写不同域解析规则。
一、使用 Item Loaders 生成 items
在使用之前,首先要实例化它。实例化过程传入字典类的对象(Item或dict),或传入为空。传入为空会自动调用 Item 类定义的 ItemLoader.default_item_class
属性。
然后使用 Selectors 收集值到 Item Loader。对同一个 item 域,可以添加多个值;Item Loader 使用合适的处理方法合并这些值。
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()
解析上面代码,name 域可以从两个不同的 XPath 定位获取:
//div[@class="product_name"]
-
//div[@class="product_title"]
换句话说,name 域的数据从两个 XPath 路径定位。
后面 price 和 stock 分别以 add_xpath 和 add_css 方法添加定位。
最后直接用 value 值填充 last_upated 域,而使用 add_value()。
最后,当所有的数据都被收集,ItemLoader.load_item() 方法会被调用,并返回填充的数据。
二、出入和输出处理器
Item Loader 的每个域都包含一个输入处理器和一个输出处理器。
输入处理器
一旦通过 add_xpath() add_css() add_value() 方式接收数据,输入处理器便从中提取数据,输入处理器的结果保存在 ItemLoader 内。输出处理器
在收集所有数据后,ItemLoader.laod_item() 方法被调用填充数据,并获取已填充的 Item 对象;此时,调用输出处理器来处理预先收集的内容。
输出处理器的结果最终分配给 Item。
输入输出处理器剖析
用一段代码来解释在特定域中,输入和输出处理器是如何被调用的。
l = ItemLoader(Product(), some_selector)
l.add_xpath('name', xpath) # (1)
l.add_xpath('name', xpath2) # (2)
l.add_css('name', css) # (3)
l.add_value('name', 'test') # (4)
return l.load_item() # (5)
分步解析
- 从
xapth
中提取数据,然后通过输入处理器传给name
域。输入处理器的结果收集和保存在 Item Loader(目前位置还没有分配给 Item) - 从
xapth2
中提取数据,传输给步骤1中的同一个输入处理器,处理器的结果附加在1中。 - 这一步与前面的略有不同,它通过 css 选择器提取数据,然后在传输给1、2中的同一个输入处理器。处理的结果附加在1和2数据集中。
- 这一步是直接赋值给数据集,而不是通过 XPath表达式 或者 CSS选择器获取值。最后该值还是会被传输给输入处理器。(注意输入处理器值接收可迭代对象,如果赋值的内容不可迭代,自动将值转换成单个的可迭代元素)
- 最后一步,把1,2,3,4中的数据集传输给 name 域的输出处理器。输出处理器的结果赋与 item 中的 name 域。
需要注意的是,处理器只是一个可调用对象,在数据被解析的时候才调用,并返回解释的值。
自定义函数作为处理器
如果想自定义函数作为处理器,需要把 self
作为第一个参数。
def lowercase_processor(self, values):
for v in values:
yield v.lower()
class MyItemLoader(ItemLoader):
name_in = lowercase_processor
阅读 https://stackoverflow.com/a/35322635 查看更多
其他注意事项
输入处理器返回的是内部列表,并传给输出处理器用于填充对应的域。
更多 Scrapy 处理器通用方法
三、申明 Item Loaders
通过类定义语法来申明 Item Loaders,如下:
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirse, MapCompose, Join
class ProductLoader(ItemLoader):
default_output_process = TakeFirst()
name_in = MapCompose(unicode.title)
name_out = Join()
price_in = MapCompose(unicode.strip)
# ...
由上可见,输入处理器使用 _in
后置定义,输出处理器使用 _out
后置定义。
默认的输入/输出处理器:ItemLoader.default_input_processor
和 ItemLoader.default_output_processor
四、申明 输入输出处理器
在上面的介绍中,输入和输出处理器都可以在 Item Loader 中定义,这是一种非常常见的定义输入处理器的方式。
然而,还有更多的地方可以定义输入和输出处理器,比如在 Item 域的元数据中:
import scrapyfrom scrapy.loader.processors import Join, MapCompose, TakeFirstfrom 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(),
)
>>> from scrapy.loader import ItemLoader
>>> il = ItemLoader(item=Product())
>>> il.add_value('name', [u'Welcome to my', u'website'])
>>> il.add_value('price', [u'€', u'1000'])
>>> il.load_item(){'name': u'Welcome to my website', 'price': u'1000'}
题外:输入/输出处理器的优先级
- Item Loader 特定域的属性: field_in 和 field_out (最高优先级)
- Field 元数据(input_processor 和 output_processor 键)
- Item Loader 默认的属性:
ItemLoader.default_input_processor
和ItemLoader.default_output_processor
(优先级最低)
更多请看:重用和扩展 Item Loaders
五、Item Loader 上下文
Item Loader 是上下文是属性键值对形式的字典,在所有的输入和输出处理器中共享。 在申明、实例化或使用 Item Loader 均生效。利用上下文能修改输入输出处理器的内容。
比如我们需要parse_length
来接收文本,而提取文本的长度:
def parse_length(text, loader_context):
unit = loader_context.get('unit', 'm')
# 解析长度
return parse_length
通过接收 loader_context
参数,函数明确的告知 Item Loader 接收 Item Loader 上下文。
多种方式修改 Item Loader 上下文的值
- 通过修改 Item Loader 上下文(
context
属性)
loader = ItemLoader(product)
loader.context['unit'] = 'cm'
- 在 Item Loader 实例化过程(构造器的关键字参数)
loader = ItemLoader(product, unit='cm')
- 申明 Item Loader 的过程中,因为输入/输出处理器支持实例化携带 Item Loader 上下文内容,
MapCompose
为其中之一。
class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length, unit='cm')
六、ItemLoader 类代码分析
这里主要去看源码
七、内嵌 Loader
在解析文档子区域的关联值(即同一节点下的内容),使用内嵌 loader 非常有用。
假设你需要提取一个页脚如下:
如果不适用内嵌加载器,需要定义全 xpath 或 css,如下:
loader = ItemLoader(item=Item())
loader.add_xpath('social', '//footer/a[@class="social"]/@href')
loader.add_xpath('email', '//footer/a[@class="email"]/@href')
laoder.load_item()
而使用内嵌加载器,首先创建一个 footer 选择器脚本,然后再添加 footer 的相对路径:
loader = ItemLoader(item=Item())
footer_loader = loader.nexted_xpath('//footer')
footer_loader.add_xpath('social', 'a[@class="social"]/@href')
footer_loader.add_xapth('social', 'a[@class="email"]/@href')
loader.load_item()
注意:嵌套加载器是为了简化代码,不要使用太多嵌套导致代码繁杂难懂
八、重用和扩展 Item Loaders
当你的项目变得越来越大,包含了更多的 spider,如何维护项目要提到日程上来。尤其在你不得不处理各种解析规则,异常百出,此时需要提炼通用处理顺序,更需要提早做考虑。
Item Loader 提供简单且灵活的方式——继承。
- 举例,部分网站产品名称使用 --- 包裹(比如 ---鼠标---),只需要提取 鼠标,而不关注 --- 。
下面是如何移除 ---,并拓展默认的 Product 事物加载器(ProductLoader):
from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
def strip_dashes(x):
return x.strip('-')
class SiteSpecificLoader(ProductLoader):
name_in = MapCompose(strip_dashes, ProductLoader.name_in)
- 另一个例子,如果有多个格式化数据的操作,扩展 Item Loaders 非常有用
比如 XML 和 HTML,在 XML 版本中你需要移除CDATA
:
from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
from myproject.utils.xml import remove_cdata
class XmlProductLoader(ProductLoader):
name_in = MapCompose(remove_cdata, ProductLoader.name_in)
输出处理器的扩展
通常在域的元数据中声明扩展内容。更多内容在上文已经做过介绍。
九、可用的内置处理器 ❤
任何可调用函数都可以作为输入输出处理器,Scrapy 还是提供了通用的处理器:
- class scrapy.loader.processors.Identity
最简单的处理器,原样返回原始值 - class scrapy.loader.processors.TakeFirst
返回第一个非空值 - class scrapy.loader.processors.Join(separator=u'')
使用指定参数拼接值 - class scrapy.loader.processors.Compose(*functions, **default_loader_context)
4.1 组装指定函数,前一个函数作为处理器的输入,其返回值传递给下一个函数,以此类推。
4.2 有一个参数 stop_on_none
4.3 还可以接收 loader_context 参数,会把当前 Loader 的上下文传入 - class scrapy.loader.processors.MapCompose(*functions, **default_loader_context)
与上面的构造类似,不同之处在内部结果在函数之间的传递方式:Compose 和 MapCompose 从表达上来看其实很难区别不同之处,简单的说,前者 Compose 将整个参数作为进行处理;而 MapCompose 针对的是迭代每个内容进行处理。
5.1 如果函数返回的值为 None,则函数会忽略它
5.2 这种处理提供了只处理单个值的方式。因此,MapCompse 处理器用做输入处理器居多。 - clsss scrapy.loader.processors.SelectJms(json_path)
查询 json 结构的内容,依赖与 jmspath
翻译自官网
[1] https://docs.scrapy.org/en/latest/topics/loaders.html#scrapy.loader.ItemLoader.default_selector_class