Scrapy爬蟲入門教程七 Item Loaders(項目加載器)

Python版本管理:pyenv和pyenv-virtualenv
Scrapy爬蟲入門教程一 安裝和基本使用
Scrapy爬蟲入門教程二 官方提供Demo
Scrapy爬蟲入門教程三 命令行工具介紹和示例
Scrapy爬蟲入門教程四 Spider(爬蟲)
Scrapy爬蟲入門教程五 Selectors(選擇器)
Scrapy爬蟲入門教程六 Items(項目)
Scrapy爬蟲入門教程七 Item Loaders(項目加載器)
Scrapy爬蟲入門教程八 交互式 shell 方便調試
Scrapy爬蟲入門教程九 Item Pipeline(項目管道)
Scrapy爬蟲入門教程十 Feed exports(導出文件)
Scrapy爬蟲入門教程十一 Request和Response(請求和響應)
Scrapy爬蟲入門教程十二 Link Extractors(鏈接提取器)

開發環境:
Python 3.6.0 版本 (當前最新)
Scrapy 1.3.2 版本 (當前最新)

項目加載器

項目加載器提供了一種方便的機制來填充抓取的項目。即使可以使用自己的類似字典的API填充項目,項目加載器提供了一個更方便的API,通過自動化一些常見的任務,如解析原始提取的數據,然后分配它從剪貼過程中填充他們。

換句話說,Items提供了抓取數據的容器,而Item Loader提供了填充該容器的機制。

項目加載器旨在提供一種靈活,高效和容易的機制,通過爬蟲或源格式(HTML,XML等)擴展和覆蓋不同的字段解析規則,而不會成為維護的噩夢。

使用裝載機項目來填充的項目

要使用項目加載器,您必須首先實例化它。您可以使用類似dict的對象(例如Item或dict)實例化它,也可以不使用它,在這種情況下,項目將在Item Loader構造函數中使用屬性中指定的Item類自動ItemLoader.default_item_class 實例化。

然后,您開始收集值到項裝載程序,通常使用選擇器。您可以向同一項目字段添加多個值; 項目加載器將知道如何使用適當的處理函數“加入”這些值。

這里是Spider中典型的Item Loader用法,使用Items部分中聲明的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()

通過快速查看該代碼,我們可以看到該name字段正從頁面中兩個不同的XPath位置提取:

  1. //div[@class="product_name"]
  2. //div[@class="product_title"]
    換句話說,通過使用add_xpath()方法從兩個XPath位置提取數據來收集數據。這是稍后將分配給name字段的數據。

之后,類似的調用用于pricestock字段(后者使用帶有add_css()方法的CSS選擇器),最后使用不同的方法last_update直接使用文字值(today)填充字段add_value()。

最后,收集的所有數據時,該ItemLoader.load_item()方法被稱為實際上返回填充先前提取并與收集到的數據的項目add_xpath()add_css()add_value()調用。

輸入和輸出處理器

項目加載器對于每個(項目)字段包含一個輸入處理器和一個輸出處理器。輸入處理器只要它的接收處理所提取的數據(通過add_xpath(),add_css()或 add_value()方法)和輸入處理器的結果被收集并保持ItemLoader內部。收集所有數據后,ItemLoader.load_item()調用該 方法來填充和獲取填充 Item對象。這是當輸出處理器使用先前收集的數據(并使用輸入處理器處理)調用時。輸出處理器的結果是分配給項目的最終值。

讓我們看一個例子來說明如何為特定字段調用輸入和輸出處理器(同樣適用于任何其他字段):

l = ItemLoader(Product(), some_selector)
l.add_xpath('name', xpath1) # (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. 從數據xpath1提取出來,并通過所傳遞的輸入處理器的的name字段。輸入處理器的結果被收集并保存在項目加載器中(但尚未分配給項目)。
  2. 從中xpath2提取數據,并通過(1)中使用的同一輸入處理器。輸入處理器的結果附加到(1)中收集的數據(如果有)。
  3. 這種情況類似于先前的情況,除了數據從cssCSS選擇器提取,并且通過在(1)和(2)中使用的相同的輸入處理器。輸入處理器的結果附加到在(1)和(2)中收集的數據(如果有的話)。
  4. 這種情況也與之前的類似,除了要收集的值直接分配,而不是從XPath表達式或CSS選擇器中提取。但是,該值仍然通過輸入處理器。在這種情況下,由于該值不可迭代,因此在將其傳遞給輸入處理器之前,它將轉換為單個元素的可迭代,因為輸入處理器總是接收迭代。
  5. 在步驟(1),(2),(3)和(4)中收集的數據通過name字段的輸出處理器。輸出處理器的結果是分配給name 項目中字段的值。

