Tras actualizar a Scrapy 2.16 o posterior, un spider que solo define start_requests() se abre, no envía ninguna petición y se cierra como si hubiera terminado:
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)Son las líneas del motor y de logstats de una ejecución con Scrapy 2.19.0. El registro no tenía ninguna línea WARNING ni ERROR, el código de salida fue 0 y parse() no llegó a ejecutarse. El volcado de estadísticas tenía 'finish_reason': 'finished' y ninguna clave downloader/request_count.
La causa: Scrapy 2.16.0 (19 de mayo de 2026) eliminó Spider.start_requests(), obsoleto desde la 2.13.0. Las notas de la versión dicen que «is removed and no longer called» (pull request #7490). La solución es renombrar el método a 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)Con ese único cambio, el mismo spider rastreó su página en Scrapy 2.16.0 y 2.19.0.
| Lo que ves en 2.16+ | Causa | Solución |
|---|---|---|
Crawled 0 pages y después Spider closed (finished), sin ningún aviso | El spider solo define start_requests() | Renómbralo a async def start() |
| El rastreo funciona, pero un middleware ya no modifica las primeras peticiones | Un spider middleware solo define process_start_requests() | Añade async def process_start() |
TypeError: Middleware ... doesn't support asynchronous spider output, código de salida 1 | Un spider middleware tiene un process_spider_output() síncrono | Conviértelo en un generador asíncrono |
Todo lo que sigue se registró el 9 de octubre de 2026 con Scrapy 2.19.0, Twisted 26.4.0 y Python 3.14.4, y se repitió en Scrapy 2.16.0, 2.15.2 y 2.12.0. Un spider que solo asigna start_urls y no define ninguno de los dos métodos no se ve afectado: rastreó en las cuatro versiones.
Migra start_requests() a async def start()
Spider.start() es un generador asíncrono: async def con yield. Las peticiones que produces no cambian. Dos descuidos dan otro síntoma, una línea ERROR y de nuevo ninguna petición:
TypeError: e1_start_not_async.LabSpider.start() must be an asynchronous generator, i.e. an async def method with yield statements, got <class 'generator'>Eso es def start() sin async. Un async def start() que devuelve una lista termina con got <class 'coroutine'>. En ambos casos Scrapy 2.19.0 cerró el spider con 'finish_reason': 'start_error' y el código de salida siguió siendo 0.
Si el spider sobrescribía start_requests() para añadir cabeceras o meta a las URL de start_urls, conserva el bucle:
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,
)El start() predeterminado es el mismo bucle con dont_filter=True y nada más, así que mantén ese argumento. En el laboratorio el callback leyó la cabecera en response.request.headers y page en response.meta.
El proxy va en meta, igual que antes:
async def start(self):
yield scrapy.Request(
"https://example.com/",
meta={"proxy": "http://user:pass@proxy.example.com:8080"},
)En el laboratorio la petición http:// llegó a un proxy local que exige autenticación Basic, con credenciales válidas. Proxies en Scrapy 2.19 cubre la rotación y las variables de entorno de proxy, y Corrige el error de proxy 407 el caso de un proxy que rechaza las credenciales.
Compatibilidad con Scrapy anterior a 2.13 y con 2.16+: define ambos métodos
El código que tiene que funcionar a ambos lados del cambio define los dos métodos. Las notas de la versión lo expresan así: «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 llamó a start_requests(), y 2.15.2, 2.16.0 y 2.19.0 llamaron a start(). Todas rastrearon la página, y 2.15.2 no registró ningún aviso de obsolescencia. El error contrario es igual de silencioso: un spider que solo tiene async def start() no envió ninguna petición en 2.12.0 y se cerró como finished.
Spider middlewares: process_start_requests() se omite sin avisar
La misma versión eliminó la compatibilidad con process_start_requests() en los spider middlewares. El middleware del laboratorio ponía meta["tagged"] = True en cada petición inicial. En 2.19.0, con un spider ya migrado, la página se rastreó, el callback registró tagged=None y el registro no tenía ningún aviso: el método no se llamó nunca. Scrapy 2.15.2 detenía esa misma pareja con un ValueError, así que la actualización convirtió un error en silencio.
Sustitúyelo por 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_requestCon él, el callback registró tagged=True en 2.15.2, 2.16.0 y 2.19.0. Para admitir también Scrapy anterior a 2.13, deja process_start_requests() junto a él. Con los dos métodos en el middleware y los dos en el spider, la petición quedó marcada en las cuatro versiones.
TypeError: Middleware doesn't support asynchronous spider output
Un process_spider_output() síncrono falla de forma visible. El rastreo se detiene antes de Spider opened, con código de salida 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 es el middleware del laboratorio; en su lugar aparece el tuyo. Scrapy 2.15.2 ejecutaba el mismo middleware con un aviso de que «will stop working in a future version of Scrapy». Convierte el método en un generador asíncrono:
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_requestLa firma no tiene el parámetro spider. Con él, 2.19.0 siguió ejecutando el middleware y registró ScrapyDeprecationWarning: StampMiddleware.process_spider_output() requires a spider argument. Un middleware que ya tiene un process_spider_output() síncrono y un process_spider_output_async() se cargó sin el error, y 2.19.0 ejecutó el asíncrono. Si la clase del mensaje pertenece a un paquete instalado, busca una versión de ese paquete compatible con tu versión de Scrapy.
Comprueba qué versión de Scrapy estás ejecutando
scrapy version
python -m pip show scrapyEn el laboratorio el primero imprimió Scrapy 2.19.0 y el segundo Version: 2.19.0. La primera línea del registro del rastreo también la indica: Scrapy 2.19.0 started (bot: scrapybot). Ejecuta los comandos en el entorno donde corre el rastreo, sea el entorno virtual, la imagen del contenedor o el trabajo de CI.
Una línea de requirements.txt que solo dice scrapy instala la versión más reciente en la siguiente compilación. Si no puedes migrar hoy, fija la última serie que todavía llama a start_requests():
scrapy>=2.15,<2.16En el laboratorio eso se resolvió a 2.15.2, que rastreó con el spider sin cambios y registró un ScrapyDeprecationWarning que nombra la clase del spider y dice que el método «will stop being called in a future version of Scrapy». Trata esa restricción como temporal: las notas de la versión enumeran correcciones de seguridad en la 2.17.0. Después de migrar, fija la versión que hayas probado. En Python 3.15 sin compilador de C, pip install --only-binary brotli scrapy instaló en silencio Scrapy 2.17.0 en lugar de 2.19.0; Python 3.15 para web scraping explica cómo detectarlo.
Detecta en CI un rastreo sin peticiones
Con código de salida 0 y finish_reason igual a finished, un trabajo que solo comprueba si el rastreo terminó bien pasa. Comprueba el número de peticiones:
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) > 0En 2.16.0 y 2.19.0 falló para el spider con start_requests() con AssertionError: assert 0 > 0 y pasó tras el cambio de nombre. En 2.15.2 pasó para los dos. El laboratorio ejecutó un rastreo por proceso de pytest contra un python -m http.server local, así que apunta el tuyo a un servidor de pruebas o a una página que tengas permiso para solicitar.
Para un trabajo programado, lee el mismo número en el volcado de estadísticas al final del registro. Una ejecución que envió peticiones tiene una entrada 'downloader/request_count', y la ejecución fallida no tenía ninguna.
Cómo se probó
En Ubuntu 26.04.1 con Python 3.14.4, todo en 127.0.0.1: python -m http.server como destino y un proxy de reenvío que exige autenticación Basic. Cuatro entornos virtuales tenían Scrapy 2.19.0 y 2.16.0 con Twisted 26.4.0, 2.15.2 con Twisted 25.5.0 y 2.12.0 con Twisted 24.11.0. Cada uno de los 17 spiders se ejecutó en cada versión con scrapy runspider en un intérprete nuevo y con un entorno limpio: 68 ejecuciones, más 8 de pytest. No se usó ninguna cuenta de proxy.
El archivo del laboratorio contiene el ejecutor, los spiders, la comprobación de pytest, el README con una tabla de todos los resultados y los resultados registrados.
No se probó: macOS y Windows, Scrapy 2.13, 2.14, 2.17 y 2.18, las reglas de CrawlSpider ni middlewares de terceros.
Cuando el spider vuelve a enviar peticiones, cada una cuenta para el plan de proxies. ¿Cuántos GB de proxies necesitas? mide lo que consume una descarga de solo HTML.
ipvolt es un servicio de proxies para desarrolladores que todavía no está abierto; únete a la lista de acceso anticipado y recibirás un solo correo cuando abra.
Fuentes y lecturas adicionales
Referencias técnicas usadas para esta guía. Consulta la documentación de tu versión instalada y la configuración compatible de tu proveedor.
- 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