Raspagem de Scroll Infinito: Pare de Adivinhar Quantas Vezes Rolagem fazer
Advanced Data Extraction Specialist
TL;DR:
- Uma página de rolagem infinita envia quase nada em seu HTML. A demonstração usada aqui serve 2.671 bytes contendo zero elementos de citação; cada item chega depois.
- Um número fixo de rolagens é a falha que todos entregam. Cinco rolagens retornaram 60 de 100 itens — sem erro, sem aviso, uma saída limpa com 40% dos dados faltando.
- Role até que a contagem de itens pare de crescer. Isso retornou todos os 100.
- O número de rolagens não é uma propriedade do site. O mesmo loop precisou de 11 ações de rolagem localmente e 3 em um navegador em nuvem, alcançando 100 em ambas as vezes.
- A página fez 10 chamadas para
/api/quotes?page=N. Ler esse endpoint diretamente retorna os mesmos 100 itens e relatahas_next, para que o loop saiba quando está terminado. - O plano gratuito do Scrapeless cobre a execução em navegador em nuvem neste guia.
A rolagem infinita quebra os scrapers silenciosamente. A solicitação é bem-sucedida, os seletores combinam, o script sai com zero — e o conjunto de dados é curto. Nada na execução diz quanto você perdeu, porque a página nunca prometeu quanto havia.
Este guia mede isso em uma página de demonstração ao vivo. Ele constrói o loop que a maioria dos tutoriais entrega, mostra exatamente o que ele descarta, substitui por um que tem uma condição de parada real e, em seguida, encontra a solicitação que a página estava fazendo o tempo todo.
O que o Servidor Realmente Envia
Busque a página sem um navegador e não há nada para analisar:
python
def served_html() -> tuple[int, int]:
request = urllib.request.Request(SCROLL_URL, headers={"User-Agent": UA})
with urllib.request.urlopen(request, timeout=45) as response:
html = response.read().decode("utf-8", "replace")
return len(html), html.count('class="quote"')
text
bytes da página de rolagem : 2671
elementos de citação no html : 0
2.671 bytes e nenhum item. A marcação é uma casca; o conteúdo é buscado por script após a página ser carregada, tipicamente quando um elemento sentinela próximo ao fundo entra em vista — o mecanismo a especificação do Intersection Observer define. Um seletor CSS não pode falhar de forma mais clara do que isso — ele não combina com nada porque nada está lá ainda.
Instalação
bash
pip install playwright
playwright install chromium
O segundo comando faz o download do binário do navegador; o pacote pip sozinho não irá iniciar. A execução de verificação usou o Playwright 1.59.0.
O Loop Que Todos Entregam
A receita padrão é rolar um número fixo de vezes e então ler a página. page.mouse.wheel() despacha um verdadeiro evento de rolagem do tipo a especificação de Eventos de UI define, que é o que o próprio ouvinte da página está esperando:
python
def scroll_fixed(page, times: int, pause: float) -> int:
for _ in range(times):
page.mouse.wheel(0, 20000)
time.sleep(pause)
return page.locator("div.quote").count()
Cinco rolagens contra a página ao vivo:
text
citações após 5 rolagens : 60
segundos decorrido : 5.1
Sessenta itens. A página tem cem. O script não gerou nada, o seletor funcionou, e a execução parece bem-sucedida por fora — o que torna isso a versão cara do bug. Escolha cinco porque funcionou uma vez, e cada execução posterior herda uma silenciosa falta de 40%.
Aumentar o número também não é uma solução. Isso troca a subcoleta por rolagens desperdiçadas e ainda codifica um palpite sobre um site que pode mudar seu tamanho de lote sempre que desejar.
Role Até Parar de Crescer
A condição que você realmente deseja é observável: continue rolando enquanto a contagem de itens ainda estiver aumentando e pare assim que se mantiver estável.
python
def scroll_until_stable(page, pause: float, no_growth_limit: int = 2) -> tuple[int, int]:
seen = page.locator("div.quote").count()
scrolls = 0
stable = 0
while stable < no_growth_limit:
page.mouse.wheel(0, 20000)
time.sleep(pause)
scrolls += 1
count = page.locator("div.quote").count()
if count == seen:
stable += 1
else:
stable = 0
seen = count
return seen, scrolls
no_growth_limit decide quantas evidências você precisa antes de acreditar que a página terminou. Uma leitura plana não é prova — um lote ainda em trânsito parece idêntico ao final da lista. Exigir duas leituras planas consecutivas custa uma rolagem extra e remove essa ambiguidade.
text
citações coletadas : 100
ações de rolagem realizadas : 11
segundos decorrido : 11.1
chamadas para a API que a página fez : 10
primeira URL da API : https://quotes.toscrape.com/api/quotes?page=1
Todos os 100, e o loop descobriu a contagem em vez de presumir. Note que o número de segundos decorrido é principalmente o intervalo que este loop escolhe esperar — onze rolagens a um segundo cada. É uma propriedade do loop, não do site.
A Contagem de Rolagens Não É uma Propriedade do Site
Executando a função idêntica em um navegador em nuvem em vez do Chromium local:
text
cotações coletadas : 100
ações de rolagem realizadas : 3
Mesma função, mesma página, mesmos 100 itens — 3 ações de rolagem em vez de 11. A altura da janela de visualização e a rapidez com que cada lote chega decidem quanto um evento de rolagem avança a página, e nenhum dos dois está sob seu controle. As dimensões da janela de visualização contra as quais a rolagem é medida são aquelas que o Módulo de Visualização CSSOM especifica. Qualquer contagem de rolagem codificada é calibrada para o tamanho da janela de uma máquina.
Essa execução usou o Navegador de Scraping Scrapeless, ao qual o Playwright se conecta através do Protocolo DevTools do Chrome. Apenas a linha de conexão muda:
python
endpoint = (
"wss://browser.scrapeless.com/api/v2/browser"
f"?token={os.environ['SCRAPELESS_API_KEY']}&sessionTTL=180&proxyCountry=ANY"
)
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(endpoint, timeout=90000)
page = browser.new_page()
page.goto(SCROLL_URL, wait_until="domcontentloaded")
cloud_total, cloud_scrolls = scroll_until_stable(page, pause=1.0)
connect_over_cdp substitui chromium.launch() e fala o Protocolo DevTools do Chrome através de um socket em vez de um processo local, então o loop de rolagem, seletores e extração permanecem exatamente como estavam. Mantenha a chave no ambiente como SCRAPELESS_API_KEY; a introdução ao Navegador de Scraping documenta os parâmetros de sessão restantes.
Começar leva um minuto — crie uma conta gratuita no Scrapeless e o plano gratuito cobre essa execução.
Veja o Que a Página Está Solicitando
O loop de rolagem é uma maneira de pedir à página que busque dados. Ouvir as solicitações mostra o que ela busca:
python
calls: list[str] = []
context = browser.new_context(user_agent=UA)
context.on("request", lambda r: calls.append(r.url) if "/api/quotes" in r.url else None)
Dez chamadas, a primeira sendo https://quotes.toscrape.com/api/quotes?page=1. A página está paginando um endpoint JSON e renderizando os resultados; a rolagem é apenas o gatilho.
Esse endpoint pode ser lido diretamente e responde à pergunta que o loop de rolagem teve que inferir:
python
def read_api() -> tuple[int, int]:
quotes = 0
page = 1
while True:
request = urllib.request.Request(API_URL.format(page=page), headers={"User-Agent": UA})
with urllib.request.urlopen(request, timeout=45) as response:
payload = json.loads(response.read().decode())
quotes += len(payload["quotes"])
if not payload["has_next"]:
return quotes, page
page += 1
text
cotações coletadas : 100
páginas da API solicitadas : 10
segundos decorridos : 3.8
Os mesmos 100 itens, com has_next como uma condição de término explícita em vez de "a contagem parou de mudar." A resposta já está estruturada, então nenhum seletor está entre você e os campos — o que também significa nenhum seletor que quebre quando a marcação é reestilizada.
Isso vale a pena verificar antes de escrever qualquer loop de rolagem. Não existe sempre: muitos sites renderizam do lado do servidor ao rolar, assinam suas solicitações ou retornam fragmentos HTML em vez de JSON. Quando existe, é o alvo mais durável, e o navegador ainda é o que mostra que ele está lá. Para as outras formas de paginação — botões de próximo, páginas numeradas, carregar mais — o guia completo de paginação cobre cada tipo.
Execute
bash
export SCRAPELESS_API_KEY="sua-chave-api"
python3 scroll_demo.py
A saída completa da execução de verificação:
text
playwright 1.59.0
--- o que o servidor realmente envia ---
bytes da página de rolagem : 2671
elementos de citação no html : 0
--- número fixo de rolagens ---
cotações após 5 rolagens : 60
segundos decorridos : 5.1
--- rolagem até a contagem parar de crescer ---
cotações coletadas : 100
ações de rolagem realizadas : 11
segundos decorridos : 11.1
chamadas da API que a página fez : 10
primeira url da API : https://quotes.toscrape.com/api/quotes?page=1
--- o mesmo loop no Navegador de Scraping ---
cotações coletadas : 100
ações de rolagem realizadas : 3
--- lendo a mesma API diretamente ---
cotações coletadas : 100
páginas da API solicitadas : 10
segundos decorridos : 3.8
proporção do tempo do navegador/api: 3x
A proporção na última linha compara o relógio do loop do navegador com as leituras diretas. A maior parte do lado do navegador é a pausa deliberada de um segundo entre os rolagens, então leia isso como o custo de verificar uma página por uma condição que nunca é reportada — não como um benchmark do próprio navegador.
Resolução de Problemas
A contagem nunca para de crescer. Algumas páginas fazem loop em seu conteúdo. Limite o loop com uma contagem máxima de rolagem junto com a verificação de estabilidade, e trate atingir esse limite como um sinal para inspecionar a página em vez de como uma execução concluída.
Zero itens mesmo após rolar. O contêiner pode rolar em vez da janela. Role o próprio elemento com page.locator(...).hover() seguido de page.mouse.wheel(...), ou chame scroll_into_view_if_needed() no último item.
A contagem cresce e a página fica em branco. Listas longas são frequentemente virtualizadas, então as linhas são removidas do DOM à medida que saem da viewport. Colete os itens à medida que avança, em vez de lê-los todos no final.
Funciona com cabeça e não sem cabeça. O tamanho da viewport difere entre os dois, o que muda a distância que um evento de roda percorre e se o carregador entra em vista. Defina uma viewport explícita no contexto.
playwright._impl._errors.Error: Executável não existe. O binário do navegador nunca foi baixado. Execute playwright install chromium.
Conclusão
A rolagem infinita transforma "eu consegui tudo?" em uma pergunta que seu scraper deve responder por conta própria, e uma contagem fixa de rolagem responde isso adivinhando. As medições aqui mostram qual é o custo: 60 itens de 100 em cinco rolagens, e uma contagem de rolagem que mudou de 11 para 3 simplesmente ao mudar para um navegador diferente.
Loop em uma condição observada — contagem de itens estável em leituras consecutivas — em vez de um número que você escolheu. E antes de escrever o loop, observe as solicitações da página: quando o conteúdo chega como JSON com uma flag has_next, a página já lhe disse como saber quando você terminou.
Pronto para tentar? Comece com o plano gratuito do Scrapeless e veja a tabela de preços atual para volumes maiores.
FAQ
Q: Quantas vezes devo rolar?
Não escolha um número. As medições acima usaram um loop contra dois navegadores e precisaram de 11 rolagens em um e 3 no outro para os mesmos 100 itens, porque a altura da viewport e o tempo de lote decidem quão longe cada evento de roda vai. Faça loop enquanto a contagem de itens ainda estiver crescendo e pare depois que ela se manter estável duas vezes.
Q: Como sei se a página terminou de carregar tudo?
Duas leituras consecutivas com uma contagem de itens inalterada é o sinal prático, porque uma única leitura plana é indistinguível de um lote ainda em voo. Se a solicitação subjacente da página expõe uma flag como has_next, essa é uma resposta definitiva em vez de uma inferência.
Q: Preciso de um navegador para rolagem infinita?
Frequentemente não. A página de demonstração aqui carrega seu conteúdo de um endpoint JSON que pode ser lido diretamente para todos os 100 itens. O navegador ainda é como você descobre esse endpoint — anexe um ouvinte de solicitações, role uma vez e leia as URLs. Sites que renderizam do lado do servidor ao rolar ou assinam suas solicitações realmente precisam do navegador.
Q: Por que meu scraper retorna menos itens do que a página exibe?
Ou o loop parou cedo, ou a lista é virtualizada e as linhas foram removidas do DOM à medida que saíram de vista. Imprima a contagem de itens após cada rolagem: uma contagem que sobe e depois cai indica virtualização, e uma contagem ainda subindo quando o loop termina indica que o loop parou muito cedo.
Q: wait_until="networkidle" resolve isso?
Não. Ele espera que o carregamento inicial se estabilize, o que acontece antes de qualquer rolagem ter acionado uma busca. Os lotes chegam em resposta aos eventos de rolagem, então a página pode estar ociosa e quase vazia ao mesmo tempo — que é exatamente o estado medido de 2.671 bytes no início.
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.