值得注意的是,處理器只是可調用對象,它們使用要解析的數據調用,并返回解析的值。所以你可以使用任何功能作為輸入或輸出處理器。唯一的要求是它們必須接受一個(也只有一個)位置參數,這將是一個迭代器。

注意

輸入和輸出處理器都必須接收一個迭代器作為它們的第一個參數。這些函數的輸出可以是任何東西。輸入處理器的結果將附加到包含收集的值(對于該字段)的內部列表(在加載程序中)。輸出處理器的結果是最終分配給項目的值。

另一件需要記住的事情是,輸入處理器返回的值在內部(在列表中)收集,然后傳遞到輸出處理器以填充字段。

最后,但并非最不重要的是,Scrapy自帶一些常用的處理器內置的方便。

聲明項目加載器

項目加載器通過使用類定義語法聲明為Items。這里是一個例子:

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 Loader定義中聲明,這種方式聲明輸入處理器是很常見的。但是,還有一個地方可以指定要使用的輸入和輸出處理器:在項目字段 元數據中。這里是一個例子:

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(),
    )
>>> 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'}

輸入和輸出處理器的優先級順序如下:

  1. 項目加載程序字段特定屬性:field_in和field_out(最高優先級)
  2. 字段元數據(input_processor和output_processor鍵)
  3. 項目加載器默認值:ItemLoader.default_input_processor()和 ItemLoader.default_output_processor()(最低優先級)

參見:重用和擴展項目加載器

項目加載器上下文

項目加載器上下文是在項目加載器中的所有輸入和輸出處理器之間共享的任意鍵/值的dict。它可以在聲明,實例化或使用Item Loader時傳遞。它們用于修改輸入/輸出處理器的行為。

例如,假設您有一個parse_length接收文本值并從中提取長度的函數:

def  parse_length (text , loader_context ):
    unit  =  loader_context 。get ('unit' , 'm' )
    #...長度解析代碼在這里... 
    return  parsed_length

通過接受一個loader_context參數,該函數顯式地告訴Item Loader它能夠接收一個Item Loader上下文,因此Item Loader在調用它時傳遞當前活動的上下文,因此處理器功能(parse_length在這種情況下)可以使用它們。

有幾種方法可以修改Item Loader上下文值:

  1. 通過修改當前活動的Item Loader上下文(context屬性):

    loader = ItemLoader(product)
    loader.context['unit'] = 'cm'
    
  2. On Item Loader實例化(Item Loader構造函數的關鍵字參數存儲在Item Loader上下文中):

    loader = ItemLoader(product, unit='cm')
    
  3. On Item Loader聲明,對于那些支持使用Item Loader上下文實例化的輸入/輸出處理器。MapCompose是其中之一:

    class ProductLoader(ItemLoader):
        length_out = MapCompose(parse_length, unit='cm')
    

ItemLoader對象

class scrapy.loader.ItemLoader([item, selector, response, ]**kwargs)

返回一個新的Item Loader來填充給定的Item。如果沒有給出項目,則使用中的類自動實例化 default_item_class。

當使用選擇器或響應參數實例化時,ItemLoader類提供了使用選擇器從網頁提取數據的方便的機制。

參數:

  • item(Item對象)-項目實例來填充用以后調用 add_xpath(),add_css()或add_value()。
  • selector(Selectorobject) - 當使用add_xpath()(或。add_css())或replace_xpath() (或replace_css())方法時,從中提取數據的選擇器 。
  • response(Responseobject) - 用于使用構造選擇器的響應 default_selector_class,除非給出選擇器參數,在這種情況下,將忽略此參數。
    項目,選擇器,響應和剩余的關鍵字參數被分配給Loader上下文(可通過context屬性訪問)。

ItemLoader 實例有以下方法:

get_value(value,* processors,** kwargs )
處理給定value的給定processors和關鍵字參數。

可用的關鍵字參數:

