> ## Documentation Index
> Fetch the complete documentation index at: https://www.octoparse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Cómo gestionar la paginación

> Aprende cómo un scraper supera la primera página: páginas numeradas, enlaces Siguiente, desplazamiento infinito, botones, offsets y cursores de API.

La Paginación es la capa de navegación de un scraper. Después de que el scraper pueda obtener, renderizar y extraer una página, todavía debe responder a una pregunta práctica: **¿dónde está el siguiente lote de registros y cómo sé que ya no quedan más?**

La mayoría de los fallos de Paginación se producen al tratar todos los sitios como si fueran listas de páginas numeradas. En la práctica, un catálogo puede usar parámetros de URL, un botón Siguiente, desplazamiento infinito, un botón Cargar más, un offset de API o un token de cursor opaco. Algunos sitios combinan varios de estos patrones.

## Empezar por la solicitud, no por la interfaz

Antes de escribir la lógica de Paginación, abre DevTools y observa qué cambia al pasar al siguiente lote.

1. Abre la pestaña Network y filtra Fetch/XHR.
2. Avanza, desplázate o pulsa Cargar más.
3. Inspecciona la URL, los parámetros, el cuerpo y la respuesta.
4. Decide si conviene seguir enlaces, interactuar con la página o llamar directamente a una API.

Usa la interfaz como pista, pero confía en la solicitud de red. Un botón que dice «Cargar más» puede llamar a una API sencilla con `offset=40`. Un enlace de página puede cargar los resultados mediante JavaScript después de cambiar la URL.

| Qué cambia                                                         | Qué probar primero                                              |
| ------------------------------------------------------------------ | --------------------------------------------------------------- |
| La URL contiene `page=2`, `p=2` o `/page/2`                        | Recorrer URL numeradas                                          |
| Un enlace `<a>` apunta a la página siguiente                       | Seguir `href` hasta que desaparezca o se desactive              |
| Aparece contenido al desplazarse                                   | Buscar la solicitud XHR; usar el navegador solo si es necesario |
| Aparece contenido al pulsar un botón                               | Reutilizar la API o pulsar el botón en una sesión de navegador  |
| El JSON contiene `next_cursor`, `endCursor`, `has_more` u `offset` | Paginar mediante la respuesta de la API                         |

## Páginas numeradas

La Paginación numerada es el caso más sencillo porque la siguiente ubicación aparece en la URL:

```text theme={null}
https://example.com/products?page=1
https://example.com/products?page=2
https://example.com/catalog/page/3
```

El scraper puede incrementar el número de página y detenerse cuando la respuesta no contenga elementos, contenga menos de los esperados o devuelva una página 404 o de estado vacío conocida.

```python theme={null}
import requests
from bs4 import BeautifulSoup

all_products = []

for page in range(1, 100):
    html = requests.get(f"https://example.com/products?page={page}").text
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select(".product-card")

    if not cards:
        break

    for card in cards:
        all_products.append(card.select_one(".title").get_text(strip=True))
```

Presta atención a los índices de página que comienzan en `0`, los nombres de parámetro como `p` o `start` y los sitios que vuelven a mostrar la primera página cuando el número solicitado está fuera del intervalo. La repetición de la primera página es peor que una página vacía, porque puede crear datos duplicados sin errores evidentes.

## Enlaces Siguiente

Algunos sitios no muestran números de página, sino únicamente un enlace o una flecha «Siguiente». Si el elemento es un ancla normal, trata la Paginación como un proceso de seguimiento de enlaces:

```python theme={null}
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup

url = "https://example.com/products"
seen_urls = set()

while url and url not in seen_urls:
    seen_urls.add(url)
    soup = BeautifulSoup(requests.get(url).text, "html.parser")

    for card in soup.select(".product-card"):
        print(card.select_one(".title").get_text(strip=True))

    next_link = soup.select_one("a[rel='next'], a.next")
    url = urljoin(url, next_link["href"]) if next_link and next_link.get("href") else None
```

La protección `seen_urls` es importante. Algunos sitios mal configurados hacen que el último enlace «Siguiente» vuelva a la página actual o a la primera. Antes de confiar en el enlace, comprueba también estados desactivados como `aria-disabled="true"`, `disabled` o una clase `disabled`.

## Desplazamiento infinito

El desplazamiento infinito parece un problema exclusivo del navegador, pero suele tener una API subyacente. Desplázate una vez con DevTools abierto y busca una solicitud que obtenga el siguiente grupo de registros. Los parámetros útiles suelen llamarse `offset`, `page`, `after`, `cursor` o `limit`.

Cuando sea posible utilizar el endpoint, llámalo directamente:

```python theme={null}
import requests

offset = 0
limit = 24
products = []

while True:
    data = requests.get(
        "https://example.com/api/products",
        params={"offset": offset, "limit": limit},
    ).json()

    batch = data.get("items", [])
    if not batch:
        break

    products.extend(batch)
    offset += len(batch)
```

Usa un navegador únicamente cuando resulte difícil llamar a la API desde fuera de la página debido a la autenticación, los parámetros firmados o un estado complejo del lado del cliente.

