当前位置: 首页 > 工具软件 > Strip Loader > 使用案例 >

Python Scrapy 爬虫教程之对象加载器 Item Loader

邢晗日
2023-12-01

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 定位获取:

  1. //div[@class="product_name"]
  2. //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)

分步解析

  1. xapth 中提取数据,然后通过输入处理器传给 name 域。输入处理器的结果收集和保存在 Item Loader(目前位置还没有分配给 Item)
  2. xapth2 中提取数据,传输给步骤1中的同一个输入处理器,处理器的结果附加在1中。
  3. 这一步与前面的略有不同,它通过 css 选择器提取数据,然后在传输给1、2中的同一个输入处理器。处理的结果附加在1和2数据集中。
  4. 这一步是直接赋值给数据集,而不是通过 XPath表达式 或者 CSS选择器获取值。最后该值还是会被传输给输入处理器。(注意输入处理器值接收可迭代对象,如果赋值的内容不可迭代,自动将值转换成单个的可迭代元素)
  5. 最后一步,把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_processorItemLoader.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'&euro;', u'<span>1000</span>'])
>>> il.load_item(){'name': u'Welcome to my website', 'price': u'1000'}

题外:输入/输出处理器的优先级

  1. Item Loader 特定域的属性: field_in 和 field_out (最高优先级)
  2. Field 元数据(input_processor 和 output_processor 键)
  3. Item Loader 默认的属性: ItemLoader.default_input_processorItemLoader.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 上下文的值

  1. 通过修改 Item Loader 上下文(context属性)
loader = ItemLoader(product)
loader.context['unit'] = 'cm'
  1. 在 Item Loader 实例化过程(构造器的关键字参数)
loader = ItemLoader(product, unit='cm')
  1. 申明 Item Loader 的过程中,因为输入/输出处理器支持实例化携带 Item Loader 上下文内容,MapCompose 为其中之一。
class ProductLoader(ItemLoader):
    length_out = MapCompose(parse_length, unit='cm')

六、ItemLoader 类代码分析

这里主要去看源码

七、内嵌 Loader

在解析文档子区域的关联值(即同一节点下的内容),使用内嵌 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()

注意:嵌套加载器是为了简化代码,不要使用太多嵌套导致代码繁杂难懂

八、重用和扩展 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 还是提供了通用的处理器:

  1. class scrapy.loader.processors.Identity
    最简单的处理器,原样返回原始值
  2. class scrapy.loader.processors.TakeFirst
    返回第一个非空值
  3. class scrapy.loader.processors.Join(separator=u’’)
    使用指定参数拼接值
  4. class scrapy.loader.processors.Compose(*functions, **default_loader_context)
    4.1 组装指定函数,前一个函数作为处理器的输入,其返回值传递给下一个函数,以此类推。
    4.2 有一个参数 stop_on_none
    4.3 还可以接收 loader_context 参数,会把当前 Loader 的上下文传入
  5. class scrapy.loader.processors.MapCompose(*functions, **default_loader_context)
    与上面的构造类似,不同之处在内部结果在函数之间的传递方式:Compose 和 MapCompose 从表达上来看其实很难区别不同之处,简单的说,前者 Compose 将整个参数作为进行处理;而 MapCompose 针对的是迭代每个内容进行处理。
    5.1 如果函数返回的值为 None,则函数会忽略它
    5.2 这种处理提供了只处理单个值的方式。因此,MapCompse 处理器用做输入处理器居多。
  6. 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

 类似资料: