アイテム・ローダー

アイテムローダーは、スクレイピングされた アイテム を生成するための便利なメカニズムを提供します。 アイテムは独自の辞書のようなAPIを使用して入力できますが、アイテムローダーは、生の抽出データを割り当てる前に解析するなどの一般的なタスクを自動化することにより、スクレイピングプロセスからアイテムを入力するための、はるかに便利なAPIを提供します。

言い換えると、 アイテム はスクレイピングされたデータの コンテナ を提供し、アイテム・ローダーはそのコンテナに 格納 するメカニズムを提供します。

アイテム・ローダーは、スパイダーまたはソース形式(HTML、XMLなど))によってさまざまなフィールド・パース・ルールを拡張およびオーバーライドするための柔軟で効率的かつ簡単なメカニズムを提供するように設計されています。

注釈

アイテム・ローダーは itemloaders ライブラリの拡張であり、 レスポンス へのサポートを追加して、Scrapyの操作をさらに簡単にします。

アイテムを格納するためにアイテム・ローダーを使う

アイテム・ローダーを使用するには、最初にインスタンス化する必要があります。 あなたは アイテム・オブジェクト を使用して、または使用せずにインスタンス化することができます。使用せずにインスタンス化する場合、アイテム・オブジェクト はアイテム・ローダーの __init__ メソッド内で、 ItemLoader.default_item_class 属性で指定された アイテム を使用して自動的に生成されます。

それから、通常は セレクター を使用して、あなたはアイテム・ローダーへの値の収集を開始します。 同じアイテム・フィールドに複数の値を追加できます。アイテム・ローダーは、適切な処理機能を使用して、それらの値を後で「結合」(join)する方法を知っています。

注釈

収集されたデータはリストとして内部に保存され、同じフィールドに複数の値を追加できます。 ローダーの作成時に item 引数が渡された場合、アイテムの各値は、既に反復可能(iterable)である場合はそのまま保存され、単一の値の場合はリストで包まれます。

以下は、 アイテムの章 で宣言された Product item を使用した、スパイダー 内での典型的なアイテム・ローダーの使用法です:

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

そのコードをざっと見ると、ページ内の2つの異なるXPathロケーションから name フィールドが抽出されていることがわかります:

  1. //div[@class="product_name"]

  2. //div[@class="product_title"]

いいかえると、データは、 add_xpath() メソッドを使用して、2つのXPathロケーションから抽出することで収集されます。 これは後で name フィールドに割り当てられるデータです。

その後、同様の呼び出しが price および stock フィールド(後者は add_css() メソッドでCSSセレクターを使用)に対して行われ、おわりに last_update フィールドは add_value() という別のメソッドを使用して、リテラル値( today )を直接入力します:

すべてのデータが収集されると、最後に、 ItemLoader.load_item() メソッドが呼び出され、実際に返されるのは、以前に add_xpath()add_css()add_value() の呼び出しで収集したデータを格納したアイテムです。

dataclassアイテムの操作

デフォルトでは、 dataclassアイテム では、作成時にすべてのフィールドを渡す必要があります。これは、アイテム・ローダーでdataclassアイテムを使用するときに問題になる可能性があります。アイテム・ローダーでは、事前入力されたアイテムがローダーに渡されない場合、各フィールドはローダーの add_xpath()add_css()add_value() メソッドを使用して段階的に入力されるためです。

これを克服するための1つのアプローチは、 default 引数を指定して field() 関数を使用してアイテムを定義することです。

from dataclasses import dataclass, field
from typing import Optional

@dataclass
class InventoryItem:
    name: Optional[str] = field(default=None)
    price: Optional[float] = field(default=None)
    stock: Optional[int] = field(default=None)

入力プロセッサと出力プロセッサ

アイテムローダーには、各(アイテム)フィールドごとに1つの入力プロセッサと1つの出力プロセッサが含まれます。入力プロセッサは、( add_xpath() または add_css() または add_value() メソッドを介して)受信したデータをすぐに処理し、入力プロセッサの結果が収集されてアイテム・ローダー内に保持されます。すべてのデータを収集した後、 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 フィールドの 入力プロセッサ を通過します。 入力プロセッサの結果が収集され、アイテムローダーに保持されます(ただし、アイテムにはまだ割り当てられていません)。

  2. xpath2 からのデータが抽出され、ステップ(1)で使用されたのと同じ 入力プロセッサ を通過します。 入力プロセッサの結果は、(存在する場合、)ステップ(1)で収集されたデータに追加されます。

  3. この場合は、データが css CSSセレクターから抽出され、ステップ(1)とステップ(2)で使用された同じ 入力プロセッサ を通過することを除いて、以前の場合と似ています。 入力プロセッサの結果は、(存在する場合、)ステップ(1)およびステップ(2)で収集されたデータに追加されます。

  4. この場合も以前の場合と似ていますが、XPath式またはCSSセレクターから抽出されるのではなく、収集される値が直接割り当てられる点が異なります。 ただし、値は引き続き入力プロセッサを介して渡されます。 この場合、値は反復可能ではなく(not iterable)、そして、入力プロセッサに常に反復可能要素を受け取るため(always receive iterables)、入力プロセッサに渡す前に単一の要素の反復可能要素に変換されます。

  5. ステップ(1)〜(4)で収集されたデータは、 name フィールドの 出力プロセッサ を介して渡されます。 出力プロセッサの結果は、アイテムの name フィールドに割り当てられた値です。

プロセッサは、パース対象のデータとともに呼び出され、パースされた値を返す、呼び出し可能なオブジェクトにすぎないことに注意してください。 したがって、任意の関数を入力または出力プロセッサとして使用できます。 唯一の要件は、反復可能(iterable)な1つの(そして1つだけの)位置引数を受け入れる必要があることです。

バージョン 2.0 で変更: プロセッサはもはやメソッドである必要はありません。

注釈

入力プロセッサと出力プロセッサは両方とも、イテレータを最初の引数として受け取る必要があります。 これらの関数の出力は何でもかまいません。 入力プロセッサの結果は、(そのフィールドのために)収集された値を含む(ローダー内の)内部リストに追加されます。出力プロセッサの結果は、最終的にアイテムに割り当てられる値です。

もう1つ注意する必要があるのは、入力プロセッサから返される値が内部(リスト)で収集され、出力プロセッサに渡されてフィールドに入力されることです。

なお、 itemloaders には便宜上いくつかの 一般的なプロセッサ が組み込まれています。

アイテム・ローダーの宣言

アイテム・ローダーは、クラス定義構文を使用して宣言されます。 以下に例を示します:

from itemloaders.processors import TakeFirst, MapCompose, Join
from scrapy.loader import ItemLoader

class ProductLoader(ItemLoader):

    default_output_processor = TakeFirst()

    name_in = MapCompose(str.title)
    name_out = Join()

    price_in = MapCompose(str.strip)

    # ...

ご覧のように、入力プロセッサは _in 接尾辞を使用して宣言され、出力プロセッサは _out 接尾辞を使用して宣言されています。 また、 ItemLoader.default_input_processorItemLoader.default_output_processor 属性を使用して、デフォルトの入出力プロセッサを宣言することもできます。

入力プロセッサと出力プロセッサの宣言

前述のとおり、入力プロセッサと出力プロセッサはアイテム・ローダー定義で宣言できます。この方法で入力プロセッサを宣言することは非常に一般的です。 ただし、使用する入力プロセッサと出力プロセッサを指定できる場所がもう1つあります。 アイテム・フィールド メタデータです。以下に例を示します:

import scrapy
from itemloaders.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', ['Welcome to my', '<strong>website</strong>'])
>>> il.add_value('price', ['&euro;', '<span>1000</span>'])
>>> il.load_item()
{'name': 'Welcome to my website', 'price': '1000'}