參數: re(str 或compiled regex)
一個正則表達式extract_regex(),用于使用方法從給定值提取數據,在處理器之前應用
例子:

>>> 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定字段的給定。

該值首先通過get_value()賦予 processors和kwargs,然后通過 字段輸入處理器及其結果追加到為該字段收集的數據。如果字段已包含收集的數據,則會添加新數據。

給定field_name可以是None,在這種情況下可以添加多個字段的值。并且已處理的值應為一個字段,其中field_name映射到值。

例子:

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 )
類似于ItemLoader.get_value()但接收XPath而不是值,用于從與此相關聯的選擇器提取unicode字符串的列表ItemLoader。

參數:

  • xpath(str) - 從中??提取數據的XPath
  • re(str 或compiled regex) - 用于從所選XPath區域提取數據的正則表達式
    例子:
# 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(field_name,xpath,* processor,** kwargs )
類似于ItemLoader.add_value()但接收XPath而不是值,用于從與此相關聯的選擇器提取unicode字符串的列表ItemLoader。

見get_xpath()的kwargs。

參數:
xpath(str) - 從中??提取數據的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,* processor,** kwargs )
類似于add_xpath()但替換收集的數據,而不是添加它。

get_css(css,* processors,** kwargs )
類似于ItemLoader.get_value()但接收一個CSS選擇器而不是一個值,用于從與此相關的選擇器提取一個unicode字符串列表ItemLoader。

參數:

  • css(str) - 從中??提取數據的CSS選擇器
  • re(str 或compiled regex) - 用于從所選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 )
類似于ItemLoader.add_value()但接收一個CSS選擇器而不是一個值,用于從與此相關的選擇器提取一個unicode字符串列表ItemLoader。

見get_css()的kwargs。

參數:
css(str) - 從中??提取數據的CSS選擇器
例子:

# 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()
使用目前收集的數據填充項目,并返回。收集的數據首先通過輸出處理器,以獲得要分配給每個項目字段的最終值。

nested_xpath(xpath )
使用xpath選擇器創建嵌套加載器。所提供的選擇器應用于與此相關的選擇器ItemLoader。嵌套裝載機股份Item 與母公司ItemLoader這么調用add_xpath(), add_value(),replace_value()等會像預期的那樣。

nested_css(css )
使用css選擇器創建嵌套加載器。所提供的選擇器應用于與此相關的選擇器ItemLoader。嵌套裝載機股份Item 與母公司ItemLoader這么調用add_xpath(), add_value(),replace_value()等會像預期的那樣。

get_collected_values(field_name )
返回給定字段的收集值。

get_output_value(field_name )
返回給定字段使用輸出處理器解析的收集值。此方法根本不填充或修改項目。

get_input_processor(field_name )
返回給定字段的輸入處理器。

get_output_processor(field_name )
返回給定字段的輸出處理器。

ItemLoader 實例具有以下屬性:

item
Item此項目加載器解析的對象。

context
此項目Loader 的當前活動上下文。

default_item_class
Item類(或工廠),用于在構造函數中未給出時實例化項。

default_input_processor
用于不指定一個字段的字段的默認輸入處理器。

default_output_processor
用于不指定一個字段的字段的默認輸出處理器。

default_selector_class
所使用的類構造selector的此 ItemLoader,如果只響應在構造函數給出。如果在構造函數中給出了選擇器,則忽略此屬性。此屬性有時在子類中被覆蓋。

selector
Selector從中提取數據的對象。它是在構造函數中給出的選擇器,或者是從構造函數中使用的給定的響應創建的 default_selector_class。此屬性意味著是只讀的。

嵌套裝載器

當解析來自文檔的子部分的相關值時,創建嵌套加載器可能是有用的。假設您從頁面的頁腳中提取細節,看起來像:

例:

<footer>
    <a class="social" >Like Us</a>
    <a class="social" >Follow Us</a>
    <a class="email" href="mailto:whatever@example.com">Email Us</a>
</footer>

如果沒有嵌套加載器,則需要為要提取的每個值指定完整的xpath(或css)。

例:

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

相反,您可以使用頁腳選擇器創建嵌套加載器,并相對于頁腳添加值。功能是相同的,但您避免重復頁腳選擇器。

例:

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

