scrapy中的item_loader

Item Loaders 提供了一个便利的机制来帮助 populating(填充) scrapted Items;虽然,Items 可以通过它类似 dict API 来填充,Item Loaders 提供了更多便利的方法来进行 populates;
简而言之,Items 提供了被爬取数据的一个容器,而 Item Loaders 为该容器提供了 populating 的机制;
Item Loaders 的目的是为不同的 field 设计出更为高效、简单的可覆盖和可扩展的解析规则,可以用于不同的 spiders,以及不同的 source format (比如 HTML,XML 等等),而不至于是维护编程噩梦;
看一个例子

item_loader = MyItemLoader(item=ArticleSpiderItem(), response=response)
item_loader.add_xpath('title', "//div[@class='entry-header']/h1/text()")
item_loader.add_xpath('create_date', "//div[@class ='entry-meta']/p/text()")
item_loader.add_value('url', response.url)
item = item_loader.load_item()

        yield item

最后,当所有的数据都已经收集好了以后,将会通过 [ItemLoader.load_item()] 将其转换为 Item 并返回;

Input and Output processors

Item Loader 为每个 Item Field 单独提供了一个 Input processor 和一个 Output processor;Input processor 一旦它通过 add_xpath(),add_css(),add_value() 方法收到提取到的数据便会执行,执行以后所得到的数据将仍然保存在 ItemLoader 实例中;当数据收集完成以后,ItemLoader 通过 load_item() 方法来进行填充并返回以填充的 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)

看看相关的执行过程,
数据已经通过xpath1提取,然后该数据将会传递给该name属性所对应的 Input processor(在item字段中声明);Input processor 将会立即进行处理,但是处理的结果将仍然放置在 ItemLoader 中,并没有直接赋值给 Item)
数据已经通过xpath2提取,然后该数据将会传递给该 #1 的 Input processor (因为两者的属性都是name);Input processor 处理的结果将会追加到 #1 的 data collected 中 (当前的 ItemLoader 中的一个对象)
和 #1 和 #2 基本上类同,唯一的区别是,它使用的是 CSS 选择器,使用相同的 Input processor,并且将处理结果追加到相同的 data collected 中;
该步没有使用任何的选择器,而是直接通过文本的方式追加;虽然不需要提取,但是该操作仍然需要通过上述的 Input processor 进行处理;在这个用例中,因为该值 ‘test’ 是不能被遍历的(iterable),所以在传递给 Input processor 之前,虽然将其转换为 iterable 的对象,因为 Input processor 只接受 iterable;
将上面四个步骤所收集到的数据 data collected 传递给name属性所对应的 Output processor;经过 Output processor 所处理的结果将会赋值给 Item 的name属性;

声明 Item Loaders

Item Loaders 的声明方式和 Item 类同,使用 class definition syntax,看一个例子,
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)
    # ...

然后在spider parse方法中可导入ProductLoader类来代替ItemLoader进行实例化

item_loader = ProductLoader(item=Product(), response=response)

有用的内置 processors

Identity
class scrapy.loader.processors.Identity
最简单的一个 processor,并不做任何事情,它的功能只是将原有的值照原样输出;

>>> from scrapy.loader.processors import Identity
>>> proc = Identity()
>>> proc(['one', 'two', 'three'])
['one', 'two', 'three']

TakeFirst
class scrapy.loader.processors.TakeFirst
在一组数据当中返回第一个 not-null / not-empty 的数据,所以它的典型应用就是为一个 single-valued field 作为它的 Output processor;

>>> from scrapy.loader.processors import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'

Join(separator=u’ ‘)
class scrapy.loader.processors.Join(separator=u’ ‘)
通过由构造函数所传入的分隔符来联合需要输出的数据值,默认的分割符是u’ ‘;当使用默认的分隔符,这个 processor 等价于使用 function, u’ ‘.join;

>>> from scrapy.loader.processors import Join
>>> proc = Join()
>>> proc(['one', 'two', 'three'])
u'one two three'
>>> proc = Join('<br>')
>>> proc(['one', 'two', 'three'])
u'one<br>two<br>three'

Compose(functions, *default_loader_context)
class scrapy.loader.processors.Compose(*functions, **default_loader_context)
Compose processor 在上述例子中被普遍的用到,可想而知它的重要性了;该 processor 是由指定的一系列的 functions 所构成的,也就是说,第一个 functions 接收原始输入数据,然后将处理结果发送给下一个 function,下一个 function 将处理结果再发送至下一个 function,直到,最后一个 function 将处理结果输出;默认情况下,整个传递过程将会在遇到None值以后既停止;不过这个行为可以在构造 Compose 的时候通过关键字参数stop_on_none=False将其停止;
看一个简单的例子,

