Элемент конвейера

После того, как элемент был получен пауком, он отправляется в конвейер элементов, который обрабатывает его через несколько компонентов, которые выполняются последовательно.

Каждый компонент конвейера элементов (иногда называемый просто «конвейер элементов») представляет собой класс Python, реализующий простой метод. Они получают элемент и выполняют над ним действие, а также решают, следует ли продолжить выполнение элемента по конвейеру или его следует отбросить и больше не обрабатывать.

Типичное использование конвейеров элементов:

  • очистка HTML-данных

  • проверка извлеченных данных (проверка того, что элементы содержат определённые поля)

  • проверка дубликатов (и их удаление)

  • сохранение извлеченного элемента в базе данных

Написание собственного конвейера элементов

Каждый компонент конвейера элементов представляет собой класс Python, который должен реализовывать следующий метод:

process_item(self, item, spider)

Данный метод вызывается для каждого компонента конвейера элементов.

item — это объект элемента, см. Поддержка всех типов элементов.

process_item() должен либо: возвращать объект элемента, возвращать Deferred, либо вызывать исключение DropItem.

Выпавшие элементы больше не обрабатываются другими компонентами конвейера.

Параметры
  • item (item object) – собранный элемент

  • spider (Spider object) – паук, собравший элемент

Кроме того, они могут также реализовать следующие методы:

open_spider(self, spider)

Данный метод вызывается при открытии паука.

Параметры

spider (Spider object) – паук, который был открыт

close_spider(self, spider)

Данный метод вызывается, когда паук закрыт.

Параметры

spider (Spider object) – паук, который закрылся

from_crawler(cls, crawler)

Если присутствует, данный метод класса вызывается для создания экземпляра конвейера из Crawler. Он должен возвращает новый экземпляр конвейера. Объект Crawler обеспечивает доступ ко всем основным компонентам Scrapy, таким как настройки и сигналы; это способ конвейера получить к ним доступ и подключить его функциональность к Scrapy.

Параметры

crawler (Crawler object) – поисковый робот, использующий данный конвейер

Пример конвейера элемента

Проверка цен и сброс элементов без цен

Давайте посмотрим на следующий гипотетический конвейер, который настраивает атрибут price для тех товаров, которые не включают НДС (атрибут price_excludes_vat), и отбрасывает те товары, которые не содержат цены:

from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
class PricePipeline:

    vat_factor = 1.15

    def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        if adapter.get('price'):
            if adapter.get('price_excludes_vat'):
                adapter['price'] = adapter['price'] * self.vat_factor
            return item
        else:
            raise DropItem(f"Missing price in {item}")

Записывать элементы в файл JSON

Следующий конвейер хранит все полученные элементы (от всех пауков) в одном файле items.jl, содержащем по одному элементу на строку, сериализованную в формате JSON:

import json

from itemadapter import ItemAdapter

class JsonWriterPipeline:

    def open_spider(self, spider):
        self.file = open('items.jl', 'w')

    def close_spider(self, spider):
        self.file.close()

    def process_item(self, item, spider):
        line = json.dumps(ItemAdapter(item).asdict()) + "\n"
        self.file.write(line)
        return item

Примечание

Цель JsonWriterPipeline — просто показать, как писать конвейеры элементов. Если вы действительно хотите сохранить все полученные элементы в файле JSON, вам следует использовать Экспорт фида.

Записывать элементы в MongoDB

В этом примере мы будем записывать элементы в MongoDB, используя pymongo. Адрес MongoDB и имя базы данных указываются в настройках Scrapy; коллекция MongoDB именуется после класса элемента.

Суть этого примера — показать, как использовать метод from_crawler() и как правильно сканировать ресурсы:

import pymongo
from itemadapter import ItemAdapter

class MongoPipeline:

    collection_name = 'scrapy_items'

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.collection_name].insert_one(ItemAdapter(item).asdict())
        return item

Создать снимок экрана с товаром

В этом примере показано, как использовать синтаксис корутины в методе process_item().

Данный конвейер элементов делает запрос к локально запущенному экземпляру Splash, чтобы отобразить снимок экрана с URL-адресом элемента. После загрузки ответа на запрос конвейер элементов сохраняет снимок экрана в файл и добавляет имя файла к элементу.

import hashlib
from urllib.parse import quote

import scrapy
from itemadapter import ItemAdapter
from scrapy.utils.defer import maybe_deferred_to_future


class ScreenshotPipeline:
    """Pipeline that uses Splash to render screenshot of
    every Scrapy item."""

    SPLASH_URL = "http://localhost:8050/render.png?url={}"

    async def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        encoded_item_url = quote(adapter["url"])
        screenshot_url = self.SPLASH_URL.format(encoded_item_url)
        request = scrapy.Request(screenshot_url)
        response = await maybe_deferred_to_future(spider.crawler.engine.download(request, spider))

        if response.status != 200:
            # Error happened, return item.
            return item

        # Save screenshot to file, filename will be hash of url.
        url = adapter["url"]
        url_hash = hashlib.md5(url.encode("utf8")).hexdigest()
        filename = f"{url_hash}.png"
        with open(filename, "wb") as f:
            f.write(response.body)

        # Store filename in item.
        adapter["screenshot_filename"] = filename
        return item

Фильтр дубликатов

Фильтр, который ищет повторяющиеся элементы и отбрасывает те элементы, которые уже были обработаны. Допустим, у наших элементов уникальный идентификатор, но наш паук возвращает несколько элементов с одним и тем же идентификатором:

from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem

class DuplicatesPipeline:

    def __init__(self):
        self.ids_seen = set()

    def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        if adapter['id'] in self.ids_seen:
            raise DropItem(f"Duplicate item found: {item!r}")
        else:
            self.ids_seen.add(adapter['id'])
            return item

Активация компонента конвейера элементов

Чтобы активировать компонент конвейера элементов, вы должны добавить его класс в параметр ITEM_PIPELINES, как в следующем примере:

ITEM_PIPELINES = {
    'myproject.pipelines.PricePipeline': 300,
    'myproject.pipelines.JsonWriterPipeline': 800,
}

Целочисленные значения, которые вы присваиваете классам в этом параметре, определяют порядок, в котором они выполняются: элементы переходят от классов с более низким значением к классам с более высоким значением. Данные числа принято определять в диапазоне от 0 до 1000.