Paginando Resultados de Pesquisa do Google com Contexto de Solicitação Rastreável
Expert Network Defense Engineer
TL;DR:
- A paginação da API de pesquisa do Google altera o segmento coletado. Mantenha
starte a requisição completa com cada resposta, em vez de tratar páginas como lotes intercambiáveis. - Continue apenas dentro de um plano definido e um contrato de resposta revisado. O exemplo inspeciona o próximo link retornado antes de mudar o deslocamento e para em dados de continuação ambíguos.
- Mantenha o trabalho pendente e observações duplicadas visíveis. Armazene cada resposta de página bruta; a deduplicação pertence a uma visão derivada, e o HTTP 201 não é uma página vazia.
A paginação pode ocultar uma mudança no experimento. Um coletor pode avançar o deslocamento enquanto perde uma configuração de país, ou combinar uma resposta pendente com páginas concluídas e chamar o resultado de completo. A lista de URLs resultante não revela como essas diferenças entraram na coleção.
A API de Pesquisa do Google Scrapeless suporta deslocamentos de pesquisa através de start. Este guia de paginação da API de pesquisa do Google constrói um coletor deliberadamente limitado com um registro para cada requisição e uma razão para parar. Ele demonstra o fluxo de controle da aplicação sem prometer acesso a cada resultado ou a uma sequência histórica de classificação ininterrupta.
Pré-requisitos e o Plano de Coleta Limitado
O programa utiliza a biblioteca padrão do Python e requer uma chave de API de conta em SCRAPELESS_API_KEY para coleta real. O nome da variável de ambiente é uma convenção deste exemplo. Mantenha a chave fora dos logs do corpo da requisição e dos arquivos de captura gerados.
Os parâmetros de Pesquisa do Google descrevem start como o deslocamento do resultado, com exemplos de 0, 10 e 20. O programa limita seu plano a esses deslocamentos. Este é um limite da aplicação, não uma declaração sobre a profundidade máxima do serviço ou uma garantia de que cada página contém um número fixo de itens orgânicos.
A consulta base é coffee com gl=us, hl=en e entrada desktop. Essas configurações são uma configuração de requisição ilustrativa. Elas não são apresentadas como uma amostra de pesquisa observada. Altere a configuração apenas após decidir o escopo de sua própria coleta.
Requisições autenticadas não foram executadas para este artigo porque nenhuma chave de conta foi fornecida. Testes locais exercitam a lógica de continuação e estado com respostas sintéticas. Uma execução de conta ao vivo continua sendo um pré-requisito para validar o comportamento atual da resposta.
Mantenha um Deslocamento de Página Ao Lado de Cada Captura Bruta
O fluxo de trabalho da requisição do Google Search envia scraper.google.search para POST https://api.scrapeless.com/api/v1/scraper/request com autenticação em x-api-token. O coletor registra o corpo enviado separadamente da resposta e adiciona timestamps do cliente e seu próprio identificador de execução.
HTTP 200 contém dados da tarefa; HTTP 201 representa uma tarefa que ainda está sendo processada. A última resposta deve ser salva antes de parar. Seu identificador de tarefa permanece na resposta bruta para um fluxo de trabalho de conclusão verificado separadamente; este exemplo não inventa um endpoint de recuperação de tarefa.
O envelope de captura é de propriedade da aplicação. Seu request, response, timestamps e campo de erro não são afirmados como a camada nativa da API. Mantê-los separados segue o modelo de dados JSON e preserva as evidências subjacentes para um mapeador posterior.
Inspecione a Evidência de Continuação Antes de Avançar
O exemplo de resposta do quickstart inclui pagination.next, mas a URL de amostra está abreviada. Não execute esse valor abreviado. O código requer uma URL de pesquisa do Google HTTPS real e não truncada antes de usar seu deslocamento como uma dica de continuação.
O exemplo aceita apenas hosts do Google revisados e verifica se a consulta, país e idioma no link retornado correspondem à configuração base. Ele preserva todas as configurações base submetidas e altera apenas start. Um campo de país ausente no próximo link causa uma parada de revisão ao invés de uma inferência sobre o que o serviço pretendia.
Essas verificações usam análise de componentes de URL. Elas definem a política de continuação estreita da aplicação, não cada URL válida do Google. O coletor nunca segue a URL retornada diretamente; ele envia a próxima requisição para o mesmo endpoint da API com o corpo na configuração de parâmetros revisada.
Execute o Coletor Com Razões de Parada Explícitas
Salve o programa como paginate_search.py. Com sua chave definida no ambiente, execute python3 paginate_search.py em um diretório gravável. O script cria um diretório de saída com nome exclusivo, envia solicitações sequencialmente e mantém cada captura de página mais collection-summary.json.
Nota: A coleta de API ao vivo é um pré-requisito e não foi realizada para este artigo. O fluxo de controle local do código foi testado usando respostas sintéticas. Inspecione a saída da conta real antes de confiar na política de próximo link e verifique a recuperação de tarefas separadamente se uma solicitação retornar 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))
O tempo limite é uma configuração do cliente local, não uma afirmação de desempenho de serviço. Uma exceção de transporte ou resposta não-JSON produz um registro de erro e interrompe a coleta. O programa mantém capturas anteriores em vez de apresentar uma execução parcial como uma varredura completa.
Comece a Raspar com Scrapeless
Impulsione seu trabalho de web scraping e automação com Scrapeless!
Inscreva-se hoje e receba $5 em crédito grátis — sem necessidade de cartão de crédito.Reivindique seu crédito gratuito agora no Painel do Scrapeless.
Separe a Deduplicação da Evidência de Pesquisa
O resumo conta novas strings de URL exatas em cada página enquanto retém cada resposta original. URLs repetidas permanecem nessas capturas com seu contexto de página. Isso permite que um revisor inspeccione a sobreposição em vez de perdê-la durante a exportação.
A deduplicação de string exata é intencionalmente estreita. Ela não mescla variantes de rastreamento, fragmentos, URLs canônicos ou páginas diferentes de um único domínio. Uma política de agrupamento mais ampla pertence a uma transformação versionada separadamente e deve reter os links originais.
O programa para quando uma página utilizável não contribui com novas strings de URL exatas. Essa é uma decisão de orçamento de coleta, não uma prova de que não existem mais páginas relevantes. Da mesma forma, um array orgânico vazio presente interrompe esta execução sem estabelecer uma fronteira de pesquisa exaustiva.
Interprete uma Coleta Parcial Honestamente
Leia a razão para a parada do resumo antes de usar sua contagem de URLs. planned_page_limit significa que o limite local foi alcançado. next_link_unavailable significa que a continuidade não foi estabelecida. Estados pendentes, não mapeados e de erro de coleta descrevem evidências inacabadas ou inutilizáveis, não a detecção bem-sucedida do final dos resultados.
Cada solicitação tem seu próprio carimbo de data/hora. Páginas sequenciais não foram coletadas simultaneamente, e o programa não afirma uma sessão de servidor compartilhada entre chamadas. Preserve a janela de coleta ao comparar esta execução com outra.
O modelo de proveniência ajuda a separar observações brutas de página, a atividade de coleta e uma lista deduplicada derivada. Mesmo um layout de sistema de arquivos pequeno pode preservar essas relações se o resumo apontar para cada captura.
Conclusão
Planeje uma coleta limitada, preserve cada solicitação enviada e avance somente quando a resposta apoiar a regra de continuidade da aplicação. Razões de parada explícitas e duplicatas retidas tornam um conjunto de dados parcial compreensível sem afirmar que ele contém todo o resultado do Google.
Uma abordagem de conjunto de dados de snapshot SERP pode ajudar a organizar as observações salvas após a coleta; mantenha os deslocamentos de página visíveis ao decidir quais registros são comparáveis.
Construa Sua Próxima Observação de Pesquisa
Use Scrapeless Google Search API para os dados de pesquisa neste fluxo de trabalho. Revise preços do Scrapeless ao planejar a coleta e mantenha os parâmetros de pesquisa do Google ao lado de sua configuração.
Discuta sua implementação com a comunidade no Discord ou Telegram.
FAQ
Q: start=10 garante dez linhas orgânicas?
Não. start especifica um deslocamento. Inspecione o array orgânico real em vez de derivar seu comprimento a partir do deslocamento solicitado.
Q: Este script coleta todos os resultados do Google?
Não. Ele é limitado por um pequeno plano de aplicação e para quando a continuidade ou dados utilizáveis não estão disponíveis.
Q: O que acontece com o HTTP 201?
A resposta é salva e a coleta para enquanto está pendente. Um fluxo de trabalho de verificação de conclusão da tarefa é necessário antes de interpretar os dados de busca finais.
P: Por que parar quando um próximo link omite um campo de contexto?
O exemplo requer concordância explícita de consulta, país e idioma. A evidência ausente aciona uma revisão em vez de uma suposição silenciosa sobre a continuidade.
P: URLs repetidos são removidos das capturas brutas?
Não. Somente a contagem exata de strings do resumo é deduplicada. As observações originais permanecem disponíveis para inspeção.
Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.



