13.2 Item Loader
在讲Item Loader之前,首先回顾一下Item的用法。Item对象是种简单的容器,保存了爬取到的数据,提供了类似于词典的API以及用于声明可用字段的简单语法。Item使用简单的class定义语法以及Field对象来声明,例如定义一个Product类:
import scrapy class Product(scrapy.Item): name = scrapy.Field() price = scrapy.Field() stock = scrapy.Field() last_updated = scrapy.Field(serializer=str)
以下都以Product类为例,讲解Item与Item Loaders的关系。
13.2.1 Item与Item Loader
Item Loader提供了一种便捷的方式填充抓取到的Items。虽然Items可以使用自带的类字典形式API填充,但是Items Loader提供了更便捷的API,可以分析原始数据并对Item进行赋值。换句话说,Items提供保存抓取数据的容器,而Item Loader提供的是填充容器的机制。
Item Loader提供的是一种灵活、高效的机制,可以更方便地被spider或HTML、XML等文件扩展,重写不同字段的解析规则,这对大型的爬虫项目的后期维护非常有利,拓展新的功能更加方便。下面是Item Loader在Spider中的典型应用方式,代码如下:
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') return l.load_item()
我们可以发现name字段被从页面中两个不同的XPath位置提取:
l.add_xpath('name', '// div[@class="product_name"]') l.add_xpath('name', '// div[@class="product_title"]')
也就是说使用add_xpath方法,将数据从两处XPath位置上收集起来,之后会分配给name字段。
类似操作被应用于price、stock和last_updated,pirce的字段数据通过XPath方式收集,stock字段数据通过CSS选择器方式收集,而last_updated字段数据直接被填充字符串today。当所有的数据被收集起来后,使用l.load_item()将数据实际填充到Item中。
13.2.2 输入与输出处理器
从上面的分析可以看到,Item Loader负责了数据的收集、处理和填充,Item仅仅承载了数据本身而已。数据的收集、处理和填充,归功于Item Loader中两个重要的组件:输入处理器(input processors)和输出处理器(output processors)。下面说一下Item Loaders这两个处理器的职能:
·首先Item Loader在每个字段中都包含了一个输入处理器和一个输出处理器
·输入处理器收到response后时立刻通过add_xpath()、add_css()或者add_value()等方法提取数据,经过输入处理器的结果被收集起来并且保存在ItemLoader内,这个时候数据还没有给Item。
·收集到所有的数据后,调用ItemLoader.load_item()方法来填充并返回Item对象。load_item()方法内部先调用输出处理器来处理收集到的数据,处理后的结果最终存入Item中。
说完了输入和输出处理器的职能,下面声明一个Item Loader。Item Loader的声明类似于Items,以class的语法来声明,代码如下:
from scrapy.contrib.loader import ItemLoader from scrapy.contrib.loader.processor 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属性来声明默认的输入/输出处理器。
除了可以在ItemLoader类中声明输入输出处理器,也可以在Item中声明。示例如下:
import scrapy from scrapy.contrib.loader.processor import Join, MapCompose, TakeFirst from w3lib.html import remove_tags def filter_price(value): if value.isdigit(): return value class ProductItem(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(), )
以上说了三种输入输出处理器的声明方式:
·ItemLoader类中声明类似field_in和field_out的属性。
·Item的字段中声明。
·Item Loader默认处理器:ItemLoader.default_input_processor()和ItemLoader.default_output_processor()。
这种三种方式的响应优先级是从上到下依次递减。
13.2.3 Item Loader Context
Item Loader Context是一个任意的键值对字典,能被Item Loader中的输入输出处理器所共享。它在Item Loader声明、实例化、使用的时候传入,可以调整输入输出处理器的行为。举个例子,假设有个parse_length方法用于接收text值并且获取其长度:
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 context,因此当这个方法被调用时,Item Loader能将当前的active Context传递给它。
有以下几种方式可以修改Item Loader Context的值:
·通过context属性修改当前active Item Loader Context:
loader = ItemLoader(product) loader.context[‘unit’] = ‘cm’
·在Item Loader实例化的时候:
loader = ItemLoader(product, unit=’cm’)
·对于那些支持Item Loader Context实例化的输入输出处理器(例如MapCompose),可以在Item Loader定义时修改Context:
class ProductLoader(ItemLoader): length_out = MapCompose(parse_length, unit=’cm’)
13.2.4 重用和扩展Item Loader
当爬虫项目越来越大,使用的Spider越来越多时,项目的维护将成为一个要紧的问题。特别是维护每一个Spider中许多而且不同的解析规则时,会出现很多异常,这个时候需要考虑重用和拓展的问题了。
Item Loader本身的设计就是为了减轻维护解析规则的负担,而且提供了方便的接口,用来重写和拓展他们。Item Loader支持通过传统Python类继承的方式来处理不同Spider解析的差异。
比如你之前写了一个ProductLoader来提取和解析某家公司网站的产品名称Plasma TV,但是一段时间之后公司网站更新,产品用三个短线封装起来,如---Plasma TV---。现在的需求是去掉这些短线,提取其中的产品名。示例代码如下:
def strip_dashes(x): return x.strip('-') class SiteSpecificLoader(ProductLoader): name_in = MapCompose(strip_dashes, ProductLoader.name_in)
通过继承ProductLoader类,通过strip_dashes方法将name中的短线去掉,这便是拓展输入处理器的方法。
对于输出处理器,更常用的方式是在Item字段元数据里声明,因为通常它们依赖于具体的字段而不是网站,这个可以参考13.2.2节在Item的字段中声明输入和输出处理器。
13.2.5 内置的处理器
除了可以使用可调用的函数作为输入输出处理器,Scrapy提供了一些常用的处理器。例如MapCompose,通常用于输入处理器,能把多个方法执行的结果按顺序组合起来产生最终的输出。
下面是一些内置的处理器。
1.Identity
类原型:class scrapy.loader.processors.Identity
最简单的处理器,不进行任何处理,直接返回原来的数据,无参数。
2.TakeFirst
类原型:class scrapy.loader.processors.TakeFirst
返回第一个非空值,常用于单值字段的输出处理器,无参数。
在Scrapy shell运行示例如下:
>>> from scrapy.loader.processors import TakeFirst >>> proc = TakeFirst() >>> proc(['', 'one', 'two', 'three']) 'one'
3.Join
类原型:class scrapy.loader.processors.Join(separator=u’‘)
返回用分隔符separator连接后的值,分隔符separator默认为空格。不接受Loader contexts。当使用默认分隔符的时候,这个处理器等同于Python字符串对象中的join方法:’‘.join。
在Scrapy shell运行示例如下:
>>> 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'
4.Compose
类原型:class scrapy.loader.processors.Compose(*functions,**default_loader_context)
用给定的多个方法的组合来构造处理器,每个输入值被传递到第一个方法,然后其输出再传递到第二个方法,诸如此类,直到最后一个方法返回整个处理器的输出。默认情况下,当遇到None值的时候停止处理,可以通过传递参数stop_on_none=False改变这种设定。
在Scrapy shell运行示例如下:
>>> from scrapy.loader.processors import Compose >>> proc = Compose(lambda v: v[0], str.upper) >>> proc(['hello', 'world']) 'HELLO'
每个方法可以选择接收一个loader_context参数。
5.MapCompose
类原型:class scrapy.loader.processors.MapCompose(*functions,**default_loader_context)
和Compose类似,也是用给定的多个方法的组合来构造处理器,不同的是内部结果在方法间传递的方式:
·处理器的输入值是被迭代处理的,每一个元素被单独传入第一个函数进行处理,处理的结果被串连起来形成一个新的迭代器,并被传入第二个函数,以此类推,直到最后一个函数。最后一个函数的输出被连接起来形成处理器的输出。
·每个函数能返回一个值或者一个值列表,也能返回None。如果返回值是None,此值会被下一个函数所忽略。
·这个处理器提供了方便的方式来组合多个处理单值的函数。因此它常用于输入处理器,因为通过selectors中的extract()函数提取出来的值是一个unicode strings列表。
在Scrapy shell运行示例如下:
>>> 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']
与Compose处理器类似,它也能接受Loader context。
6.SelectJmes
类原型:class scrapy.loader.processors.SelectJmes(json_path)
使用指定的json_path查询并返回值。需要jmespath的支持,而且每次只接受一个输入。jmespath:https://github.com/jmespath/jmespath.py 。
在Scrapy shell运行示例如下:
>>> 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"}') u'bar' >>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo'))) >>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]') [u'bar']
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论