Item Loaders 为当下流行的爬取 item 提供一个便捷的机制,也就是说,Items 提供抓取数据的容器,而 Item Loaders 提供了填充容器的机制。
Item Loaders 提供灵活的、高效的和简单的机制,用于扩展和重写不同域解析规则。
在使用之前,首先要实例化它。实例化过程传入字典类的对象(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"]
最后,当所有的数据都被收集,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中。需要注意的是,处理器只是一个可调用对象,在数据被解析的时候才调用,并返回解释的值。
如果想自定义函数作为处理器,需要把 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,如下:
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'<strong>website</strong>'])
>>> il.add_value('price', [u'€', u'<span>1000</span>'])
>>> il.load_item(){'name': u'Welcome to my website', 'price': u'1000'}
ItemLoader.default_input_processor
和 ItemLoader.default_output_processor
(优先级最低)更多请看:重用和扩展 Item Loaders
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 上下文。
context
属性)loader = ItemLoader(product)
loader.context['unit'] = 'cm'
loader = ItemLoader(product, unit='cm')
MapCompose
为其中之一。class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length, unit='cm')
这里主要去看源码
在解析文档子区域的关联值(即同一节点下的内容),使用内嵌 loader 非常有用。
假设你需要提取一个页脚如下:
<footer>
<a class="social" href="https://facebook.com/whatever">Like Us</a>
<a class="social" href="https://twitter.com/whatever">Follow Us</a>
<a class="email" href="mailto:whatever@example.com">Eamil Us</a>
</footer>
如果不适用内嵌加载器,需要定义全 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()
注意:嵌套加载器是为了简化代码,不要使用太多嵌套导致代码繁杂难懂
当你的项目变得越来越大,包含了更多的 spider,如何维护项目要提到日程上来。尤其在你不得不处理各种解析规则,异常百出,此时需要提炼通用处理顺序,更需要提早做考虑。
Item Loader 提供简单且灵活的方式——继承。
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)
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 还是提供了通用的处理器:
翻译自官网
[1] https://docs.scrapy.org/en/latest/topics/loaders.html#scrapy.loader.ItemLoader.default_selector_class