您可以任意嵌套加載器,并且可以使用xpath或css選擇器。作為一般的指導原則,當他們使你的代碼更簡單,但不要超越嵌套或使用解析器可能變得難以閱讀使用嵌套加載程序。

重用和擴展項目加載器

隨著你的項目越來越大,越來越多的爬蟲,維護成為一個根本的問題,特別是當你必須處理每個爬蟲的許多不同的解析規則,有很多異常,但也想重用公共處理器。

項目加載器旨在減輕解析規則的維護負擔,同時不會失去靈活性,同時提供了擴展和覆蓋它們的方便的機制。因此,項目加載器支持傳統的Python類繼承,以處理特定爬蟲(或爬蟲組)的差異。

例如,假設某個特定站點以三個短劃線(例如)包含其產品名稱,并且您不希望最終在最終產品名稱中刪除那些破折號。---Plasma TV---

以下是如何通過重用和擴展默認產品項目Loader(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)

另一種擴展項目加載器可能非常有用的情況是,當您有多種源格式,例如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)

這就是你通常擴展輸入處理器的方式。

對于輸出處理器,更常見的是在字段元數據中聲明它們,因為它們通常僅依賴于字段而不是每個特定站點解析規則(如輸入處理器)。另請參見: 聲明輸入和輸出處理器。

還有許多其他可能的方法來擴展,繼承和覆蓋您的項目加載器,不同的項目加載器層次結構可能更適合不同的項目。Scrapy只提供了機制; 它不強加任何特定的組織你的Loader集合 - 這取決于你和你的項目的需要。

可用內置處理器

即使您可以使用任何可調用函數作為輸入和輸出處理器,Scrapy也提供了一些常用的處理器,如下所述。其中一些,像MapCompose(通常用作輸入處理器)組成按順序執行的幾個函數的輸出,以產生最終的解析值。

下面是所有內置處理器的列表:

class scrapy.loader.processors.Identity

最簡單的處理器,什么都不做。它返回原始值不變。它不接收任何構造函數參數,也不接受Loader上下文。

例:

>>> from scrapy.loader.processors import Identity
>>> proc = Identity()
>>> proc(['one', 'two', 'three'])
['one', 'two', 'three']

class scrapy.loader.processors.TakeFirst

從接收到的值中返回第一個非空值/非空值,因此它通常用作單值字段的輸出處理器。它不接收任何構造函數參數,也不接受Loader上下文。

例:

>>> from scrapy.loader.processors import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'



class scrapy.loader.processors.Join(separator=u' ')

返回與構造函數中給定的分隔符聯接的值,默認為。它不接受加載器上下文。u' '

當使用默認分隔符時,此處理器相當于以下功能: 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'



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參數。對于那些處理器,這個處理器將通過該參數傳遞當前活動的Loader上下文。

在構造函數中傳遞的關鍵字參數用作傳遞給每個函數調用的默認Loader上下文值。但是,傳遞給函數的最后一個Loader上下文值將被當前可用該屬性訪問的當前活動Loader上下文ItemLoader.context() 覆蓋。


class scrapy.loader.processors.MapCompose(*functions, **default_loader_context)

與處理器類似,由給定功能的組成構成的Compose處理器。與此處理器的區別在于內部結果在函數之間傳遞的方式,如下所示:

該處理器的輸入值被迭代,并且第一函數被應用于每個元素。這些函數調用的結果(每個元素一個)被連接以構造新的迭代,然后用于應用??第二個函數,等等,直到最后一個函數被應用于收集的值列表的每個值遠。最后一個函數的輸出值被連接在一起以產生該處理器的輸出。

每個特定函數可以返回值或值列表,這些值通過應用于其他輸入值的相同函數返回的值列表展平。函數也可以返回None,在這種情況下,該函數的輸出將被忽略,以便在鏈上進行進一步處理。

此處理器提供了一種方便的方法來組合只使用單個值(而不是iterables)的函數。由于這個原因, MapCompose處理器通常用作輸入處理器,因為數據通常使用選擇器的 extract()方法提取,選擇器返回unicode字符串的列表。

下面的例子應該說明它是如何工作的:

>>> 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上下文,并且構造函數關鍵字參數用作默認上下文值。有關Compose更多信息,請參閱 處理器。


class scrapy.loader.processors.SelectJmes(json_path)

使用提供給構造函數的json路徑查詢值,并返回輸出。需要運行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']
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容