Scrapy學習篇:Item詳解

Scrapy筆記05- Item詳解

Item是保存結構數據的地方,Scrapy可以將解析結果以字典形式返回,但是Python中字典缺少結構,在大型爬蟲系統中很不方便。

Item提供了類字典的API,並且可以很方便的聲明字段,很多Scrapy組件可以利用Item的其他信息。

定義Item

定義Item非常簡單,只需要繼承scrapy.Item類,並將所有字段都定義為scrapy.Field類型即可

import scrapy

class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)

Item Fields

Field對象可用來對每個字段指定元數據。例如上面last_updated的序列化函數指定為str,可任意指定元數據,不過每種元數據對於不同的組件意義不一樣。

Item使用示例

你會看到Item的使用跟Python中的字典API非常類似

創建Item

>>> product = Product(name='Desktop PC', price=1000)
>>> print product
Product(name='Desktop PC', price=1000)

獲取值

>>> product['name']
Desktop PC
>>> product.get('name')
Desktop PC

>>> product['price']
1000

>>> product['last_updated']
Traceback (most recent call last):
...
KeyError: 'last_updated'

>>> product.get('last_updated', 'not set')
not set

>>> product['lala'] # getting unknown field
Traceback (most recent call last):
...
KeyError: 'lala'

>>> product.get('lala', 'unknown field')
'unknown field'

>>> 'name' in product # is name field populated?
True

>>> 'last_updated' in product # is last_updated populated?
False

>>> 'last_updated' in product.fields # is last_updated a declared field?
True

>>> 'lala' in product.fields # is lala a declared field?
False

設置值

>>> product['last_updated'] = 'today'
>>> product['last_updated']
today

>>> product['lala'] = 'test' # setting unknown field
Traceback (most recent call last):
...

KeyError: 'Product does not support field: lala'

>>> product.keys()訪問所有的值

['price', 'name']

>>> product.items()
[('price', 1000), ('name', 'Desktop PC')]

Item Loader為我們提供了生成Item的相當便利的方法。Item為抓取的數據提供了容器,而Item Loader可以讓我們非常方便的將輸入填充到容器中。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字段是從兩個xpath路徑添累加後得到。

輸入/輸出處理器

每個Item Loader對每個Field都有一個輸入處理器和一個輸出處理器。輸入處理器在數據被接受到時執行,當數據收集完後調用ItemLoader.load_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字段的輸入處理器中,在輸入處理器處理完後生成結果放在Item Loader裡面(這時候沒有賦值給item)
  2. xpath2數據被提取出來,然後傳輸給(1)中同樣的輸入處理器,因為它們都是name字段的處理器,然後處理結果被附加到(1)的結果後面
  3. 跟2一樣
  4. 跟3一樣,不過這次是直接的字面字符串值,先轉換成一個單元素的可迭代對象再傳給輸入處理器
  5. 上面4步的數據被傳輸給name的輸出處理器,將最終的結果賦值給name字段

自定義Item Loader

使用類定義語法,下面是一個例子

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_input_processor.

在Field定義中聲明輸入/輸出處理器

還有個地方可以非常方便的添加輸入/輸出處理器,那就是直接在Field定義中

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

優先級:

  1. 在Item Loader中定義的field_in和field_out
  2. Filed元數據(input_processor和output_processor關鍵字)
  3. Item Loader中的默認的

Tips:一般來講,將輸入處理器定義在Item Loader的定義中field_in,然後將輸出處理器定義在Field元數據中

Item Loader上下文

Item Loader上下文被所有輸入/輸出處理器共享,比如你有一個解析長度的函數

def parse_length(text, loader_context):
unit = loader_context.get('unit', 'm')
# ... length parsing code goes here ...
return parsed_length

初始化和修改上下文的值

loader = ItemLoader(product)
loader.context['unit'] = 'cm'

loader = ItemLoader(product, unit='cm')

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

內置的處理器

  1. Identity 啥也不做
  2. TakeFirst 返回第一個非空值,通常用作輸出處理器
  3. Join 將結果連起來,默認使用空格’ ‘
  4. Compose 將函數鏈接起來形成管道流,產生最後的輸出
  5. MapCompose 跟上面的Compose類似,區別在於內部結果在函數中的傳遞方式. 它的輸入值是可迭代的,首先將第一個函數依次作用於所有值,產生新的可迭代輸入,作為第二個函數的輸入,最後生成的結果連起來返回最終值,一般用在輸入處理器中。
  6. SelectJmes 使用json路徑來查詢值並返回結果


分享到:


相關文章: