Item Loaders 對(duì)象加載器
Item Loaders 為當(dāng)下流行的爬取 item 提供一個(gè)便捷的機(jī)制,也就是說(shuō)炫惩,Items 提供抓取數(shù)據(jù)的容器僻弹,而 Item Loaders 提供了填充容器的機(jī)制。
Item Loaders 提供靈活的他嚷、高效的和簡(jiǎn)單的機(jī)制蹋绽,用于擴(kuò)展和重寫(xiě)不同域解析規(guī)則。
一筋蓖、使用 Item Loaders 生成 items
在使用之前卸耘,首先要實(shí)例化它。實(shí)例化過(guò)程傳入字典類(lèi)的對(duì)象(Item或dict)粘咖,或傳入為空蚣抗。傳入為空會(huì)自動(dòng)調(diào)用 Item 類(lèi)定義的 ItemLoader.default_item_class
屬性。
然后使用 Selectors 收集值到 Item Loader瓮下。對(duì)同一個(gè) item 域翰铡,可以添加多個(gè)值;Item Loader 使用合適的處理方法合并這些值讽坏。
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()
解析上面代碼锭魔,name 域可以從兩個(gè)不同的 XPath 定位獲取:
//div[@class="product_name"]
-
//div[@class="product_title"]
換句話(huà)說(shuō)路呜,name 域的數(shù)據(jù)從兩個(gè) XPath 路徑定位迷捧。
后面 price 和 stock 分別以 add_xpath 和 add_css 方法添加定位。
最后直接用 value 值填充 last_upated 域拣宰,而使用 add_value()党涕。
最后,當(dāng)所有的數(shù)據(jù)都被收集巡社,ItemLoader.load_item() 方法會(huì)被調(diào)用膛堤,并返回填充的數(shù)據(jù)。
二晌该、出入和輸出處理器
Item Loader 的每個(gè)域都包含一個(gè)輸入處理器和一個(gè)輸出處理器肥荔。
輸入處理器
一旦通過(guò) add_xpath() add_css() add_value() 方式接收數(shù)據(jù),輸入處理器便從中提取數(shù)據(jù)朝群,輸入處理器的結(jié)果保存在 ItemLoader 內(nèi)燕耿。輸出處理器
在收集所有數(shù)據(jù)后,ItemLoader.laod_item() 方法被調(diào)用填充數(shù)據(jù)姜胖,并獲取已填充的 Item 對(duì)象誉帅;此時(shí),調(diào)用輸出處理器來(lái)處理預(yù)先收集的內(nèi)容。
輸出處理器的結(jié)果最終分配給 Item蚜锨。
輸入輸出處理器剖析
用一段代碼來(lái)解釋在特定域中档插,輸入和輸出處理器是如何被調(diào)用的。
l = ItemLoader(Product(), some_selector)
l.add_xpath('name', xpath) # (1)
l.add_xpath('name', xpath2) # (2)
l.add_css('name', css) # (3)
l.add_value('name', 'test') # (4)
return l.load_item() # (5)
分步解析
- 從
xapth
中提取數(shù)據(jù)亚再,然后通過(guò)輸入處理器傳給name
域郭膛。輸入處理器的結(jié)果收集和保存在 Item Loader(目前位置還沒(méi)有分配給 Item) - 從
xapth2
中提取數(shù)據(jù),傳輸給步驟1中的同一個(gè)輸入處理器氛悬,處理器的結(jié)果附加在1中则剃。 - 這一步與前面的略有不同,它通過(guò) css 選擇器提取數(shù)據(jù)如捅,然后在傳輸給1棍现、2中的同一個(gè)輸入處理器。處理的結(jié)果附加在1和2數(shù)據(jù)集中伪朽。
- 這一步是直接賦值給數(shù)據(jù)集轴咱,而不是通過(guò) XPath表達(dá)式 或者 CSS選擇器獲取值。最后該值還是會(huì)被傳輸給輸入處理器烈涮。(注意輸入處理器值接收可迭代對(duì)象,如果賦值的內(nèi)容不可迭代窖剑,自動(dòng)將值轉(zhuǎn)換成單個(gè)的可迭代元素)
- 最后一步坚洽,把1,2西土,3讶舰,4中的數(shù)據(jù)集傳輸給 name 域的輸出處理器。輸出處理器的結(jié)果賦與 item 中的 name 域需了。
需要注意的是跳昼,處理器只是一個(gè)可調(diào)用對(duì)象,在數(shù)據(jù)被解析的時(shí)候才調(diào)用肋乍,并返回解釋的值鹅颊。
自定義函數(shù)作為處理器
如果想自定義函數(shù)作為處理器,需要把 self
作為第一個(gè)參數(shù)墓造。
def lowercase_processor(self, values):
for v in values:
yield v.lower()
class MyItemLoader(ItemLoader):
name_in = lowercase_processor
閱讀 https://stackoverflow.com/a/35322635 查看更多
其他注意事項(xiàng)
輸入處理器返回的是內(nèi)部列表堪伍,并傳給輸出處理器用于填充對(duì)應(yīng)的域。
更多 Scrapy 處理器通用方法
三觅闽、申明 Item Loaders
通過(guò)類(lèi)定義語(yǔ)法來(lái)申明 Item Loaders帝雇,如下:
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirse, MapCompose, Join
class ProductLoader(ItemLoader):
default_output_process = TakeFirst()
name_in = MapCompose(unicode.title)
name_out = Join()
price_in = MapCompose(unicode.strip)
# ...
由上可見(jiàn),輸入處理器使用 _in
后置定義蛉拙,輸出處理器使用 _out
后置定義尸闸。
默認(rèn)的輸入/輸出處理器:ItemLoader.default_input_processor
和 ItemLoader.default_output_processor
四、申明 輸入輸出處理器
在上面的介紹中,輸入和輸出處理器都可以在 Item Loader 中定義吮廉,這是一種非常常見(jiàn)的定義輸入處理器的方式睹栖。
然而,還有更多的地方可以定義輸入和輸出處理器茧痕,比如在 Item 域的元數(shù)據(jù)中:
import scrapyfrom scrapy.loader.processors import Join, MapCompose, TakeFirstfrom 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(),
)
>>> from scrapy.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'}
題外:輸入/輸出處理器的優(yōu)先級(jí)
- Item Loader 特定域的屬性: field_in 和 field_out (最高優(yōu)先級(jí))
- Field 元數(shù)據(jù)(input_processor 和 output_processor 鍵)
- Item Loader 默認(rèn)的屬性:
ItemLoader.default_input_processor
和ItemLoader.default_output_processor
(優(yōu)先級(jí)最低)
更多請(qǐng)看:重用和擴(kuò)展 Item Loaders
五野来、Item Loader 上下文
Item Loader 是上下文是屬性鍵值對(duì)形式的字典,在所有的輸入和輸出處理器中共享踪旷。 在申明曼氛、實(shí)例化或使用 Item Loader 均生效。利用上下文能修改輸入輸出處理器的內(nèi)容令野。
比如我們需要parse_length
來(lái)接收文本舀患,而提取文本的長(zhǎng)度:
def parse_length(text, loader_context):
unit = loader_context.get('unit', 'm')
# 解析長(zhǎng)度
return parse_length
通過(guò)接收 loader_context
參數(shù),函數(shù)明確的告知 Item Loader 接收 Item Loader 上下文气破。
多種方式修改 Item Loader 上下文的值
- 通過(guò)修改 Item Loader 上下文(
context
屬性)
loader = ItemLoader(product)
loader.context['unit'] = 'cm'
- 在 Item Loader 實(shí)例化過(guò)程(構(gòu)造器的關(guān)鍵字參數(shù))
loader = ItemLoader(product, unit='cm')
- 申明 Item Loader 的過(guò)程中聊浅,因?yàn)檩斎?輸出處理器支持實(shí)例化攜帶 Item Loader 上下文內(nèi)容,
MapCompose
為其中之一现使。
class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length, unit='cm')
六低匙、ItemLoader 類(lèi)代碼分析
這里主要去看源碼
七、內(nèi)嵌 Loader
在解析文檔子區(qū)域的關(guān)聯(lián)值(即同一節(jié)點(diǎn)下的內(nèi)容)碳锈,使用內(nèi)嵌 loader 非常有用顽冶。
假設(shè)你需要提取一個(gè)頁(yè)腳如下:
<footer>
<a class="social" >Like Us</a>
<a class="social" >Follow Us</a>
<a class="email" href="mailto:whatever@example.com">Eamil Us</a>
</footer>
如果不適用內(nèi)嵌加載器,需要定義全 xpath 或 css售碳,如下:
loader = ItemLoader(item=Item())
loader.add_xpath('social', '//footer/a[@class="social"]/@href')
loader.add_xpath('email', '//footer/a[@class="email"]/@href')
laoder.load_item()
而使用內(nèi)嵌加載器强重,首先創(chuàng)建一個(gè) footer 選擇器腳本,然后再添加 footer 的相對(duì)路徑:
loader = ItemLoader(item=Item())
footer_loader = loader.nexted_xpath('//footer')
footer_loader.add_xpath('social', 'a[@class="social"]/@href')
footer_loader.add_xapth('social', 'a[@class="email"]/@href')
loader.load_item()
注意:嵌套加載器是為了簡(jiǎn)化代碼贸人,不要使用太多嵌套導(dǎo)致代碼繁雜難懂
八间景、重用和擴(kuò)展 Item Loaders
當(dāng)你的項(xiàng)目變得越來(lái)越大,包含了更多的 spider艺智,如何維護(hù)項(xiàng)目要提到日程上來(lái)倘要。尤其在你不得不處理各種解析規(guī)則,異常百出力惯,此時(shí)需要提煉通用處理順序碗誉,更需要提早做考慮。
Item Loader 提供簡(jiǎn)單且靈活的方式——繼承父晶。
- 舉例哮缺,部分網(wǎng)站產(chǎn)品名稱(chēng)使用 --- 包裹(比如 ---鼠標(biāo)---),只需要提取 鼠標(biāo)甲喝,而不關(guān)注 --- 尝苇。
下面是如何移除 ---,并拓展默認(rèn)的 Product 事物加載器(ProductLoader):
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)
- 另一個(gè)例子,如果有多個(gè)格式化數(shù)據(jù)的操作糠溜,擴(kuò)展 Item Loaders 非常有用
比如 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)
輸出處理器的擴(kuò)展
通常在域的元數(shù)據(jù)中聲明擴(kuò)展內(nèi)容。更多內(nèi)容在上文已經(jīng)做過(guò)介紹非竿。
九蜕着、可用的內(nèi)置處理器 ?
任何可調(diào)用函數(shù)都可以作為輸入輸出處理器,Scrapy 還是提供了通用的處理器:
- class scrapy.loader.processors.Identity
最簡(jiǎn)單的處理器红柱,原樣返回原始值 - class scrapy.loader.processors.TakeFirst
返回第一個(gè)非空值 - class scrapy.loader.processors.Join(separator=u'')
使用指定參數(shù)拼接值 - class scrapy.loader.processors.Compose(*functions, **default_loader_context)
4.1 組裝指定函數(shù)承匣,前一個(gè)函數(shù)作為處理器的輸入,其返回值傳遞給下一個(gè)函數(shù)锤悄,以此類(lèi)推韧骗。
4.2 有一個(gè)參數(shù) stop_on_none
4.3 還可以接收 loader_context 參數(shù),會(huì)把當(dāng)前 Loader 的上下文傳入 - class scrapy.loader.processors.MapCompose(*functions, **default_loader_context)
與上面的構(gòu)造類(lèi)似零聚,不同之處在內(nèi)部結(jié)果在函數(shù)之間的傳遞方式:Compose 和 MapCompose 從表達(dá)上來(lái)看其實(shí)很難區(qū)別不同之處袍暴,簡(jiǎn)單的說(shuō),前者 Compose 將整個(gè)參數(shù)作為進(jìn)行處理隶症;而 MapCompose 針對(duì)的是迭代每個(gè)內(nèi)容進(jìn)行處理政模。
5.1 如果函數(shù)返回的值為 None,則函數(shù)會(huì)忽略它
5.2 這種處理提供了只處理單個(gè)值的方式沿腰。因此览徒,MapCompse 處理器用做輸入處理器居多。 - clsss scrapy.loader.processors.SelectJms(json_path)
查詢(xún) json 結(jié)構(gòu)的內(nèi)容颂龙,依賴(lài)與 jmspath
翻譯自官網(wǎng)
[1] https://docs.scrapy.org/en/latest/topics/loaders.html#scrapy.loader.ItemLoader.default_selector_class