Items and ItemLoaders

Scrapy · Practical · 12 min

Declared item fields catch typos and document your data, and loaders keep cleaning logic out of callbacks.

Plain dicts work, but larger projects declare item types. Scrapy supports, through the itemadapter library:

  • scrapy.Item subclasses with scrapy.Field() attributes: assigning an undeclared field raises KeyError, which catches typos.
  • dataclasses and attrs classes, giving type hints and defaults.

Pipelines and exporters handle all of them uniformly via ItemAdapter(item).

ItemLoader separates where data comes from and how it's cleaned:

  • loader = ItemLoader(item=Product(), response=response) (or selector= for a sub-element).
  • loader.add_css("name", "h1::text"), add_xpath(...), add_value("url", response.url); each call can add several values to a field.
  • loader.load_item() returns the populated item.

Every value passes through processors from the itemloaders.processors module:

  • Input processors run on each extracted value as it's added: MapCompose(str.strip, to_float) applies functions in order and drops None results.
  • Output processors run once on the collected list when loading: TakeFirst() returns the first non-empty value, Join(", ") concatenates.

Configure them per field (name_in, price_out attributes on a loader subclass, or Field(input_processor=...)) and set default_output_processor = TakeFirst(), since otherwise every field is a list.

Loaders shine on sites with many similar pages; for a quick spider, plain dicts with careful selectors are fine.

Example

from dataclasses import dataclass
from itemloaders.processors import MapCompose, TakeFirst, Join
from scrapy.http import HtmlResponse
from scrapy.loader import ItemLoader
@dataclass
class Product:
    name: str | None = None
    price: float | None = None
    tags: str | None = None

class ProductLoader(ItemLoader):
    default_item_class = Product
    default_output_processor = TakeFirst()
    name_in = MapCompose(str.strip)
    price_in = MapCompose(lambda s: s.replace("EUR", "").strip(), float)
    tags_out = Join(", ")

body = b'<h1> Kettle </h1><span class="price">29.90 EUR</span><a class="tag">home</a><a class="tag">steel</a>'
response = HtmlResponse(url="https://shop.example/p/1", body=body, encoding="utf-8")
loader = ProductLoader(response=response)
loader.add_css("name", "h1::text")
loader.add_css("price", "span.price::text")
loader.add_css("tags", "a.tag::text")
print(loader.load_item())
# Product(name='Kettle', price=29.9, tags='home, steel')

Remember

  • scrapy.Item, dataclasses and attrs all work as items via ItemAdapter
  • ItemLoader: add_css / add_xpath / add_value, then load_item()
  • Input processors clean each value; output processors combine the list
  • Set default_output_processor = TakeFirst() to avoid list fields

Watch out for

  • Forgetting an output processor and exporting every field as a one-element list.

Check yourself

  1. What does MapCompose(str.strip, float) do as an input processor?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scrapy.org/en/latest/topics/loaders.html