```python theme={null}
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com/products")

    previous_count = 0
    for _ in range(40):
        page.mouse.wheel(0, 4000)
        page.wait_for_timeout(1500)

        current_count = page.locator(".product-card").count()
        if current_count == previous_count:
            break
        previous_count = current_count

    print(page.locator(".product-card").count())
    browser.close()
```

En el desplazamiento infinito, no confíes únicamente en la altura de la página. Algunos diseños siguen cambiando de altura debido a anuncios, imágenes o listas virtualizadas. Una combinación del número de elementos, la inactividad de red y un número máximo de desplazamientos es más segura.

## Botones Cargar más

Un botón Cargar más es una forma controlada de desplazamiento infinito. La página espera a que se haga clic antes de solicitar el siguiente lote. Esto facilita el control del ritmo, porque el scraper puede esperar, validar el nuevo número de elementos y reintentar si la solicitud falla.

Si el botón llama a una API sencilla, usa esa API. De lo contrario, haz clic en el botón dentro de un bucle del navegador:

```python theme={null}
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com/products")

    while page.locator("button.load-more").is_visible():
        before = page.locator(".product-card").count()
        page.locator("button.load-more").click()
        page.wait_for_function(
            "(count) => document.querySelectorAll('.product-card').length > count",
            before,
        )

    browser.close()
```

La comprobación importante no es solo «se ha hecho clic en el botón», sino «han aparecido registros nuevos». Los botones pueden fallar silenciosamente, quedar desactivados o seguir visibles después del último lote.

## API con offset y cursor

Los sitios modernos suelen Paginar los datos en la capa de API. La Paginación por offset solicita una posición numérica:

```text theme={null}
/api/products?offset=40&limit=20
```

La Paginación por cursor solicita el siguiente token opaco devuelto por la respuesta anterior:

```json theme={null}
{
  "items": [],
  "pageInfo": {
    "hasNextPage": true,
    "endCursor": "eyJpZCI6MTAwfQ=="
  }
}
```

La Paginación por cursor es más estable cuando se agregan o eliminan registros durante la extracción. En lugar de indicar «omite las primeras 40 filas», el cursor indica «continúa después de esta posición conocida».

```python theme={null}
import requests

cursor = None

while True:
    params = {"limit": 50}
    if cursor:
        params["after"] = cursor

    data = requests.get("https://example.com/api/products", params=params).json()
    for item in data.get("items", []):
        print(item["name"])

    page_info = data.get("pageInfo", {})
    if not page_info.get("hasNextPage"):
        break

    cursor = page_info.get("endCursor")
```

En la Paginación mediante API, gestiona deliberadamente los límites de frecuencia. Respeta `Retry-After`, reintenta los fallos temporales con espera incremental y guarda el progreso si el trabajo es lo bastante grande como para que reiniciarlo desde la primera página resulte costoso.

## Paginación híbrida

Los sitios reales suelen combinar varios patrones:

* Una categoría tiene páginas numeradas, pero cada página carga más productos de forma diferida después de desplazarse.
* Una página de búsqueda empieza con un botón Cargar más y después cambia a enlaces numerados.
* Una interfaz con pestañas tiene una Paginación independiente para «Novedades», «Popular» y «Ofertas».
* Una página de listado Pagina las URL de resultados y cada página de detalles tiene sus propias reseñas o comentarios Paginados.

Gestiona estos casos como bucles anidados. Haz que el bucle exterior se ocupe de la unidad de navegación mayor y que cada bucle interior se encargue de una única acción repetida. Registra identificadores únicos durante toda la ejecución para impedir que los registros duplicados lleguen a la salida.

## Medidas prácticas

* **Define una señal de parada.** Los conjuntos de resultados vacíos, los enlaces Siguiente ausentes, los botones desactivados, `hasNextPage: false`, los cursores repetidos y los límites máximos de iteraciones son señales de parada válidas.
* **Detecta duplicados.** El desplazamiento infinito y las API con cursor pueden repetir registros cuando los datos cambian durante la ejecución. Guarda identificadores estables o URL canónicas.
* **Modera la navegación.** Agrega pequeñas pausas aleatorias entre lotes. La Automatización del navegador debe esperar a que cambie el contenido, no solo usar tiempos de espera fijos.
* **Registra los fallos.** Si una página falla después de varios reintentos, registra la URL o el cursor y continúa cuando sea posible.
* **Prefiere las API cuando sean legítimas y estables.** La Paginación directa mediante API suele ser más rápida y fácil de validar que controlar un navegador.
* **Usa una herramienta visual cuando la rapidez importe más que el código personalizado.** En Octoparse, puedes configurar visualmente la Paginación habitual mediante Siguiente, Cargar más y desplazamiento infinito, y después ejecutarla localmente o en la nube.

La Paginación no consiste simplemente en «ir a la página siguiente». Es el bucle de control del scraper. Cuando ese bucle tiene una lógica clara para el siguiente paso, una condición de parada fiable y protección contra duplicados, el scraper puede recorrer un sitio sin detenerse silenciosamente en la primera página ni girar indefinidamente.
