Paginación de Resultados de Búsqueda de Google con Contexto de Solicitud Rastreable
Expert Network Defense Engineer
TL;DR:
- La paginación de la API de búsqueda de Google cambia la porción recopilada. Mantén
starty la solicitud completa con cada respuesta en lugar de tratar las páginas como lotes intercambiables. - Continúa solo dentro de un plan definido y un contrato de respuesta revisado. El ejemplo inspecciona el siguiente enlace devuelto antes de cambiar el desplazamiento y se detiene ante datos de continuación ambiguos.
- Mantén visible el trabajo pendiente y las observaciones duplicadas. Almacena cada respuesta de página en crudo; la deduplicación pertenece a una vista derivada, y el HTTP 201 no es una página vacía.
La paginación puede ocultar un cambio en el experimento. Un recolector puede avanzar el desplazamiento mientras pierde una configuración de país, o combinar una respuesta pendiente con páginas completadas y llamar al resultado completo. La lista de URL resultante no revela cómo esas diferencias ingresaron en la colección.
API de Búsqueda de Google Sin "Scraping" admite desplazamientos de búsqueda a través de start. Esta guía de paginación de la API de búsqueda de Google construye un recolector deliberadamente limitado con un registro para cada solicitud y una razón para detenerse. Demuestra el flujo de control de la aplicación sin prometer acceso a cada resultado o una secuencia de clasificación histórica ininterrumpida.
Requisitos Previos y el Plan de Colección Limitada
El programa utiliza la biblioteca estándar de Python y requiere una clave de API de cuenta en SCRAPELESS_API_KEY para la recopilación real. El nombre de la variable de entorno es una convención de este ejemplo. Mantén la clave fuera de los registros de cuerpo de solicitud y los archivos de captura generados.
Los parámetros de búsqueda de Google describen start como el desplazamiento del resultado, con ejemplos de 0, 10 y 20. El programa limita su plan a esos desplazamientos. Esto es una limitación de la aplicación, no una declaración sobre la profundidad máxima del servicio o una garantía de que cada página contenga un número fijo de elementos orgánicos.
La consulta base es coffee con gl=us, hl=en y entrada de escritorio. Estas configuraciones son una configuración de solicitud ilustrativa. No se presentan como una muestra de búsqueda observada. Cambia la configuración solo después de decidir el alcance de tu propia recopilación.
No se ejecutaron solicitudes autenticadas para este artículo porque no se proporcionó ninguna clave de cuenta. Las pruebas locales ejercen la continuidad y la lógica de estado con respuestas sintéticas. Una ejecución de cuenta en vivo sigue siendo un requisito previo para validar el comportamiento actual de las respuestas.
Mantén un Desplazamiento de Página Junto a Cada Captura en Crudo
El flujo de trabajo de solicitud de búsqueda de Google envía scraper.google.search a POST https://api.scrapeless.com/api/v1/scraper/request con autenticación en x-api-token. El recolector registra el cuerpo enviado por separado de la respuesta y agrega marcas de tiempo del cliente y su propio identificador de ejecución.
El HTTP 200 contiene datos de tarea; el HTTP 201 representa una tarea que aún se está procesando. La última respuesta debe ser guardada antes de detenerse. Su identificador de tarea permanece en la respuesta en crudo para un flujo de trabajo de finalización verificado por separado; este ejemplo no inventa un endpoint de recuperación de tareas.
El sobre de captura es propiedad de la aplicación. Su request, response, marcas de tiempo y campo de error no se afirman como el envoltorio nativo de la API. Mantenerlos separados sigue el modelo de datos JSON y preserva la evidencia subyacente para un mapeador posterior.
Inspecciona la Evidencia de Continuación Antes de Avanzar
El ejemplo de respuesta de inicio rápido incluye pagination.next, pero la URL de ejemplo está abreviada. No ejecutes ese valor abreviado. El código requiere una URL de búsqueda HTTPS de Google actual y no truncada antes de usar su desplazamiento como una pista de continuación.
El ejemplo acepta solo hosts de Google revisados y verifica que la consulta, el país y el idioma en el enlace devuelto coincidan con la configuración base. Preserva todas las configuraciones base enviadas y cambia solo start. Un campo de país faltante en el siguiente enlace causa una detención de revisión en lugar de una inferencia sobre lo que el servicio pretendía.
Estas verificaciones utilizan análisis de componentes de URL. Definen la política de continuación estrecha de la aplicación, no cada URL válida de Google. El recolector nunca sigue directamente la URL devuelta; envía la siguiente solicitud al mismo punto final de la API con el cuerpo en modo de parámetro revisado.
Ejecuta el Recolector Con Motivos de Detención Explícitos
Guarda el programa como paginate_search.py. Con tu clave configurada en el entorno, ejecuta python3 paginate_search.py en un directorio escribible. El script crea un directorio de salida con un nombre único, envía solicitudes de manera secuencial y mantiene cada captura de página más collection-summary.json.
Nota: La colección de API en vivo es un requisito previo y no se realizó para este artículo. El flujo de control local del código se probó utilizando respuestas sintéticas. Inspecciona la salida de la cuenta real antes de depender de la política de siguiente enlace y verifica la recuperación de tareas por separado si una solicitud devuelve HTTP 201.
python
import json
import os
import uuid
from datetime import datetime, timezone
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import parse_qs, urlsplit
from urllib.request import Request, urlopen
ENDPOINT = 'https://api.scrapeless.com/api/v1/scraper/request'
OFFSETS = (0, 10, 20)
BASE = {'q': 'coffee', 'gl': 'us', 'hl': 'en', 'device': 'desktop'}
def now():
return datetime.now(timezone.utc).isoformat()
def fetch(body, key):
request = Request(ENDPOINT, data=json.dumps(body).encode(), method='POST',
headers={'Content-Type': 'application/json', 'x-api-token': key})
try:
with urlopen(request, timeout=60) as response:
status, raw = response.status, response.read().decode('utf-8')
except HTTPError as error:
status, raw = error.code, error.read().decode('utf-8', errors='replace')
except (URLError, TimeoutError) as error:
return None, None, type(error).__name__
try:
return status, json.loads(raw), None
except json.JSONDecodeError:
return status, {'unparsed_body': raw}, 'response_not_json'
def next_offset(payload, current):
pagination = payload.get('pagination')
link = pagination.get('next') if isinstance(pagination, dict) else None
if not isinstance(link, str) or not link:
return None, 'next_link_unavailable'
try:
parts = urlsplit(link)
if (parts.scheme != 'https' or parts.hostname not in ('google.com', 'www.google.com')
or parts.username or parts.password or parts.port or parts.path != '/search'
or parts.fragment or '...' in link or '…' in link):
return None, 'next_link_needs_review'
query = parse_qs(parts.query, keep_blank_values=True)
if any(query.get(k) != [BASE[k]] for k in ('q', 'gl', 'hl')):
return None, 'next_context_needs_review'
values = query.get('start', [])
if len(values) != 1 or not values[0].isascii() or not values[0].isdigit():
return None, 'next_offset_needs_review'
offset = int(values[0])
if offset <= current or offset not in OFFSETS:
return None, 'next_offset_outside_plan'
return offset, None
except ValueError:
return None, 'next_link_needs_review'
def collect(directory, key):
directory = Path(directory)
directory.mkdir(parents=True, exist_ok=False)
seen, visited, pages = set(), set(), []
offset, stop = 0, None
while offset in OFFSETS and offset not in visited:
visited.add(offset)
body = {'actor': 'scraper.google.search', 'input': dict(BASE, start=offset)}
started = now()
status, payload, error = fetch(body, key)
run_id = uuid.uuid4().hex
capture = {'run_id': run_id, 'requested_at': started, 'received_at': now(),
'request': body, 'http_status': status, 'response': payload, 'error': error}
filename = f'{offset}-{run_id}.json'
(directory / filename).write_text(json.dumps(capture, ensure_ascii=False, indent=2), encoding='utf-8')
page = {'start': offset, 'capture': filename, 'new_exact_urls': None}
pages.append(page)
if status == 201:
stop = 'pending'
break
if status != 200 or error:
stop = 'collection_error'
break
rows = payload.get('organic_results') if isinstance(payload, dict) else None
if not isinstance(rows, list) or any(not isinstance(row, dict) for row in rows):
stop = 'unmapped_organic_results'
break
links = {row['link'] for row in rows if isinstance(row.get('link'), str) and row['link']}
page['new_exact_urls'] = len(links - seen)
seen.update(links)
if not rows:
stop = 'empty_organic_slice'
break
if not links:
stop = 'no_usable_link_strings'
break
if page['new_exact_urls'] == 0:
stop = 'no_new_exact_urls'
break
if len(visited) == len(OFFSETS):
stop = 'planned_page_limit'
break
offset, stop = next_offset(payload, offset)
if stop:
break
summary = {'pages': pages, 'stop_reason': stop, 'unique_exact_url_strings': len(seen)}
(directory / 'collection-summary.json').write_text(json.dumps(summary, indent=2), encoding='utf-8')
return summary
if __name__ == '__main__':
key = os.environ['SCRAPELESS_API_KEY']
output = 'search-pages-' + uuid.uuid4().hex
print(json.dumps(collect(output, key), indent=2))
El tiempo de espera es una configuración del cliente local, no un reclamo de rendimiento del servicio. Una excepción de transporte o una respuesta no JSON produce un registro de error y detiene la colección. El programa mantiene las capturas anteriores en lugar de presentar una ejecución parcial como un barrido completo.
Comienza a raspar con Scrapeless
¡Potencia tu flujo de trabajo de web scraping y automatización con Scrapeless!
Regístrate hoy y obtén $5 en crédito gratis — no se requiere tarjeta de crédito.Reclama tu crédito gratis ahora en el Tablero de Scrapeless.
Separar la Desduplicación de la Evidencia de Búsqueda
El resumen cuenta nuevas cadenas de URL exactas en cada página mientras retiene cada respuesta original. Las URL repetidas permanecen en esas capturas con su contexto de página. Esto permite a un revisor inspeccionar la superposición en lugar de perderla durante la exportación.
La desduplicación de cadenas exactas es intencionalmente estrecha. No fusiona variantes de seguimiento, fragmentos, URLs canónicas o diferentes páginas de un mismo dominio. Una política de agrupamiento más amplia pertenece a una transformación versionada por separado y debería conservar los enlaces originales.
El programa se detiene cuando una página utilizable no contribuye con nuevas cadenas de URL exactas. Esa es una decisión de presupuesto de colección, no prueba de que no existan más páginas relevantes. Del mismo modo, un array orgánico vacío presente detiene esta ejecución sin establecer un límite de búsqueda exhaustivo.
Interpretar una Colección Parcial Honestamente
Lee la razón de detención del resumen antes de usar su cuenta de URL. planned_page_limit significa que se alcanzó el límite local. next_link_unavailable significa que no se estableció la continuación. Estados pendientes, no mapeados y de error de colección describen evidencia incompleta o inutilizable, no detección exitosa de fin de resultados.
Cada solicitud tiene su propia marca de tiempo. Las páginas secuenciales no se recopilaron simultáneamente, y el programa no reclama una sesión de servidor compartida a lo largo de las llamadas. Preserva la ventana de colección al comparar esta ejecución con otra.
El modelo de procedencia ayuda a separar las observaciones de páginas en bruto, la actividad de colección y una lista derivada desduplicada. Incluso un pequeño diseño de sistema de archivos puede preservar esas relaciones si el resumen apunta de nuevo a cada captura.
Conclusión
Planifica una colección limitada, conserva cada solicitud enviada y avanza solo cuando la respuesta apoye la regla de continuación de la aplicación. Razones de detención explícitas y duplicados retenidos hacen que un conjunto de datos parcial sea comprensible sin reclamar que contenga todos los resultados de Google.
Un enfoque de conjunto de datos instantáneo de SERP puede ayudar a organizar las observaciones guardadas después de la colección; mantén visibles los desplazamientos de página al decidir qué registros son comparables.
Construye tu Próxima Observación de Búsqueda
Usa Scrapeless Google Search API para los datos de búsqueda en este flujo de trabajo. Revisa precios de Scrapeless al planificar la colección, y mantén los parámetros de búsqueda de Google junto a tu configuración.
Discute tu implementación con la comunidad en Discord o Telegram.
FAQ
P: ¿start=10 garantiza diez filas orgánicas?
No. start especifica un desplazamiento. Inspecciona el array orgánico real en lugar de derivar su longitud del desplazamiento solicitado.
P: ¿Este script recopila todos los resultados de Google?
No. Está limitado por un pequeño plan de aplicación y se detiene cuando la continuación o los datos utilizables no están disponibles.
P: ¿Qué pasa con HTTP 201?
La respuesta se guarda y la recopilación se detiene como pendiente. Se requiere un flujo de trabajo de finalización de tarea verificado antes de interpretar los datos de búsqueda finales.
P: ¿Por qué detenerse cuando un siguiente enlace omite un campo de contexto?
El ejemplo requiere un acuerdo explícito sobre la consulta, el país y el idioma. La falta de evidencia desencadena una revisión en lugar de una suposición silenciosa sobre la continuación.
P: ¿Se eliminan las URL repetidas de las capturas en bruto?
No. Solo se dedupla el recuento exacto de cadenas del resumen. Las observaciones originales siguen estando disponibles para su inspección.
En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.



