После обновления до Scrapy 2.16 или новее паук, в котором определён только start_requests(), открывается, не отправляет ни одного запроса и закрывается так, будто отработал:
2026-10-09 12:33:33 [scrapy.core.engine] INFO: Spider opened
2026-10-09 12:33:33 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2026-10-09 12:33:33 [scrapy.core.engine] INFO: Closing spider (finished)
2026-10-09 12:33:33 [scrapy.core.engine] INFO: Spider closed (finished)Это строки движка и logstats из запуска на Scrapy 2.19.0. В журнале не было ни одной строки WARNING или ERROR, код выхода был 0, а parse() не вызывался. В итоговой статистике было 'finish_reason': 'finished' и не было ключа downloader/request_count.
Причина: в Scrapy 2.16.0 (19 мая 2026 года) удалён метод Spider.start_requests(), устаревший с версии 2.13.0. В примечаниях к версии сказано, что он «is removed and no longer called» (pull request #7490). Решение: переименовать метод в async def start():
# Before: Scrapy 2.16 and later never call this
def start_requests(self):
yield scrapy.Request("https://example.com/", callback=self.parse)# After
async def start(self):
yield scrapy.Request("https://example.com/", callback=self.parse)После одной этой правки тот же паук обошёл свою страницу на Scrapy 2.16.0 и 2.19.0.
| Что вы видите на 2.16+ | Причина | Решение |
|---|---|---|
Crawled 0 pages, затем Spider closed (finished), предупреждений нет | В пауке определён только start_requests() | Переименуйте его в async def start() |
| Обход идёт, но middleware больше не меняет стартовые запросы | В spider middleware определён только process_start_requests() | Добавьте async def process_start() |
TypeError: Middleware ... doesn't support asynchronous spider output, код выхода 1 | В spider middleware синхронный process_spider_output() | Сделайте его асинхронным генератором |
Всё, что описано ниже, записано 9 октября 2026 года на Scrapy 2.19.0, Twisted 26.4.0 и Python 3.14.4 и повторено на Scrapy 2.16.0, 2.15.2 и 2.12.0. Паука, который только задаёт start_urls и не определяет ни одного из двух методов, изменение не затрагивает: он обошёл страницу на всех четырёх версиях.
Замените start_requests() на async def start()
Spider.start() — асинхронный генератор: async def с yield. Сами запросы остаются прежними. Две оплошности дают другой симптом: строку ERROR и по-прежнему ни одного запроса:
TypeError: e1_start_not_async.LabSpider.start() must be an asynchronous generator, i.e. an async def method with yield statements, got <class 'generator'>Это def start() без async. Если async def start() возвращает список, сообщение заканчивается на got <class 'coroutine'>. В обоих случаях Scrapy 2.19.0 закрыл паука с 'finish_reason': 'start_error', а код выхода остался 0.
Если паук переопределял start_requests(), чтобы добавить заголовки или meta к запросам из start_urls, сохраните цикл:
async def start(self):
for url in self.start_urls:
yield scrapy.Request(
url,
headers={"Accept-Language": "en-AU"},
meta={"page": 1},
dont_filter=True,
)Стандартный start() — тот же цикл с dont_filter=True и больше ничем, поэтому оставьте этот аргумент. В лаборатории callback прочитал заголовок из response.request.headers, а page из response.meta.
Прокси по-прежнему задаётся в meta:
async def start(self):
yield scrapy.Request(
"https://example.com/",
meta={"proxy": "http://user:pass@proxy.example.com:8080"},
)В лаборатории запрос к адресу http:// пришёл на локальный прокси с Basic-авторизацией с верными учётными данными. Руководство Прокси в Scrapy 2.19 описывает ротацию и переменные окружения прокси, а Ошибка прокси 407 разбирает случай, когда прокси отклоняет учётные данные.
Поддержка Scrapy ниже 2.13 и 2.16+: оба метода
Код, который должен работать по обе стороны от этого изменения, определяет оба метода. В примечаниях к версии сказано так: «Use start() instead, or both to maintain support for lower Scrapy versions.»
async def start(self):
for request in self.start_requests():
yield request
def start_requests(self):
yield scrapy.Request("https://example.com/", callback=self.parse)Scrapy 2.12.0 вызвал start_requests(), а 2.15.2, 2.16.0 и 2.19.0 вызвали start(). Каждая версия обошла страницу, и 2.15.2 не вывел предупреждения об устаревании. Обратная ошибка так же незаметна: паук только с async def start() не отправил ни одного запроса на 2.12.0 и закрылся как finished.
Spider middleware: process_start_requests() пропускается без предупреждения
В том же выпуске удалена поддержка process_start_requests() в spider middleware. Middleware из лаборатории выставлял meta["tagged"] = True на каждом стартовом запросе. На 2.19.0 с уже переведённым пауком страница была обойдена, callback вывел tagged=None, а предупреждений в журнале не было: метод ни разу не вызывался. Scrapy 2.15.2 останавливал ту же пару с ValueError, так что обновление превратило ошибку в тишину.
Замените его на process_start():
class TagStartMiddleware:
async def process_start(self, start):
async for item_or_request in start:
if isinstance(item_or_request, scrapy.Request):
item_or_request.meta["tagged"] = True
yield item_or_requestС ним callback вывел tagged=True на 2.15.2, 2.16.0 и 2.19.0. Чтобы поддерживать и Scrapy ниже 2.13, оставьте рядом process_start_requests(). Когда оба метода были и в middleware, и в пауке, запрос был помечен на всех четырёх версиях.
TypeError: Middleware doesn't support asynchronous spider output
Синхронный process_spider_output() отказывает громко. Обход останавливается до строки Spider opened, код выхода 1:
TypeError: Middleware d2_mw_sync_output.StampMiddleware doesn't support asynchronous spider output. Its process_spider_output() method should be an async generator function or it should additionally define a process_spider_output_async() method.d2_mw_sync_output.StampMiddleware — это middleware из лаборатории, на его месте будет имя вашего. Scrapy 2.15.2 запускал тот же middleware с предупреждением, что он «will stop working in a future version of Scrapy». Сделайте метод асинхронным генератором:
class StampMiddleware:
async def process_spider_output(self, response, result):
async for item_or_request in result:
if isinstance(item_or_request, dict):
item_or_request["stamped"] = True
yield item_or_requestВ сигнатуре нет параметра spider. С ним 2.19.0 всё ещё запускал middleware и выводил ScrapyDeprecationWarning: StampMiddleware.process_spider_output() requires a spider argument. Middleware, в котором уже есть и синхронный process_spider_output(), и process_spider_output_async(), загрузился без ошибки, и 2.19.0 выполнил асинхронный метод. Если класс из сообщения относится к установленному пакету, поищите выпуск этого пакета, который поддерживает вашу версию Scrapy.
Как узнать, какая версия Scrapy у вас запущена
scrapy version
python -m pip show scrapyВ лаборатории первая команда вывела Scrapy 2.19.0, а вторая Version: 2.19.0. Версию называет и первая строка журнала обхода: Scrapy 2.19.0 started (bot: scrapybot). Выполняйте команды в том окружении, где идёт обход: в виртуальном окружении, образе контейнера или задаче CI.
Строка requirements.txt, в которой написано только scrapy, при следующей сборке установит самый новый выпуск. Если мигрировать сегодня нельзя, закрепите последнюю ветку выпусков, которая ещё вызывает start_requests():
scrapy>=2.15,<2.16В лаборатории это дало 2.15.2: он обошёл страницу неизменённым пауком и вывел ScrapyDeprecationWarning, в котором назван класс паука и сказано, что метод «will stop being called in a future version of Scrapy». Считайте такое закрепление временным: в примечаниях к версии 2.17.0 перечислены исправления уязвимостей. После миграции закрепите ту версию, которую вы проверили. На Python 3.15 без компилятора C команда pip install --only-binary brotli scrapy молча установила Scrapy 2.17.0 вместо 2.19.0; как это заметить, показано в статье Python 3.15 для веб-скрапинга.
Как поймать обход без запросов в CI
При коде выхода 0 и finish_reason, равном finished, задача, которая проверяет только успешность обхода, проходит. Проверяйте число запросов:
from scrapy.crawler import CrawlerProcess
from myproject.spiders.quotes import QuotesSpider
def test_spider_sends_requests():
process = CrawlerProcess(settings={"LOG_LEVEL": "WARNING"})
crawler = process.create_crawler(QuotesSpider)
process.crawl(crawler)
process.start()
assert crawler.stats.get_value("downloader/request_count", 0) > 0На 2.16.0 и 2.19.0 тест упал для паука со start_requests() с сообщением AssertionError: assert 0 > 0 и прошёл после переименования. На 2.15.2 он прошёл для обоих. В лаборатории каждый процесс pytest выполнял один обход локального python -m http.server, поэтому направьте свой тест на тестовый стенд или на страницу, которую вам разрешено запрашивать.
Для задачи по расписанию возьмите то же число из статистики в конце журнала. В запуске, который отправлял запросы, есть запись 'downloader/request_count', а в неудачном её не было.
Как это проверялось
Ubuntu 26.04.1, Python 3.14.4, всё на 127.0.0.1: python -m http.server в роли цели и прямой прокси с Basic-авторизацией. В четырёх виртуальных окружениях стояли Scrapy 2.19.0 и 2.16.0 с Twisted 26.4.0, 2.15.2 с Twisted 25.5.0 и 2.12.0 с Twisted 24.11.0. Каждый из 17 пауков запускался на каждой версии командой scrapy runspider в новом интерпретаторе с чистым окружением: 68 запусков и ещё 8 запусков pytest. Аккаунт прокси не использовался.
Архив лаборатории содержит скрипт запуска, пауков, проверку для pytest, README с таблицей всех результатов и записанные результаты.
Не проверялось: macOS и Windows, Scrapy 2.13, 2.14, 2.17 и 2.18, правила CrawlSpider и сторонние middleware.
Когда паук снова отправляет запросы, каждый из них расходует трафик прокси-тарифа. В статье Сколько ГБ прокси вам нужно измерено, сколько тратит загрузка одного HTML.
ipvolt — прокси-сервис для разработчиков, который пока не открыт; запишитесь в список раннего доступа, и вы получите одно письмо, когда он откроется.
Источники и дополнительное чтение
Технические материалы, использованные при подготовке руководства. Сверяйтесь с документацией вашей версии и с поддерживаемой конфигурацией вашего провайдера.
- Scrapy release notes: 2.16.0, deprecation removals
- Scrapy documentation: Spider.start()
- Scrapy documentation: SpiderMiddleware.process_start()
- Scrapy documentation: SpiderMiddleware.process_spider_output()
- scrapy/scrapy pull request #7490: Drop start_requests() support
- scrapy/scrapy pull request #7504: Remove support for sync process_spider_output()
- Scrapy release notes: 2.17.0, security bug fixes