scrapy中的item其实设计上使用的是面向对象的模式,将爬取的数据字段组合封装,进一步交由管道处理存储,这里对其机制做相关讲解。
1. Items
爬虫的主要任务就是从非结构化的数据中获得结构化的数据。
Item 对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like) 的API以及用于声明可用字段的简单语法。
声明Item
Item使用简单的class定义语法以及 Field 对象来声明。例如:
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
Item字段:
Field 对象指明了每个字段的元数据(metadata)。例如上面例子中 last_updated 中指明了该字段的序列化函数。
可以为每个字段指明任何类型的元数据。Field 对象对接受的值没有任何限制。Field 对象中保存的每个键可以由多个组件使用,并且只有这些组件知道这个键的存在。设置 Field 对象的主要目的就是在一个地方定义好所有的元数据。
需要注意的是,用来声明item的 Field 对象并没有被赋值为class的属性。 不过您可以通过 Item.fields 属性进行访问。
2. 用Item Loader来填充Item
Item Loaders提供了一种便捷的方式填充抓取到的 Items 。 虽然Items可以使用自带的类字典形式API填充,但是Items Loaders提供了更便捷的API, 可以分析原始数据并对Item进行赋值。
从另一方面来说, Items 提供保存抓取数据的 容器 , 而 Item Loaders提供的是 填充 容器的机制。
Item Loaders提供的是一种灵活,高效的机制,可以更方便的被spider或source format (HTML, XML, etc)扩展,并override更易于维护的、不同的内容分析规则。
要使用Item Loader, 你必须先将它实例化. 可以使用类似字典的对象来进行实例化, 或者不使用对象也可以, 当不用对象进行实例化的时候,Item会自动使用 ItemLoader.default_item_class 属性中指定的Item 类在Item Loader constructor中实例化.
然后,你开始收集数值到Item Loader时,通常使用 Selectors. 你可以在同一个item field 里面添加多个数值;Item Loader将知道如何用合适的处理函数来“添加”这些数值.
下面是在 Spider 中典型的Item Loader的用法, 使用 Items chapter 中声明的 Product item:
from scrapy.contrib.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.
3. 输入处理器与输出处理器
- Item Loader在每个字段中都包含了一个输入处理器和一个输出处理器。
- 输入处理器收到数据时立刻提取数据 (通过 add_xpath(), add_css() 或者 add_value() 方法) 之后输入处理器的结果被收集起来并且保存在ItemLoader内(但尚未分配给该Item).
- 收集到所有的数据后, 调用 ItemLoader.load_item() 方法来填充,并得到填充后的 Item 对象。在这一步中先调用输出处理器来处理之前收集到的数据,然后再存入Item中。输出处理器的结果是被分配到Item的最终值。
需要注意的是,输入和输出处理器都是可调用对象,调用时传入需要被分析的数据, 处理后返回分析得到的值。因此你可以使用任意函数作为输入、输出处理器。 唯一需注意的是它们必须接收一个(并且只是一个)迭代器性质的positional参数。
4. 声明Items Loaders
Item Loaders 的声明类似于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)
# ...
input processors 以_in为后缀来声明,output processors 以_out 为后缀来声明。也可以用ItemLoader.default_input_processor 和ItemLoader.default_output_processor 属性来声明默认的 input/output processors。
5. 声明Input and Output Processors
前面讲到,input and output processors可以在定义Item Loaders的时候声明,这是非常普遍的使用方法。但是,你也可以在定义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(),
)
使用Item:
>>> from scrapy.contrib.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'}
关于集中声明 input and output processors方式的优先级排序如下:
- 在Item Loader 中声明的 field-specific 属性: field_in and field_out (most precedence)
- Item中的字段元数据(input_processor and output_processor key)
- Item Loader 默认处理器: ItemLoader.default_input_processor() and ItemLoader.default_output_processor() (least precedence)
6. 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的值:
- 修改当前 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’)
7. ItemLoader object
参见官方文档
8. 重用和扩展Item Loaders
当你的项目逐渐变大,使用了越来越多的spider的时候,维护变成了一个基本的问题。尤其是当你需要处理每个spider的许多不同的解析规则的时候,会出现很多的异常,迫使你开始考虑重用的问题。
Item Loader支持传统的Python继承机制来处理spider之间的差异。
例如,有些网站把它们的产Product名用三个短线封装起来(如:---Plasma TV---),而你想要去掉这些东西。
你可以通过reusing and extending默认Product Item Loader的方式去掉短线:
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)
另一种情形时继承Item Loader也很有用:有多种格式的源数据(如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)
这便是扩展输入处理器的方法。
- 对于输出处理器,更常用的方式是在Item字段元数据里声明。因为通常它们依赖于具体的字段而不是网站。
- 还有很多其他方式开扩展、继承和覆盖Item Loader,不同的层次结构适于不同的项目。Scrapy只是提供了这些机制,不强制要求具体的组织方式。
9. 内置的处理器
尽管你可以使用可调用的函数作为输入输出处理器,Scrapy提供了一些常用的处理器。有些处理器,如MapCompose(通常用于输入处理器),能把多个函数执行的结果按顺序组合起来产生最终的输出。
下面是一些内置的处理器:
9.1 Identity
class scrapy.loader.processors.Identity
最简单的处理器,不进行任何处理,直接返回原来的数据。无参数。
9.2 TakeFirst
class scrapy.loader.processors.TakeFirst
返回第一个非空(non-null/non-empty)值,常用于单值字段的输出处理器。无参数。
示例如下:
>>> from scrapy.loader.processors import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'
9.3 Join
class scrapy.loader.processors.Join(separator=u’ ‘)
返回用分隔符连接后的值。分隔符默认为空格。不接受Loader contexts。
当使用默认分隔符的时候,这个处理器等同于这个函数:
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'
9.4 Compose
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参数。
9.5 MapCompose
class scrapy.loader.processors.MapCompose(functions, *default_loader_context)
与Compose处理器类似,区别在于各个函数结果在内部传递的方式:
- 输入值是被迭代的处理的,每一个元素被单独传入第一个函数进行处理。处理的结果被l连接起来(concatenate)形成一个新的迭代器,并被传入第二个函数,以此类推,直到最后一个函数。最后一个函数的输出被连接起来形成处理器的输出。
- 每个函数能返回一个值或者一个值列表,也能返回None(会被下一个函数所忽略)
- 这个处理器提供了方便的方式来组合多个处理单值的函数。因此它常用与输入处理器,因为用extract()函数提取出来的值是一个unicode strings列表。
下面的例子能说明这个处理器的工作方式:
>>> 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。
9.6 SelectJmes
class scrapy.loader.processors.SelectJmes(json_path)
查询指定的JSON path并返回输出。需要jmespath(https://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"}')
u'bar'
>>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
>>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
[u'bar']
版权属于:Jolly
本文链接:https://totoro.site/index.php/archives/12/
关于转载:原创文章,禁止转载