Item Loaders 提供了一种便捷的方式填充抓取到的:Items
。虽然 Items
可以使用自带的类字典形式 API
填充,但是 Items Loaders
提供了更便捷的 API,可以分析原始数据并对 Item
进行赋值。
从另一方面来说,Items
提供保存抓取数据的容器,而 Item Loaders
提供的是填充容器
的机制。
Item Loaders
提供的是一种灵活,高效的机制,可以更方便的被 spider
或 source format
(HTML,XML,etc)扩展,并 override
更易于维护的、不同的内容分析规则。
1.使用itemLoaders填充item
要使用 Item Loader
, 你必须先将它实例化。你可以使用类似字典的对象(例如: Item or dict)来进行实例化,或者不使用对象也可以,当不用对象进行实例化的时候,Item 会自动使用 ItemLoader.default._item_class
属性中指定的 Item 类在 Item Loader constructor
中实例化。
然后,你开始收集数值到 Item Loader
时,通常使用 Selectors
。你可以在同一个 i
tem field 里面添加多个数值;
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"]
换言之,数据通过用 add_xpath()
的方法,把从两个不同的 XPath
位置提取的数据收集起来. 这是将在以后分配给 name
字段中的数据。
之后,类似的请求被用于 price
和 stock
字段 (后者使用 CSS selector
和 add_css()
方法), 最后使用不同的方法 add_value()
对 last_update
填充文本值(today)。
最终,当所有数据被收集起来之后, 调用 ItemLoader.load_item()
方法,实际上填充并且返回了之前通过调用add_xpath()
,add_css()
,and add_value()
所提取和收集到的数据的 Item
。
2.输入和输出处理器
Item Loader
在每个(Item)字段中都包含了一个输入处理器
和一个输出处理器
。 输入处理器收到数据时立刻提取数据 (通过 add_xpath()
,add_css()
或者 add_value()
方法)之后输入处理器的结果被收集起来并且保存在 ItemLoader
内. 收集到所有的数据后, 调用 ItemLoader.load_item()
方法来填充,并得到填充后的 Item
对象。这是当输出处理器被和之前收集到的数据(和用输入处理器处理的)被调用.输出处理器的结果是被分配到 Item
的最终值。
让我们看一个例子来说明如何输入和输出处理器被一个特定的字段调用(同样适用于其他 field):
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)
中使用的相同的 输入处理器。输入处理器的结果被附加到在(1)中收集的数据(如果有的话)。 - 3.和之前相似,只不过这里的数据是通过
css
CSS selector 抽取,之后传输到在(1)和(2)使用 的 输入处理器中。最终输入处理器的结果被附加到在(1)和(2)中收集的数据之后 (如果存在数据的话)。 - 4.这里的处理方式也和之前相似,但是此处的值是通过
add_value
直接赋予的, 而不是利用 XPath
表达式或 CSS selector
获取。得到的值仍然是被传送到输入处理器。 在这里例程中,因为得到的值并非可迭代,所以在传输到输入处理器之前需要将其 转化为可迭代的单个元素,这才是它所接受的形式。 - 5.在之前步骤中所收集到的数据被传送到 输出处理器 的
name field
中。输出处理器的结果就是赋到 item
中 name field
的值。
需要注意的是,输入和输出处理器都是可调用对象,调用时传入需要被分析的数据, 处理后返回分析得到的值。因此你可以使用任意函数作为输入、输出处理器。唯一需注意的是它们必须接收一个(并且只是一个)迭代器性质的 positional 参数
。
3.声明itemLoaders
通过使用类定义语法,像item
一样声明item loaders
。这是一个例子:
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)
# ...
如您所见,输入处理器使用_in
后缀声明,而输出处理器使用_out
后缀声明。您还可以使用ItemLoader.default_input_processor
和ItemLoader.default_output_processor
属性声明默认的输入/输出处理器。
4.声明输入输出处理器
如上一节所述,可以在Item Loader
定义中声明输入和输出处理器,并且以这种方式声明输入处理器非常普遍。但是,还有一个地方可以指定要使用的输入和输出处理器:item 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(),
)
>>> 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'}
输入和输出处理器的优先顺序如下:
- 1.
Item Loader
特定于字段的属性:field_in
和field_out
(最高优先级) - 2.字段元数据(
input_processor
和output_processor
键) - 3.
Item Loader
的默认设置:ItemLoader.default_input_processor()
和ItemLoader.default_output_processor()
(最低优先级)
5.Item Loader Context
Item Loader上下文是在Item Loader
中所有输入和输出处理器之间共享的任意key/values
的字典。可以在声明,实例化或使用Item Loader时传递。它们用于修改input/output
处理器的行为。
例如,假设您有一个parse_length
函数,该函数接收文本值并从中提取一个长度:
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
上下文,Item Loader
在调用它时传递当前活动的上下文,因此处理器函数(在这种情况下为parse_length)可以使用它们。
有几种方法可以修改Item Loader
上下文值:
1.通过修改当前活动的Item Loader
上下文(context
属性):
loader = ItemLoader(product)
loader.context['unit'] = 'cm'
2.在Item Loader
实例化上(Item Loader构造函数的关键字参数存储在Item Loader上下文中):
loader = ItemLoader(product, unit='cm')
3.在Item Loader
声明上,对于那些支持使用Item Loader上下文实例化它们的input/output
处理器。MapCompose
是其中之一:
class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length, unit='cm')
6.ItemLoader objects
classscrapy.loader.ItemLoader([item, selector, response, ]**kwargs
返回一个新的item loader
以填充给定的item。如果没有给出任何item
,则使用default_item_class
中的类自动实例化一个itme
当使用selector
或response
参数实例化时,ItemLoader
类提供了使用selectors
从网页提取数据的便捷机制。
Parameters
- item(Item object) – 使用后续对
add_xpath()
,add_css()
或 add_value()
的调用来填充的项目实例。 - selector (Selector object) - 当使用
add_xpath()
(resp.add_css()
)或replace_xpath()
(resp.replace_css()
)方法时从中提取数据的选择器。 - response(Response object) – 除非指定了选择器参数,否则使用
default_selector_class
构造选择器的响应,在这种情况下,将忽略该参数。
item
, selector
, response
和其余关键字参数都分配给Loader上下文(可通过context属性访问)。
ItemLoader实例具有以下方法:
方法 | 描述 |
---|
get_value(value, *processors, **kwargs) | 通过给定的处理器和关键字参数处理给定的值。 |
add_value(field_name, value, *processors, **kwargs) | 为给定字段添加给定值 |
replace_value(field_name, value, *processors, **kwargs) | 与add_value() 类似,但用新值替换收集的数据,而不是添加新值 |
get_xpath(xpath, *processors, **kwargs) | 与ItemLoader.get_value() 类似,但接收XPath 而不是值 ,该值用于从与此ItemLoader 关联的选择器中提取Unicode字符串列表。 |
add_xpath(field_name, xpath, *processors, **kwargs) | 与ItemLoader.add_value() 类似,但接收XPath 而不是值,该值用于从与此ItemLoader 关联的选择器中提取Unicode字符串列表。 |
replace_xpath(field_name, xpath, *processors, **kwargs) | 与add_xpath() 类似,但是替换收集的数据而不是添加数据。 |
get_css(css, *processors, **kwargs) | 与ItemLoader.get_value() 类似,但是接收CSS选择器 而不是值,该选择器用于从与此ItemLoader 关联的选择器中提取Unicode字符串列表 |
add_css(field_name, css, *processors, **kwargs) | 与ItemLoader.add_value() 类似,但接收CSS选择器 而不是值,该选择器用于从与此ItemLoader 关联的选择器中提取Unicode字符串列表。 |
replace_css(field_name, css, *processors, **kwargs) | 与add_css() 类似,但替换收集的数据而不是添加数据。 |
load_item() | 使用到目前为止收集的数据填充项目,然后将其返回。收集的数据首先通过输出处理器传递,以获取最终值以分配给每个项目字段。 |
nested_xpath(xpath) | 使用xpath选择器创建一个嵌套的加载器。提供的选择器相对于与此ItemLoader 关联的选择器应用。嵌套的加载器与父ItemLoader 共享该Item,因此对add_xpath() ,add_value() ,replace_value() 等的调用将按预期进行。 |
get_collected_values(field_name) | 返回给定字段的收集值 |
get_output_value(field_name) | 对于给定的字段,返回使用输出处理器解析的收集值。此方法完全不会填充或修改项目。 |
get_input_processor(field_name) | 返回给定字段的输入处理器 |
get_output_processor(field_name) | 返回给定字段的输出处理器 |
ItemLoader实例具有以下属性:
属性 | 描述 |
---|
item | 此Item Loader解析的Item对象 |
context | 此项目加载器的当前活动上下文 |
default_item_class | Item类(或工厂),用于在构造函数中未指定时实例化项目 |
default_input_processor | 用于那些未指定一个字段的默认输入处理器 |
default_output_processor | 用于未指定一个字段的默认输出处理器 |
default_selector_class | 如果构造函数中仅给出响应,则用于构造此ItemLoader的选择器的类。如果在构造函数中指定了选择器,则忽略此属性。有时在子类中覆盖此属性 |
selector | 从中提取数据的Selector对象。它既可以是构造函数中指定的选择器,也可以是使用default_selector_class 根据构造函数中指定的响应创建的选择器。此属性应为只读 |
7.嵌套加载器
Nested Loaders
从文档的子部分解析相关值时,创建嵌套的加载器可能很有用。假设您要从页面的页脚中提取详细信息,如下所示:
<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:[email protected]">Email Us</a>
</footer>
如果没有nested loaders
,则需要为要提取的每个值指定完整的xpath(或css)
loader = ItemLoader(item=Item())
# load stuff not in the footer
loader.add_xpath('social', '//footer/a[@class = "social"]/@href')
loader.add_xpath('email', '//footer/a[@class = "email"]/@href')
loader.load_item()
相反,您可以使用页脚选择器创建一个nested loaders
,并添加相对于页脚的值。功能相同,但避免重复页脚选择器。
loader = ItemLoader(item=Item())
# load stuff not in the footer
footer_loader = loader.nested_xpath('//footer')
footer_loader.add_xpath('social', 'a[@class = "social"]/@href')
footer_loader.add_xpath('email', 'a[@class = "email"]/@href')
# no need to call footer_loader.load_item()
loader.load_item()
您可以任意嵌套装载程序,它们可以与xpath或css选择器一起使用。作为一般准则,当嵌套加载器使您的代码更简单但又不会过多嵌套时,请使用嵌套加载器,否则解析器可能变得难以阅读。
8.覆盖和扩展Item Loaders
Reusing and extending Item Loaders
随着您的项目变得越来越大并获得越来越多的蜘蛛,维护成为一个基本问题,尤其是当您必须为每个蜘蛛处理许多不同的解析规则,有很多异常并且还想重用公共处理器时。
Item Loader
旨在减轻解析规则的维护负担,而又不失去灵活性,同时,提供了一种方便的机制来扩展和覆盖
它们。因此,Item Loader支持传统的Python类继承来处理特定蜘蛛(或蜘蛛组)的差异。
例如,假设某个特定的网站将其产品名称括在三个破折号中(例如— Plasma TV —),而您最终不想将这些破折号刮到最终产品名称中。
通过重新
使用和扩展默
认的Product Item Loader(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)
扩展项目加载器可能非常有用的另一种情况是,当您具有多种源格式时,例如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仅提供机制。它不会强加您的Loaders集合的任何特定组织-取决于您和您项目的需求
9.内置处理器
Available built-in processors
即使您可以使用任何可调用函数作为输入和输出处理器,Scrapy仍提供了一些常用的处理器,如下所述。其中一些函数,例如MapCompose
(通常用作输入处理器)组成了按顺序执行的多个函数的输出,以生成最终的解析值
class scrapy.loader.processors.Identity
最简单的处理器,什么都不做。它返回原始值不变。它不接收任何构造函数参数,也不接受Loader上下文。
>>> from scrapy.loader.processors import Identity
>>> proc = Identity()
>>> proc(['one', 'two', 'three'])
['one', 'two', 'three']
class scrapy.loader.processors.TakeFirst
从接收到的值中返回第一个非空/非空值,因此它通常用作单值字段的输出处理器。它不接收任何构造函数参数,也不接受Loader上下文。
>>> from scrapy.loader.processors import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'
class scrapy.loader.processors.Join(separator=u’’)
返回与构造函数中给定的分隔符相连的值,默认为u''
。它不接受Loader上下文。
使用默认分隔符时,此处理器等效于以下功能:u''.join
>>> from scrapy.loader.processors import Join
>>> proc = Join()
>>> proc(['one', 'two', 'three'])
'one two three'
>>> proc = Join('<br>')
>>> proc(['one', 'two', 'three'])
'one<br>two<br>three'
class scrapy.loader.processors.Compose(*functions, **default_loader_context)
根据给定功能的组成构造的处理器。这意味着该处理器的每个输入值将传递给第一个函数,该函数的结果将传递给第二个函数,依此类推,直到最后一个函数返回该处理器的输出值为止。
默认情况下,在None
值上停止进程。可以通过传递关键字参数stop_on_none = False
来更改此行为。
>>> from scrapy.loader.processors import Compose
>>> proc = Compose(lambda v: v[0], str.upper)
>>> proc(['hello', 'world'])
'HELLO'
每个函数可以选择接收loader_context
参数。对于那些这样做的处理器,此处理器将通过该参数传递当前活动的Loader上下文。
构造函数中传递的关键字参数用作传递给每个函数调用的默认Loader上下文值。但是,传递给函数的最终Loader上下文值将被可通过ItemLoader.context()
属性访问的当前活动Loader上下文覆盖。
class scrapy.loader.processors.MapCompose(*functions, **default_loader_context)
由给定功能的组成构造的处理器,类似于Compose
处理器。与该处理器的区别在于内部结果在函数之间传递的方式如下:
迭代此处理器的输入值,并将第一个功能应用于每个元素。这些函数调用的结果(每个元素一个)被串联以构造一个新的Iterable,然后将其应用于第二个函数,依此类推,直到最后一个函数应用于所收集的值列表中的每个值,这样远。最后一个函数的输出值连接在一起以产生此处理器的输出
每个特定的函数都可以返回一个值或一个值列表,该值或值列表将由应用到其他输入值的同一函数返回的值列表展平。这些函数还可以返回None
,在这种情况下,该函数的输出将被忽略,以进行链上的进一步处理。
该处理器提供了一种简便的方法来组合仅使用单个值(而不是可迭代值)的函数。由于这个原因,MapCompose
处理器通常用作输入处理器,因为数据通常是使用选择器的extract()
方法提取的,该方法返回一个Unicode
字符串列表
>>> def filter_world(x):
... return None if x == 'world' else x
...
>>> from scrapy.loader.processors import MapCompose
>>> proc = MapCompose(filter_world, str.upper)
>>> proc(['hello', 'world', 'this', 'is', 'scrapy'])
['HELLO, 'THIS', 'IS', 'SCRAPY']
使用提供给构造函数的json路径查询值并返回输出。需要jmespathhttps://github.com/jmespath/jmespath.py才能运行。该处理器一次仅接受一个输入。
>>> from scrapy.loader.processors import SelectJmes, Compose, MapCompose
>>> proc = SelectJmes("foo") #for direct use on lists and dictionaries
>>> proc({'foo': 'bar'})
'bar'
>>> proc({'foo': {'bar': 'baz'}})
{'bar': 'baz'}
使用Json:
>>> import json
>>> proc_single_json_str = Compose(json.loads, SelectJmes("foo"))
>>> proc_single_json_str('{"foo": "bar"}')
'bar'
>>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
>>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
['bar']