>>> from scrapy.loader.processors import Compose
>>> proc = Compose(lambda v: v[0], str.upper)
>>> proc(['hello', 'world'])
'HELLO'

很简单,通过Compose的构造函数传参了两个 Function,第一个方法是取队列中的第一个元素值,第二个方法是将该元素值变为大写;
另外,每一个 function 可以通过通过参数loader_context接收 Item Loader Context 实例;
在构造 Compose 的时候,通过构造函数所传递的关键字参数将会被当做默认的 Loader context values 并将其传递到每一个 function 的调用过程中;

MapCompose(functions, *default_loader_context)
class scrapy.loader.processors.MapCompose(*functions, **default_loader_context)
同Compose processor 类似,它也是由一组 functions 所构造而成;区别是,内部的结果将会在多个 functions 中传递,执行规则如下,
该 processor 的输入数据类型必须是 iterable (言外之意,输入值是一个队列 AA),第一个 function 将会作用到该队列中 AA 的每一个元素上;然后将每一个元素的输出值(如果有的话)再次进行级联(concatenated)并生成一个新的 iterable (记为队列 BB),然后队列 BB 中的每一个元素将会作用到第三个 function 上;然后将输出值再进行级联,并生成一个新的 iterable … 就这样,以此类推,直到调用最后一个 function 并得到相应的 iterable 作为结果返回;
如果调用过程中,针对某一个输入元素的 function 返回的是None,那么它的值将会被自动忽略,并不会成为下一次级联的输入元素;
正式因为每一次 function 调用都是作用到所有的输入元素上,所以,它经常被作为 Input processor 使用,因为数据经常是通过 SelectorList( selectors ) 的方法 extract() 进行提取的,因此,一个 MapCompose 便可以作用在 selectors 所返回的 values 上面,非常方便,看一个例子,

>>> def filter_world(x):
...     return None if x == 'world' else x
...
>>> from scrapy.loader.processors import MapCompose
>>> proc = MapCompose(filter_world, unicode.upper)
>>> proc([u'hello', u'world', u'this', u'is', u'scrapy'])
[u'HELLO', u'THIS', u'IS', u'SCRAPY']

简单的描述一下上面的这个例子,通过两个 functions 作为参数来通过构造函数构造一个MapCompose processor,第一个 function 当输入值是 ‘world’ 的时候,返回None,第二个 function 将返回结果进行大写转换;那么第一轮执行过程中,因为 ‘world’ 元素经过 filter_world() 调用有返回None所以被过滤掉了,第二轮将返回剩余三个元素的大写输出;

SelectJmes(json_path)
class scrapy.loader.processors.SelectJmes(json_path)

Queries the value using the json path provided to the constructor and returns the output. Requires jmespath (https://github.com/jmespath/jmespath.py) to run. This processor takes only one input at a time.
可以在 lists 或者 dict 上使用

>>> 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 string 上
>>> import json
>>> proc_single_json_str = Compose(json.loads, SelectJmes("foo"))
>>> proc_single_json_str('{"foo": "bar"}')
u'bar'
>>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
>>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
[u'bar']

注意,这里好玩的是第二个 Compose 构造器,第一个构造参数是一个普通的函数,但是注意第二个构造参数,是一个通过 SelectJmes() 作为构造参数所构造的MapCompose对象;那么我们来简单的分析一下它的执行流程,首先,输入的 json string 将会分别作用到这两个方法中,第一个是 json.loads 方法,将会把输入的 json string输入值拆分成两个键值元素的队列 LL,然后该队列将会作为第一个 function 的输出传递给 MapCompose 对象,因为 MapCompose 是一个 processor,所以将会按照该 processor 的特性对输入值进行处理,这里根据MapCompose的特性,会将队列 LL 里面的所有元素分别作用于方法 SelectJmes(‘foo’),所以,只有 {“foo”:”bar”} 元素与之匹配,因此输出的是 ‘bar’;
上面的例子中描述了一种特殊的 processor 调用情况,那就是 processor 中嵌入另外一个 processor,感觉这是一个比较重要的特性,可以针对这个特性单独再写出一个小节出来单独描述;
我的总结
Item Loader 的设计目的就是尽最大的可能性为所爬去的元素定义可扩展的规则,转换规则,并且通过重载 Item Loader 的方式能够最大限度的重用这些规则;使得当爬虫项目日益庞大以后,在运维上是可控的;
下一步要做的就是,写一个完整的例子,从前往后全部将其串通理解;

转载自: http://www.shangyang.me/2017/07/23/scrapy-learning-7-item-loaders/
scrapy讲的很好

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值