Itemloader提供了一种机制,可以很方便的填充item
首先需要初始化Itemloader,可以用字典或是item作为构造函数的参数,如果没有指定,Itemloader会自己自动初始化一个item(对应属性ItemLoader.default_item_class),下面是一个使用例子(使用之前构造好的Product类):
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()
最后,需要使用load_item()方法返回item
Itemloader对于item的每一个Field都包含有一个输入进程和一个输出进程,当爬虫提取到数据后(通过前面提到的add_xpath()等方法),会调用输入进程。输入进程处理的结果将被保存(此时并未保存到item中),当调用load_item函数时,将会通过输出进程获得之前保存的填充的值,然后由load_item函数内部的逻辑进行item的填充,返回item,这里说的进程(包括下面说的)只是一个可调用模块,因此我们可以使用函数,但是函数必须接收一个迭代器
可以定义在itemloader的定义中:
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属性表示默认的输入与输出进程,在定义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(),
)
由于定义输入与输出进程的方式有许多种,这些方式对应的优先级如下:
1、itemloader子类定义的_in和_out属性
2、Field方法声明的输入与输出进程
3、itemloader默认的输入输出进程
Item Loader的上下文是一个字典,可在输入与输出进程之间共享任意的键值对,相当于信息的共享区域,用于修改输入输出处理器的行为(这个东西文档本身解释也不是很清楚)
当我们的自定函数接收一个名为loader_context的参数时,Scrapy会自动将目前使用的上下文传递给loader_context
有三种方式可以修正item_loader上下文的值:
1、使用itemloader的context属性:
loader = ItemLoader(product)
loader.context['unit'] = 'cm'
2、初始化itemloader时:
loader = ItemLoader(product, unit='cm')
3、itemloader子类定义输入输出进程时:
class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length, unit='cm')
一、__init__:初始化一个itemloader对象,可选参数:
item:需要填充的item,selector(selector对象):选择器,当使用add_xpath()、add_css()、add_value()时,使用selector提取数据,response(response对象):若没有指定selector,response用于构建选择器,相当于选择器作用的对象,若没有指定selector,将使用default_selector_class
二、get_value
(value, *processors, **kwargs)
value通过processors和kwargs的处理后返回value,例子:
>>> from scrapy.loader.processors import TakeFirst
>>> loader.get_value(u'name: foo', TakeFirst(), unicode.upper, re='name: (.+)')
'FOO`
三、add_value(field_name,value,*processors,**kwargs)
将value通过*processors和**kwargs的处理后,添加到field_name区域,如果field_name区域早就存在了对应数据,新的数据将会覆盖旧的数据,field_name可以是None,但是value必须是字典,例子:
loader.add_value('name', u'Color TV')
loader.add_value('colours', [u'white', u'blue'])
loader.add_value('length', u'100')
loader.add_value('name', u'name: foo', TakeFirst(), re='name: (.+)')
loader.add_value(None, {'name': u'foo', 'sex': u'male'})
四、replace_value(field_name,value,*processors,**kwargs)
和add_value()类似,但是是替换已经存储的数据
五、get_xpath(xpath,*processors,**kwargs)
传递给selector属性,然后使用这个spath提取数据,经过*processors和**kwargs的处理后返回,例子:
# HTML snippet: <p class="product-name">Color TV</p>
loader.get_xpath('//p[@class="product-name"]')
# HTML snippet: <p id="price">the price is $1200</p>
loader.get_xpath('//p[@id="price"]', TakeFirst(), re='the price is (.*)')
六、add_xpath(xpath,*processors,**kwargs)
和add_value()类似,不过是使用xpath提取数据后在处理保存,例子:
# HTML snippet: <p class="product-name">Color TV</p>
loader.add_xpath('name', '//p[@class="product-name"]')
# HTML snippet: <p id="price">the price is $1200</p>
loader.add_xpath('price', '//p[@id="price"]', re='the price is (.*)')
七、replace_xpath(field_name,xpath,*processors,**kwargs)
和add_xpath()类似,但是会替换field_name的值
八、get_css(css,*processors,**kwargs)
和get_value类似,但是接收的是css选择器,例子:
# HTML snippet: <p class="product-name">Color TV</p>
loader.get_css('p.product-name')
# HTML snippet: <p id="price">the price is $1200</p>
loader.get_css('p#price', TakeFirst(), re='the price is (.*)')
九、add_css(field_name,css,*processors,**kwargs)
使用css选择器去提取数据,通过processors和kwargs的处理后,存储到field_name
# HTML snippet: <p class="product-name">Color TV</p>
loader.add_css('name', 'p.product-name')
# HTML snippet: <p id="price">the price is $1200</p>
loader.add_css('price', 'p#price', re='the price is (.*)')
十、replace_css(field_name,css,*processors,**kwargs)
和add_css()类似,但是是替换而不是添加
十一、load_item()
用之前存储的数据(通过输出进程返回)填充item
十二、nested_xpath(xpath)
根据xpath选择器创建Nested Loaders类的实例,该实例与调用者ItemLoader的item共用
十三、nested_css(css)
根据css选择器创建Nested Loaders类的实例,该实例与调用者ItemLoader的item共用
十四、get_collected_values(field_name)
返回field_name对应的存储区存储的值
十五、get_output_value(field_name)
将field_name对应的存储区域存储的值经过输出进程处理后返回
十六、get_input_processor(field_name)
返回field_name对应的输入进程
十七、get_output_processor(field_name)
返回field_name对应的输出进程
Itemloader有如下属性:
1、item,2、context
3、default_item_class:当没有给定item时,使用这个属性实例化一个item
4、default_input_processor:默认的输入进程
5、default_output_processor:默认的输出进程
6、default_selector_class:当没有指定选择器时,使用这个属性构建选择器
7、selecor:用于提取数据的选择器
当提取的数据集中在html文档的一部分时,为了防止代码重复太多,使用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:whatever@example.com">Email Us</a>
</footer>
我们想提取上述例子的a元素的href属性的值
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()
可以看到有一部分代码重复了,如果footer在html文档树的较深处,为了提取a元素的href,将会有大量重复的xpath需要书写,这个时候可以使用nested— loader:
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()
Itemloader允许被继承并覆盖其中的某些属性和方法
scrapy.loader.processors.Identity:最简单的进程,不做任何事,只是简单的返回接收到的值,构造它不需要任何参数
>>> from scrapy.loader.processors import Identity
>>> proc = Identity()
>>> proc(['one', 'two', 'three'])
['one', 'two', 'three']
scrapy.loader.processors.TakeFirst:返回迭代器中第一个不为null,不为空的值,构造它不需要任何参数
>>> from scrapy.loader.processors import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'
scrapy.loader.processors.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'
scrapy.loader.processors.Compose(*functions,**default_loader_context):一系列函数组成的处理器,第一个函数处理的结果会交给第二个函数处理。直到最后一个函数处理完,默认情况下,处理器处理到None时会停止,将stop_on_none置为False可以防止这种情况出现,default_loader_context用做上下文,由itemloader.context()给定,用于多个函数之间信息的共享
>>> from scrapy.loader.processors import Compose
>>> proc = Compose(lambda v: v[0], str.upper)
>>> proc(['hello', 'world'])
'HELLO'
scrapy.loader.processors.MapCompose(*functions,**default_loader_context):和Compose类似,但是内部处理方式不一样,对于输入的迭代器,第一个函数应用于所有迭代器的值,所有值经过处理后,组织成新的迭代器,交给第二个函数,依次进行下去,每个函数可以返回一个值,也可以返回一个列表,也可以返回None,这也意味着函数处理链后续不会处理对应的值:
>>> 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']
MapCompose常用做输入进程,default_loader_context用做上下文,由itemloader.context()给定,用于多个函数之间信息的共享
scrapy.loader.processors.SelectJmes(json_path):从给定的json路径开始查询,返回对应的值,需要运行jmespath,该处理器一次只需要一个输入:
>>> 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'}
上述例子将从foo开始查询,获取其中的值
使用json模块:
>>> 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']