Python爬虫笔记(九)——Scrapy官方文档阅读——Itemloader

 

什么是itemloader

Itemloader提供了一种机制,可以很方便的填充item

使用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的上下文

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')

ItemLoader对象的API

一、__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:用于提取数据的选择器

Nested Loaders

当提取的数据集中在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:[email protected]">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自定义的可使用的输入输出进程

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']

猜你喜欢

转载自blog.csdn.net/dhaiuda/article/details/81501350