Python Scrapy 爬蟲(chóng)教程之對(duì)象加載器 Item Loader

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 定位獲取:

  1. //div[@class="product_name"]
  2. //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)

分步解析

  1. xapth 中提取數(shù)據(jù)亚再,然后通過(guò)輸入處理器傳給 name 域郭膛。輸入處理器的結(jié)果收集和保存在 Item Loader(目前位置還沒(méi)有分配給 Item)
  2. xapth2 中提取數(shù)據(jù),傳輸給步驟1中的同一個(gè)輸入處理器氛悬,處理器的結(jié)果附加在1中则剃。
  3. 這一步與前面的略有不同,它通過(guò) css 選擇器提取數(shù)據(jù)如捅,然后在傳輸給1棍现、2中的同一個(gè)輸入處理器。處理的結(jié)果附加在1和2數(shù)據(jù)集中伪朽。
  4. 這一步是直接賦值給數(shù)據(jù)集轴咱,而不是通過(guò) XPath表達(dá)式 或者 CSS選擇器獲取值。最后該值還是會(huì)被傳輸給輸入處理器烈涮。(注意輸入處理器值接收可迭代對(duì)象,如果賦值的內(nèi)容不可迭代窖剑,自動(dòng)將值轉(zhuǎn)換成單個(gè)的可迭代元素)
  5. 最后一步坚洽,把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_processorItemLoader.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'&euro;', u'<span>1000</span>'])
>>> il.load_item(){'name': u'Welcome to my website', 'price': u'1000'}

題外:輸入/輸出處理器的優(yōu)先級(jí)

  1. Item Loader 特定域的屬性: field_in 和 field_out (最高優(yōu)先級(jí))
  2. Field 元數(shù)據(jù)(input_processor 和 output_processor 鍵)
  3. Item Loader 默認(rèn)的屬性: ItemLoader.default_input_processorItemLoader.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 上下文的值

  1. 通過(guò)修改 Item Loader 上下文(context屬性)
loader = ItemLoader(product)
loader.context['unit'] = 'cm'
  1. 在 Item Loader 實(shí)例化過(guò)程(構(gòu)造器的關(guān)鍵字參數(shù))
loader = ItemLoader(product, unit='cm')
  1. 申明 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 還是提供了通用的處理器:

  1. class scrapy.loader.processors.Identity
    最簡(jiǎn)單的處理器红柱,原樣返回原始值
  2. class scrapy.loader.processors.TakeFirst
    返回第一個(gè)非空值
  3. class scrapy.loader.processors.Join(separator=u'')
    使用指定參數(shù)拼接值
  4. 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 的上下文傳入
  5. 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 處理器用做輸入處理器居多。
  6. 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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纽什,隨后出現(xiàn)的幾起案子措嵌,更是在濱河造成了極大的恐慌,老刑警劉巖芦缰,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件企巢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡让蕾,警方通過(guò)查閱死者的電腦和手機(jī)浪规,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)探孝,“玉大人笋婿,你說(shuō)我怎么就攤上這事《俾” “怎么了缸濒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我庇配,道長(zhǎng)斩跌,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任捞慌,我火速辦了婚禮耀鸦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啸澡。我一直安慰自己袖订,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布锻霎。 她就那樣靜靜地躺著著角,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旋恼。 梳的紋絲不亂的頭發(fā)上吏口,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音冰更,去河邊找鬼产徊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蜀细,可吹牛的內(nèi)容都是我干的舟铜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼奠衔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谆刨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起归斤,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤痊夭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后脏里,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體她我,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年迫横,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了番舆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矾踱,死狀恐怖恨狈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情介返,我是刑警寧澤拴事,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布沃斤,位于F島的核電站,受9級(jí)特大地震影響刃宵,放射性物質(zhì)發(fā)生泄漏衡瓶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一牲证、第九天 我趴在偏房一處隱蔽的房頂上張望哮针。 院中可真熱鬧,春花似錦坦袍、人聲如沸十厢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛮放。三九已至,卻和暖如春奠宜,著一層夾襖步出監(jiān)牢的瞬間包颁,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工压真, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留娩嚼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓滴肿,卻偏偏與公主長(zhǎng)得像岳悟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泼差,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容