Архитектура Scrapy: путь запроса по конвейеру

Понять Scrapy — значит понять путь одного запроса через его компоненты.
В сердце Scrapy — движок (Engine), который дирижирует потоком данных между пауком, планировщиком (Scheduler), загрузчиком (Downloader) и пайплайнами (Item Pipeline). Понимание этого цикла объясняет, где что настраивать.

Scrapy кажется магией, пока не увидишь его архитектуру. На самом деле это конвейер с понятными станциями. Проследим путь данных по официальной схеме фреймворка.

        ┌─────────────────── ДВИЖОК (Engine) ───────────────────┐
        │            дирижёр: гоняет данные по кругу             │
        v                  ^                  ^                   v
   ┌─────────┐        ┌─────────┐       ┌──────────┐       ┌──────────┐
   │  ПАУК   │  items │ПЛАНИРОВ- │ req   │ ЗАГРУЗЧИК │ resp │ ПАЙПЛАЙН │
   │ (Spider)│ -----> │  ЩИК     │ ----> │(Downloader)│ ---> │(Pipeline)│
   │ parse() │ <----- │(очередь) │ <---- │ HTTP-запрос│      │ чистка/БД│
   └─────────┘  req   └─────────┘ resp  └──────────┘       └──────────┘

  1. паук отдаёт первый Request -> 2. планировщик ставит в очередь
  3. загрузчик качает страницу  -> 4. паук парсит ответ
  5. данные идут в пайплайн     -> 6. новые ссылки -> снова в очередь

Кто за что отвечает

КомпонентРоль
Spider (паук)Твой код: что качать и как парсить
Scheduler (планировщик)Очередь запросов, дедупликация URL
Downloader (загрузчик)Асинхронно скачивает страницы по HTTP
Item PipelineЧистит, валидирует и сохраняет данные (в БД, файл)
MiddlewaresХуки в потоке: заголовки, прокси, повторы, robots.txt

Как работает под капотом

Цикл такой: движок берёт у паука первый Request и отдаёт планировщику. Планировщик кладёт его в очередь (и отбрасывает дубли URL). Загрузчик асинхронно качает страницу и возвращает Response. Движок передаёт ответ пауку — тот в parse выдаёт данные и новые запросы. Данные уходят в пайплайн (чистка, запись в базу), новые запросы — обратно в планировщик. Так колесо крутится, пока очередь не опустеет. Middleware — это точки врезки: например, RobotsTxtMiddleware проверяет robots.txt, а другой может подставлять прокси или User-Agent.

Попробуй сам ▶

# смоделируем планировщик Scrapy: очередь + дедупликация URL
from collections import deque

queue = deque(['/page1'])
seen = {'/page1'}

# что 'находит' паук на каждой странице
found = {'/page1': ['/page2', '/page1'],
         '/page2': ['/page3', '/page2'],
         '/page3': ['/page1']}

visited = []
while queue:
    url = queue.popleft()
    visited.append(url)
    for link in found.get(url, []):
        if link not in seen:      # дедупликация!
            seen.add(link)
            queue.append(link)

print('Обойдено:', visited)
print('Дублей не было — каждый URL ровно один раз')

Asyncio под капотом и роль движка

Ключ к производительности Scrapy — асинхронность. Исторически фреймворк построен на Twisted, а в современных версиях интегрирован и с asyncio. Смысл один: загрузчик не блокируется в ожидании ответа сервера. Пока летят и ждут сотни запросов, процессор не простаивает — он обрабатывает уже пришедшие ответы. Поэтому Scrapy на одном ядре обгоняет наивный последовательный скрипт в десятки раз, не прибегая к сложной ручной многопоточности.

Движок (Engine) в этой схеме — диспетчер, который никогда не «решает» сам, что парсить, а лишь передаёт объекты между компонентами по строгому протоколу. Именно поэтому каждую станцию конвейера можно настраивать и расширять независимо: подменить планировщик, добавить middleware для прокси, вставить пайплайн валидации — и остальное продолжит работать. Эта развязанность (loose coupling) — образец хорошей архитектуры: понимая, какой объект на каком этапе движется, ты точно знаешь, где вмешаться, чтобы изменить поведение краулера, не ломая всё остальное.

Частые ошибки

  • Сохранять данные прямо в пауке. Запись в БД — задача пайплайна, а не parse.
  • Не пользоваться дедупликацией. Планировщик сам отбрасывает повторы URL — не дублируй обход вручную.
  • Лезть в middleware без нужды. Большинство задач решаются настройками; кастомный middleware — для особых случаев (прокси, ротация UA).

Best practices

  • Держи паука тонким: только «что качать и как парсить». Логику сохранения вынеси в пайплайн.
  • Чистку и валидацию данных делай в Item Pipeline — это его прямая задача.
  • Используй middleware для сквозных задач: прокси, повторы, заголовки, соблюдение robots.txt.

Понимание архитектуры окупается ровно тогда, когда что-то идёт не так. Данные не сохраняются — смотри пайплайн. Сайт блокирует — смотри downloader middleware (заголовки, прокси). URL обходятся не те — смотри логику паука и правила следования по ссылкам. Краулер слишком быстрый — смотри настройки и AutoThrottle. Зная, какой компонент за что отвечает, ты диагностируешь проблему за минуты вместо часов наугад. Архитектурная карта — это, по сути, карта мест, где можно вмешаться.

Итог: Scrapy — это конвейер «паук → планировщик → загрузчик → пайплайн», которым дирижирует движок. Понимая роли, ты знаешь, где настраивать вежливость, где чистить данные, а где подключать прокси.

Проверьте себя
1. За что отвечает Item Pipeline в Scrapy?
AЗа скачивание страниц
BЗа очередь запросов
CЗа чистку, валидацию и сохранение извлечённых данных (например, в базу)
DЗа рендеринг JavaScript
2. Что делает планировщик (Scheduler) Scrapy с дублирующимися URL?
AСкачивает их повторно
BОтбрасывает дубли, не добавляя один и тот же URL в очередь дважды
CСохраняет в отдельный файл
DПередаёт в пайплайн