Python Paginación: Cómo Colectar Datos a Través de Páginas Paginadas
TL;DR
La paginación es un contrato entre tu cliente y la fuente de datos. Inspecciona la respuesta en busca de un número de página, desplazamiento, cursor o enlace siguiente antes de escribir el bucle.
Para las APIs de límite/desplazamiento, avanza por el número de registros realmente devueltos y detente en el total informado o en una página vacía. Añade una protección de página máxima y deduplica por un ID estable.
Las APIs de cursor son opacas por diseño. Envía el exacto cursor siguiente devuelto por el servidor; no fabriques números de página a partir de él.
Este tutorial utiliza DummyJSON como API de demostración. Es útil para aprender la mecánica de paginación, no como un conjunto de datos empresariales de producción o contrato de disponibilidad garantizada.
Lo que significa la paginación en Python para la recopilación de datos
La paginación en Python es la lógica que solicita un gran conjunto de resultados en páginas más pequeñas. Las APIs y sitios web utilizan la paginación porque devolver todos los registros en una sola respuesta puede ser lento, pesado en memoria o exceder los límites del servidor.
Un scraper que solo obtiene la primera respuesta puede parecer saludable mientras silenciosamente se pierde la mayor parte del conjunto de datos. El bucle correcto depende del estilo de paginación de la fuente: números de página, desplazamiento y límite, tokens de cursor, una URL de siguiente página, o contenido cargado a medida que el usuario desplaza. Siempre lee la documentación de API del proveedor e inspecciona una respuesta de ejemplo antes de elegir un bucle. Para el descubrimiento de URL a nivel de sitio web y extracción de páginas, Nstdata Crawl es independiente de la paginación de cursor o desplazamiento específica de la aplicación.
Descubre Nstproxy Crawl - Empieza Tu Prueba Gratuita Hoy
Fallo común
Número de página
?page=3
Página vacía, sin campo de siguiente página, o total conocido
Suponer que cada endpoint comienza en la página 1
Desplazamiento + límite
?skip=60&limit=30
Total devuelto o sin registros
Incrementar por el tamaño de página incorrecto
Cursor / token de continuación
?cursor=opaque-value
Sin siguiente cursor
Tratar el cursor como un número o modificarlo
Enlace siguiente HTML
Un ancla etiquetada Como Siguiente
Sin enlace siguiente utilizable
Construyendo la siguiente URL por concatenación de cadenas
Desplazamiento infinito
El navegador obtiene más después de desplazar
Marcador de fin específico del sitio o API documentada agotada
Desplazarse para siempre o llamar a endpoints no documentados sin permiso
Los nombres de los parámetros son ejemplos, no estándares universales de API. Algunas APIs devuelven una URL completa de siguiente; otras devuelven un cursor dentro de un objeto anidado. Usa los nombres y la condición de detención que documenten la fuente.
Construir un script de paginación límite/desplazamiento en Python
La documentación del endpoint de productos de DummyJSON define los parámetros limit y skip y devuelve el total de resultados. El ejemplo a continuación solicita páginas limitadas, valida la forma de la respuesta, deduplica por ID de producto y escribe en CSV.
import csv
import time
from typing import Any
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
API_URL ="https://dummyjson.com/products"PAGE_SIZE =30MAX_PAGES =100defmake_session()-> requests.Session: retry = Retry( total=3, connect=3, read=2, status=3, backoff_factor=0.5, status_forcelist=(429,500,502,503,504), allowed_methods=frozenset({"GET"}), respect_retry_after_header=True,) session = requests.Session() session.mount("https://", HTTPAdapter(max_retries=retry)) session.headers.update({"User-Agent":"pagination-demo/1.0"})return session
deffetch_all_products()->list[dict[str, Any]]: session = make_session() products:list[dict[str, Any]]=[] seen_ids:set[int]=set() skip =0 total:int|None=Nonefor page_number inrange(1, MAX_PAGES +1): response = session.get( API_URL, params={"limit": PAGE_SIZE,"skip": skip}, timeout=(5,20),) response.raise_for_status() payload = response.json() batch = payload.get("products") page_total = payload.get("total")ifnotisinstance(batch,list)ornotisinstance(page_total,int):raise ValueError("Respuesta inesperada de la API: se esperaban products[] y total entero") total = page_total
ifnot batch:breakfor item in batch: item_id = item.get("id")ifnotisinstance(item_id,int):continueif item_id notin seen_ids: seen_ids.add(item_id) products.append(item)# Avanza por el número devuelto, no por el límite solicitado. skip +=len(batch)if skip >= total:break time.sleep(0.1)else:raise RuntimeError(f"Detenido en el límite de seguridad de {MAX_PAGES} páginas")if total isnotNoneandlen(products)< total:print(f"Advertencia: recibidos {len(products)} productos únicos; la API informó {total}")return products
defmain()->None: products = fetch_all_products() fields =["id","title","category","price"]withopen("products.csv","w", newline="", encoding="utf-8")as output: writer = csv.DictWriter(output, fieldnames=fields, extrasaction="ignore") writer.writeheader() writer.writerows(products)print(f"Guardados {len(products)} filas únicas en products.csv")if __name__ =="__main__": main()
Este ejemplo utiliza una política de reintentos para fallos transitorios de HTTP y respeta Retry-After cuando el servidor lo proporciona. No reintenta cada error: un 400 o 401 generalmente requiere corregir la solicitud o las credenciales, no repetirla.
Por qué el bucle avanza por el conteo devuelto
La API puede devolver menos registros de los solicitados al final de la colección o cuando impone un tamaño máximo de página. Actualizar skip por len(batch) mantiene la próxima solicitud alineada con la respuesta. El bucle termina cuando el desplazamiento alcanza total, o antes si la API devuelve un batch vacío.
El guardia de la página máxima protege contra un campo total dañado o un endpoint que sigue devolviendo la misma página. Si aparece la advertencia, compara ID y desplazamientos en los logs guardados antes de aumentar el límite. Los ID duplicados pueden significar que la fuente cambió durante la colección o que los parámetros de paginación no se aplicaron como se esperaba.
Cómo manejar las APIs de número de página
Para una API de número de página, comienza en la página definida por su documentación, luego detente en la condición terminal documentada de la fuente. Si la respuesta incluye un conteo total de páginas, úsalo. Si incluye una URL de siguiente página, sigue esa URL. Si la API devuelve una última página corta pero sin total, detente cuando el batch esté vacío o sea más corto que el tamaño solicitado solo si esa regla está documentada.
Evita suposiciones como "la página 1 siempre existe" o "la última página es la primera página vacía". Algunas APIs devuelven una bandera explícita has_more. Prefiere eso sobre inferir la finalización a partir del número de filas.
Cómo manejar la paginación basada en cursor
Las APIs basadas en cursor a menudo devuelven datos así:
Los nombres de los campos anteriores son ilustrativos. En una integración real, copia el campo de respuesta exacto y el parámetro de solicitud de la documentación de la API. El bucle es conceptualmente:
Haz la solicitud inicial sin un cursor.
Procesa y persiste los elementos devueltos.
Lee el siguiente cursor de la respuesta.
Detente si el proveedor no devuelve un siguiente cursor.
De lo contrario, envía ese cursor sin cambios con la siguiente solicitud.
Persiste el cursor junto con el estado del trabajo si necesitas reanudar después de un fallo. Haz que las escrituras de registro sean idempotentes para que una página reintentada después de un tiempo de espera no duplique las filas posteriores.
Cómo seguir enlaces de paginación HTML
Cuando la fuente es un sitio HTML permitido en lugar de una API, utiliza Requests para obtener la página, analizarla con Beautiful Soup, y resolver URLs relativas en relación con la URL de respuesta actual. No anexes un número de página a menos que el esquema de URL del sitio web sea estable y esté documentado.
from urllib.parse import urljoin
from bs4 import BeautifulSoup
import requests
defget_next_url(page_url:str)->str|None: response = requests.get(page_url, timeout=(5,20)) response.raise_for_status() soup = BeautifulSoup(response.text,"html.parser") link = soup.select_one("a.next")if link isNone:returnNone href = link.get("href")return urljoin(response.url, href)if href elseNone
La documentación oficial de Beautiful Soup explica cómo funcionan los selectores y los atributos analizados. Agrega un conjunto de URL vistas y un límite de página máxima alrededor de este auxiliar. Si el siguiente enlace apunta fuera de tu ámbito de dominio aprobado, detente y revísalo en lugar de seguirlo automáticamente.
Desplazamiento infinito y páginas paginadas por JavaScript
El desplazamiento infinito es un comportamiento de presentación, no un protocolo de paginación universal. Primero verifica si el sitio documenta una API o expone un siguiente cursor permitido en su propia interfaz. Si posees la aplicación, utiliza el mismo contrato de API que el frontend. Si no, obtén permiso antes de automatizar el navegador o llamar a un endpoint que no está destinado para tu uso.
Para una página autorizada que requiere JavaScript, Playwright para Python puede esperar un elemento conocido y realizar un número limitado de acciones de desplazamiento. Define un conteo máximo de elementos o un marcador final estable; un bucle que se desplaza hasta que "nada cambia" puede quedar colgado en una página con contenido periódico o un error de red transitorio.
Evitar Registros Perdidos y Bucles de Paginación
Un trabajo de recolección confiable necesita más que una condición de bucle:
Identidad estable: deduplica registros por un ID o URL canónica.
Seguimiento del progreso: registra el número de página, desplazamiento/cursor, conteo devuelto y conteo acumulativo.
Trabajo limitado: establece el número máximo de páginas, tiempo transcurrido y tasa de solicitud.
Verificación de esquema: verifica que el campo de colección y los metadatos de paginación existan.
Disciplina de reintentos: reintenta timeouts transitorios y errores del servidor con retroceso; detente en errores de autorización o validación.
Verificaciones de consistencia: compara los registros únicos recibidos con el total informado cuando la fuente proporciona uno.
Estado de reanudación: guarda la última página o cursor exitoso si un trabajo puede abarcar reinicios.
La paginación por desplazamiento aún puede omitir o repetir registros cuando el conjunto de datos subyacente cambia durante un largo funcionamiento. Si la fuente admite un token de instantánea o cursor estable, prefierelo para datos mutables. De lo contrario, registra el tiempo de recolección y utiliza un pase de reconciliación posterior.
Cuándo Usar un Marco de Raspado en Lugar de Paginación Manual
Un bucle corto de desplazamiento/límite es a menudo suficiente para una API. Usa un marco como Crawlee para Python cuando necesites colas persistentes, descubrimiento de páginas, múltiples controladores de páginas, reintentos a través de un gran conjunto de URL, o almacenamiento reutilizable. Mantén los conceptos separados: la paginación de API recupera páginas en un punto final de datos; el raspado de sitios descubre y visita URL enlazadas.
Nstdata Crawl está destinado a la recolección a nivel de URL y sitio web, incluido el descubrimiento de páginas/profundidad limitado y salidas de páginas normalizadas. No conoce automáticamente el parámetro de cursor específico de negocio de una aplicación arbitraria. Para el análisis de páginas y contenido dinámico, consulta las guías de Nstdata sobre extracción de texto HTML en Python, páginas renderizadas por JavaScript, y colección de datos automatizada.
La paginación en Python es confiable cuando sigues el contrato real de la fuente, haces que cada página sea limitada y verificas que los IDs y totales recolectados tengan sentido. Comienza con una pequeña ejecución y guarda la respuesta en bruto y el estado de la página. Usa un cursor exactamente como se devuelve, resuelve los enlaces HTML siguientes con urljoin(), y cambia a un marco de raspado solo cuando el flujo de trabajo requiera más que solicitudes API secuenciales.
Mantén el Raspado del Sitio Separado de la Paginación de API
Nstdata Crawl puede ayudar con el descubrimiento de URL de sitio web permitidas y la extracción de páginas. Continúa siguiendo el contrato de página, desplazamiento o cursor de cada API en el código de aplicación.
Lee los campos de paginación de la fuente, bucle utilizando su número de página, desplazamiento, cursor o URL siguiente, y detente en la condición terminal documentada. Agrega un límite de páginas máximas y deduplica registros por una clave estable.
P: ¿Cómo manejo la paginación con solicitudes?
Utiliza una requests.Session, establece timeouts, valida cada respuesta y actualiza el parámetro de paginación a partir del contrato de respuesta. Persiste el progreso si la recolección necesita reanudarse.
P: ¿Cómo raspo desplazamiento infinito?
Para un sitio que posees o al que estás autorizado a acceder, utiliza su API documentada o un flujo de automatización de navegador con una condición de fin clara y un límite de elementos. No automatices alrededor de restricciones de acceso.
P: ¿Cómo detengo un bucle de paginación en Python?
Detente cuando la API diga que no hay más páginas, el cursor está ausente, el siguiente enlace falta, el offset alcanza el total, o la respuesta está vacía—según el comportamiento documentado de esa API. Mantén un límite estricto de páginas como medida de seguridad.
P: ¿Cómo puedo raspar la paginación del cursor?
Envía el cursor opaco devuelto por una respuesta en la siguiente solicitud y detente cuando la API no devuelva ningún siguiente cursor. No incrementes ni decodifiques el cursor a menos que el proveedor documente ese comportamiento.
Ivy Lin
Oct. 19th 2026
Rastrea sitios web completos con una sola solicitud API
99,8% de éxito con renderizado JavaScript
Obtén datos limpios, listos para LLM, en múltiples formatos
Convierte cualquier sitio web en Markdown, HTML, JSON, enlaces, PDF y más, sin gestionar infraestructura de rastreo.