Scrapy筆記(5)- 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 Loader為我們提供了生成Item的相當便利的方法。Item為抓取的資料提供了容器,而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)
執行流程是這樣的:
xpath1
中的資料被提取出來,然後傳輸到name
欄位的輸入處理器中,在輸入處理器處理完後生成結果放在Item Loader裡面(這時候沒有賦值給item)xpath2
資料被提取出來,然後傳輸給(1)中同樣的輸入處理器,因為它們都是name
欄位的處理器,然後處理結果被附加到(1)的結果後面- 跟2一樣
- 跟3一樣,不過這次是直接的字面字串值,先轉換成一個單元素的可迭代物件再傳給輸入處理器
- 上面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(),
)
優先順序:
- 在Item Loader中定義的
field_in
和field_out
- Filed元資料(
input_processor
和output_processor
關鍵字) - 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')
內建的處理器
Identity
啥也不做TakeFirst
返回第一個非空值,通常用作輸出處理器Join
將結果連起來,預設使用空格’ ‘Compose
將函式連結起來形成管道流,產生最後的輸出MapCompose
跟上面的Compose
類似,區別在於內部結果在函式中的傳遞方式.它的輸入值是可迭代的,首先將第一個函式依次作用於所有值,產生新的可迭代輸入,作為第二個函式的輸入,最後生成的結果連起來返回最終值,一般用在輸入處理器中。SelectJmes
使用json路徑來查詢值並返回結果