入力プロセッサと出力プロセッサの両方の優先順位は次のとおりです:

  1. アイテムローダーのフィールド固有の属性: field_in および field_out (最優先)

  2. フィールド・メタデータ(input_processoroutput_processor キー)

  3. アイテム・ローダー デフォルト: ItemLoader.default_input_processor()ItemLoader.default_output_processor() (最も低い優先度)

アイテムローダーの再利用と拡張 も参照下さい。

アイテム・ローダー・コンテキスト

アイテムローダーコンテキストは、アイテムローダーのすべての入力プロセッサおよび出力プロセッサ間で共有される任意のキー・値ペアの辞書です。アイテム・ローダーの宣言、インスタンス化、または使用時に渡すことができます。これらは、入出力プロセッサの動作を変更するために使用されます。

たとえば、テキスト値を受け取り、そこから長さを抽出する parse_length 関数があるとします:

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

loader_context 引数を受け入れることにより、プロセッサ関数はアイテム・ローダーがアイテム・ローダー・コンテキストを受け取ることができることを明示的に伝えています。そのため、アイテム・ローダーはプロセッサ関数呼び出し時に現在アクティブなコンテキストを渡します。よってプロセッサ関数(この場合 parse_length)は現在アクティブなコンテキストを使用できます。

アイテムローダーのコンテキスト値を変更する方法はいくつかあります:

  1. 現在アクティブなアイテム・ローダー・コンテキスト( context 属性)を変更する:

    loader = ItemLoader(product)
    loader.context['unit'] = 'cm'
    
  2. アイテム・ローダーのインスタンス化時(アイテムローダーの __init __ メソッドのキーワード引数はアイテム・ローダーのコンテキストに格納されます):

    loader = ItemLoader(product, unit='cm')
    
  3. アイテム・ローダーの宣言で、アイテム・ローダー・コンテキストを使用したインスタンス化をサポートする入出力プロセッサ用。 MapCompose はそれらの1つです:

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

ItemLoaderオブジェクト

class scrapy.loader.ItemLoader(item=None, selector=None, response=None, parent=None, **context)[ソース]

A user-friendly abstraction to populate an item with data by applying field processors to scraped data. When instantiated with a selector or a response it supports data extraction from web pages using selectors.

パラメータ

If no item is given, one is instantiated automatically using the class in default_item_class.

The item, selector, response and remaining keyword arguments are assigned to the Loader context (accessible through the context attribute).

item

The item object being parsed by this Item Loader. This is mostly used as a property so, when attempting to override this value, you may want to check out default_item_class first.

context

The currently active Context of this Item Loader.

default_item_class

An item class (or factory), used to instantiate items when not given in the __init__ method.

default_input_processor

The default input processor to use for those fields which don't specify one.

default_output_processor

The default output processor to use for those fields which don't specify one.

default_selector_class

The class used to construct the selector of this ItemLoader, if only a response is given in the __init__ method. If a selector is given in the __init__ method this attribute is ignored. This attribute is sometimes overridden in subclasses.

selector

The Selector object to extract data from. It's either the selector given in the __init__ method or one created from the response given in the __init__ method using the default_selector_class. This attribute is meant to be read-only.

add_css(field_name, css, *processors, **kw)[ソース]

Similar to ItemLoader.add_value() but receives a CSS selector instead of a value, which is used to extract a list of unicode strings from the selector associated with this ItemLoader.

See get_css() for kwargs.

パラメータ

css (str) -- the CSS selector to extract data from

Examples:

# 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 (.*)')
add_value(field_name, value, *processors, **kw)[ソース]

Process and then add the given value for the given field.

The value is first passed through get_value() by giving the processors and kwargs, and then passed through the field input processor and its result appended to the data collected for that field. If the field already contains collected data, the new data is added.

The given field_name can be None, in which case values for multiple fields may be added. And the processed value should be a dict with field_name mapped to values.

Examples:

loader.add_value('name', 'Color TV')
loader.add_value('colours', ['white', 'blue'])
loader.add_value('length', '100')
loader.add_value('name', 'name: foo', TakeFirst(), re='name: (.+)')
loader.add_value(None, {'name': 'foo', 'sex': 'male'})
add_xpath(field_name, xpath, *processors, **kw)[ソース]

Similar to ItemLoader.add_value() but receives an XPath instead of a value, which is used to extract a list of strings from the selector associated with this ItemLoader.

See get_xpath() for kwargs.

パラメータ

xpath (str) -- the XPath to extract data from

Examples:

# 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 (.*)')
get_collected_values(field_name)[ソース]

Return the collected values for the given field.

get_css(css, *processors, **kw)[ソース]

Similar to ItemLoader.get_value() but receives a CSS selector instead of a value, which is used to extract a list of unicode strings from the selector associated with this ItemLoader.

パラメータ
  • css (str) -- the CSS selector to extract data from

  • re (str or typing.Pattern) -- a regular expression to use for extracting data from the selected CSS region

Examples:

# 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 (.*)')
get_output_value(field_name)[ソース]

Return the collected values parsed using the output processor, for the given field. This method doesn't populate or modify the item at all.

get_value(value, *processors, **kw)[ソース]

Process the given value by the given processors and keyword arguments.

Available keyword arguments:

パラメータ

re (str or typing.Pattern) -- a regular expression to use for extracting data from the given value using extract_regex() method, applied before processors

Examples:

>>> from itemloaders import ItemLoader
>>> from itemloaders.processors import TakeFirst
>>> loader = ItemLoader()
>>> loader.get_value('name: foo', TakeFirst(), str.upper, re='name: (.+)')
'FOO'
get_xpath(xpath, *processors, **kw)[ソース]

Similar to ItemLoader.get_value() but receives an XPath instead of a value, which is used to extract a list of unicode strings from the selector associated with this ItemLoader.

パラメータ
  • xpath (str) -- the XPath to extract data from

  • re (str or typing.Pattern) -- a regular expression to use for extracting data from the selected XPath region

Examples:

# 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 (.*)')
load_item()[ソース]

Populate the item with the data collected so far, and return it. The data collected is first passed through the output processors to get the final value to assign to each item field.

nested_css(css, **context)[ソース]

Create a nested loader with a css selector. The supplied selector is applied relative to selector associated with this ItemLoader. The nested loader shares the item with the parent ItemLoader so calls to add_xpath(), add_value(), replace_value(), etc. will behave as expected.

nested_xpath(xpath, **context)[ソース]

Create a nested loader with an xpath selector. The supplied selector is applied relative to selector associated with this ItemLoader. The nested loader shares the item with the parent ItemLoader so calls to add_xpath(), add_value(), replace_value(), etc. will behave as expected.

replace_css(field_name, css, *processors, **kw)[ソース]

Similar to add_css() but replaces collected data instead of adding it.

replace_value(field_name, value, *processors, **kw)[ソース]

Similar to add_value() but replaces the collected data with the new value instead of adding it.

replace_xpath(field_name, xpath, *processors, **kw)[ソース]

Similar to add_xpath() but replaces collected data instead of adding it.

入れ子になったローダー

ドキュメントのサブセクションから関連する値をパースする場合、入れ子になったローダーを作成すると便利です。以下のようなページのフッターから詳細を抽出しているとします:

例:

<footer>
    <a class="social" href="https://facebook.com/whatever">Like Us</a>
    <a class="social" href="https://twitter.com/whatever">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クラスの継承をサポートしています。

たとえば、ある特定のサイトが製品名を3つのダッシュ(たとえば ---Plasma TV--- )で囲んでおり、最終製品名でそれらのダッシュをスクレイピングしたくないと仮定します。

ここで、デフォルトの製品アイテムローダー( ProductLoader )を再利用して拡張することで、これらのダッシュを削除する方法を次に示します:

from itemloaders.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 itemloaders.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はメカニズムのみを提供します。 ローダーコレクションの特定の構成を強制することはありません。それはあなたとプロジェクトのニーズ次第です。