Элемент конвейера
После того, как элемент был получен пауком, он отправляется в конвейер элементов, который обрабатывает его через несколько компонентов, которые выполняются последовательно.
Каждый компонент конвейера элементов (иногда называемый просто «конвейер элементов») представляет собой класс 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.