# Scrapy no llama a start_requests(): Crawled 0 pages

Source: https://ipvolt.com/es/guides/scrapy-start-requests-not-called
Markdown: https://ipvolt.com/es/guides/scrapy-start-requests-not-called.md
Language: es

[Inicio de ipvolt](https://ipvolt.com/es.md) / [Guías](https://ipvolt.com/es/guides.md) / Scrapy no llama a start_requests(): Crawled 0 pages

Solución de problemas
Publicado: 2026-10-10
6 min de lectura
Por ipvolt

Scrapy 2.16 eliminó Spider.start_requests(): el spider registra Crawled 0 pages y se cierra como finished sin avisos. Solución: async def start(). Probado en 2.19.

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:

```text
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](https://docs.scrapy.org/en/latest/news.html#release-2-16-0) dicen que «is removed and no longer called» ([pull request #7490](https://github.com/scrapy/scrapy/pull/7490)). La solución es renombrar el método a `async def start()`:

```python
# Before: Scrapy 2.16 and later never call this
def start_requests(self):
    yield scrapy.Request("https://example.com/", callback=self.parse)
```

```python
# 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()`](https://docs.scrapy.org/en/latest/topics/spiders.html#scrapy.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:

```text
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:

```python
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:

```python
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](/es/guides/scrapy-proxy-setup) cubre la rotación y las variables de entorno de proxy, y [Corrige el error de proxy 407](/es/guides/fix-proxy-error-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.»

```python
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()`](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#scrapy.spidermiddlewares.SpiderMiddleware.process_start):

```python
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
```

Con é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:

```text
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:

```python
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
```

La 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

```sh
scrapy version
python -m pip show scrapy
```

En 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()`:

```text
scrapy>=2.15,<2.16
```

En 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](https://docs.scrapy.org/en/latest/news.html#release-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](/es/blog/python-3-15-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:

```python
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
```

En 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](https://ipvolt.com/downloads/scrapy-start-requests-not-called/scrapy-start-requests-lab.zip) contiene el [ejecutor](https://ipvolt.com/downloads/scrapy-start-requests-not-called/run_lab.py), los spiders, la [comprobación de pytest](https://ipvolt.com/downloads/scrapy-start-requests-not-called/ci/test_crawl.py), el [README](https://ipvolt.com/downloads/scrapy-start-requests-not-called/README.md) con una tabla de todos los resultados y los [resultados registrados](https://ipvolt.com/downloads/scrapy-start-requests-not-called/results.json).

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?](/es/blog/how-many-gb-of-proxies-do-i-need) 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](https://ipvolt.com/#waitlist-closing) y recibirás un solo correo cuando abra.

## Fuentes y lecturas adicionales

- [Scrapy release notes: 2.16.0, deprecation removals](https://docs.scrapy.org/en/latest/news.html#release-2-16-0)
- [Scrapy documentation: Spider.start()](https://docs.scrapy.org/en/latest/topics/spiders.html#scrapy.Spider.start)
- [Scrapy documentation: SpiderMiddleware.process_start()](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#scrapy.spidermiddlewares.SpiderMiddleware.process_start)
- [Scrapy documentation: SpiderMiddleware.process_spider_output()](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#scrapy.spidermiddlewares.SpiderMiddleware.process_spider_output)
- [scrapy/scrapy pull request #7490: Drop start_requests() support](https://github.com/scrapy/scrapy/pull/7490)
- [scrapy/scrapy pull request #7504: Remove support for sync process_spider_output()](https://github.com/scrapy/scrapy/pull/7504)
- [Scrapy release notes: 2.17.0, security bug fixes](https://docs.scrapy.org/en/latest/news.html#release-2-17-0)

## Guías relacionadas

- [Proxies en Scrapy 2.19: autenticación, rotación, errores](https://ipvolt.com/es/guides/scrapy-proxy-setup.md)
- [Corrige el error de proxy 407 sin adivinar](https://ipvolt.com/es/guides/fix-proxy-error-407.md)
- [Variables de entorno de proxy: HTTP_PROXY y NO_PROXY](https://ipvolt.com/es/guides/proxy-environment-variables.md)

## Sobre ipvolt

Los ejemplos usan configuraciones de proxy genéricas, con enlaces a la documentación técnica original. El comportamiento específico de cada producto debe comprobarse con tu proveedor. ipvolt sigue en desarrollo.

[Leer el original en inglés](https://ipvolt.com/guides/scrapy-start-requests-not-called.md)

## Entérate cuando se abra el acceso.

ipvolt · En desarrollo

Estamos construyendo infraestructura de proxies para desarrolladores y equipos de datos. Únete a la lista de interés para recibir un aviso cuando ipvolt esté listo.

Un correo cuando se abra el acceso. Nada más.

[Solicitar acceso anticipado](https://ipvolt.com/es/guides/scrapy-start-requests-not-called#waitlist-closing)

[Privacidad](https://ipvolt.com/privacy)

