Voltar ao blogue
Guias
Ștefan RăcilăLast updated on Apr 29, 202613 min read

Tutorial do Scrapy Splash: Renderizar páginas JavaScript

Tutorial do Scrapy Splash: Renderizar páginas JavaScript
Resumo: O Scrapy Splash combina o rápido motor de rastreamento do Scrapy com o navegador headless Splash para renderizar páginas com muito JavaScript. Este tutorial sobre o Scrapy Splash orienta-o passo a passo pela configuração do Docker, pela configuração do projeto Scrapy, pelos conceitos básicos do SplashRequest, pelos scripts Lua para rolagem e cliques, pela integração de proxy e pela correção dos erros mais comuns que irá encontrar.

O Scrapy é uma das estruturas de rastreamento web mais eficientes no ecossistema Python, mas tem um ponto fraco bem conhecido: não consegue executar JavaScript. Qualquer site que carregue dados através de renderização do lado do cliente, chamadas AJAX ou estruturas de aplicações de página única é invisível para um spider Scrapy padrão. Este é exatamente o problema que um tutorial do Scrapy Splash resolve.

O Scrapy Splash é uma camada de integração entre o Scrapy e o navegador headless Splash. O Splash é um serviço de renderização leve, baseado em Qt, desenvolvido pela Zyte (a mesma equipa por trás do Scrapy) que expõe uma API HTTP. Em vez de executar um navegador de desktop completo, o Splash carrega uma página num motor WebKit simplificado, executa o JavaScript e devolve HTML totalmente renderizado ao seu spider. Os seus métodos de análise continuam a funcionar com seletores CSS e XPath padrão, como se nada tivesse mudado.

Neste guia, irá instalar o Docker e o Splash a partir do zero, configurar o seu projeto Scrapy, escrever spiders que renderizam páginas dinâmicas, criar scripts Lua para interações avançadas, configurar proxies e resolver os erros que mais atrapalham os novatos.

O que é o Scrapy Splash e quando deve usá-lo?

O Splash é um navegador headless com uma API HTTP que renderiza páginas web carregadas com JavaScript. Ao contrário dos navegadores completos, o Splash foi concebido para ser leve: é iniciado dentro de um contentor Docker, escuta numa porta e devolve HTML renderizado (ou capturas de ecrã PNG, ou registos HAR) através de HTTP. O Scrapy Splash é a biblioteca oficial que liga o Scrapy a este serviço de renderização.

Recorra ao Scrapy Splash quando o seu site de destino carregar conteúdo crítico via JavaScript ou AJAX e precisar das funcionalidades integradas do Scrapy, como pipeline, middleware e gestão de rastreamento. A Zyte criou o Splash especificamente para fluxos de trabalho de scraping, e este integra-se no ciclo de vida de pedido/resposta do Scrapy sem atritos. Dito isto, o Splash utiliza um motor WebKit mais antigo, pelo que o seu suporte a JavaScript não é tão moderno quanto as alternativas baseadas no Chromium. Se o seu alvo depende de APIs de navegador de ponta, avalie ferramentas de navegador headless com um backend Chromium.

Para uma renderização JS simples em escala (páginas de produtos, listas de diretórios, resultados paginados), o Splash continua a ser uma opção prática e eficiente em termos de recursos.

Pré-requisitos e configuração do ambiente

Antes de mergulhar neste tutorial do Scrapy Splash, certifique-se de que tem o seguinte:

  • Python 3.7+ instalado no seu sistema
  • Um projeto Scrapy (ou a intenção de criar um)
  • Docker Desktop (ou o motor Docker no Linux) para executar o contentor Splash
  • Um terminal para executar os comandos do Docker e da CLI do Scrapy

Essa é a lista completa. A próxima secção aborda a instalação do Docker.

Instalar o Docker e executar o contentor Splash

O Splash é executado dentro de um contentor Docker, pelo que o Docker precisa de ser instalado primeiro. Obtenha o Docker Desktop para macOS ou Windows, ou instale o motor Docker diretamente no Linux. Assim que o Docker estiver a funcionar, recupere a imagem oficial do Splash:

docker pull scrapinghub/splash

Em seguida, inicie o contentor:

docker run -it -p 8050:8050 --rm scrapinghub/splash

Isto mapeia a porta 8050 no contentor para a porta 8050 no seu host. A --rm flag remove o contêiner quando o parares.

Abra http://localhost:8050/ no seu navegador. Se vir a página interativa padrão do Splash, o serviço está ativo. Teste um URL nesse mesmo local para confirmar que a renderização funciona antes de escrever qualquer código Scrapy.

Para uma configuração do Splash no Docker com o Scrapy destinada à produção, considere os limites de recursos. A --max-timeout permite aumentar o tempo de espera padrão de 60 segundos (o máximo é de aproximadamente 90 segundos, a menos que o substitua; no entanto, deve verificar o valor exato na documentação atual do Splash, uma vez que os detalhes podem variar). Limite a memória com a --memory para evitar que páginas descontroladas consumam os recursos do seu host.

Configurar o seu projeto Scrapy para o Splash

Se ainda não tiver um projeto, crie um:

scrapy startproject myproject

Instale o plugin scrapy-splash:

pip install scrapy scrapy-splash

Abra settings.py e adicione estas entradas:

SPLASH_URL = 'http://localhost:8050'

DOWNLOADER_MIDDLEWARES = {
    'scrapy_splash.SplashCookiesMiddleware': 723,
    'scrapy_splash.SplashMiddleware': 725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}

SPIDER_MIDDLEWARES = {
    'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}

DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'

SPLASH_URL indica ao scrapy-splash onde se encontra o serviço de renderização. SplashCookiesMiddleware lida com o encaminhamento de cookies entre o Scrapy e o Splash. SplashMiddleware intercepta objetos SplashRequest e encaminha-os através da API HTTP do Splash. O DUPEFILTER_CLASS garante que o filtro de pedidos duplicados do Scrapy tenha em conta argumentos específicos do Splash, evitando a filtragem acidental de pedidos que diferem apenas nos parâmetros de renderização.

Com estas configurações definidas, o seu projeto está pronto para qualquer spider de tutorial do Scrapy Splash que venha a criar.

O seu primeiro spider do tutorial do Scrapy Splash: SplashRequest em ação

Gere um esqueleto de spider:

scrapy genspider quotes_js quotes.toscrape.com

Substitua o padrão start_urls padrão por start_requests, porque a classe de pedido padrão do Scrapy não passa pelo Splash:

import scrapy
from scrapy_splash import SplashRequest

class QuotesJsSpider(scrapy.Spider):
    name = 'quotes_js'

    def start_requests(self):
        yield SplashRequest(
            url='http://quotes.toscrape.com/js/',
            callback=self.parse,
            args={'wait': 2}
        )

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
            }

A principal diferença em relação a um padrão scrapy.Request é que o SplashRequest envia primeiro a URL para o Splash. O Splash renderiza a página, aguarda os segundos especificados para que o JavaScript seja executado e devolve HTML totalmente renderizado. Dentro parse, trabalha-se com a resposta exatamente como faria normalmente: seletores CSS, XPath, tudo funciona porque as respostas do Splash contêm todas as propriedades de resposta padrão.

Execute-o com scrapy crawl quotes_js e deverá ver os dados da cotação renderizados na sua saída.

Controlar a renderização da página com argumentos do SplashRequest

O SplashRequest aceita vários argumentos que controlam a forma como o Splash renderiza a página:

Argumento

Tipo

Finalidade

wait

float

Segundos a esperar após o carregamento da página antes de devolver o HTML

timeout

float

Tempo máximo de renderização (segundos). Padrão 60, limite máximo de aproximadamente 90, a menos que seja substituído

images

int (0/1)

Defina como 0 para desativar o carregamento de imagens, acelerando a renderização

resource_timeout

float

Tempo de espera por recurso individual (CSS, ficheiro JS, imagem)

http_method

string

Utilizar POST para envios de formulários

body

string

Conteúdo do corpo da solicitação POST, em conjunto com http_method='POST'

Por exemplo, para enviar uma solicitação POST (útil para envios de formulários):

yield SplashRequest(
    url='https://example.com/search',
    args={
        'wait': 1,
        'http_method': 'POST',
        'body': 'query=scrapy+splash',
    },
    callback=self.parse_results,
)

O http_method e body são úteis para sites que processam formulários de pesquisa ou ações de login no lado do servidor. Isto cobre os conceitos básicos de renderização do JavaScript do scrapy splash, mas para interação com a página (clicar, percorrer, aguardar elementos dinâmicos), são necessários scripts Lua.

Escrever scripts Lua para interações avançadas

O render.html endpoint lida com casos simples, mas assim que precisar de interagir com uma página, passa para o execute ponto de extremidade com um script Lua. Um script Lua do Scrapy Splash oferece-lhe controlo passo a passo sobre o navegador:

function main(splash, args)
  splash:go(args.url)
  splash:wait(1)
  return splash:html()
end

Envie isto através do SplashRequest utilizando endpoint='execute' e passe o script em args={'lua_source': script}. A partir daqui, incorpore esperas por elementos, loops de rolagem e ações de clique.

À espera que elementos específicos carreguem

Um wait funciona quando se conhece o tempo de carregamento da página, mas é frágil. Em vez disso, verifique se existe um elemento DOM específico:

function main(splash, args)
  splash:go(args.url)
  while not splash:select('.target-element') do
    splash:wait(0.5)
  end
  splash:wait(0.5)
  return splash:html()
end

Este script entra em loop até que splash:select() encontrar um elemento que corresponda .target-element, aguardando meio segundo entre tentativas. Assim que o elemento aparece, uma breve espera final trata da renderização restante. Este padrão é muito mais fiável do que adivinhar um atraso estático.

O Splash não possui comandos de rolagem integrados. Em vez disso, insira JavaScript para manipular a posição de rolagem. Aqui está um script Lua para rolagem infinita no Splash do Scrapy:

function main(splash, args)
  splash:go(args.url)
  splash:wait(2)
  local scroll_count = 5
  for i = 1, scroll_count do
    splash:runjs("window.scrollTo(0, document.body.scrollHeight)")
    splash:wait(2)
  end
  return splash:html()
end

O script percorre até ao fundo, aguarda por novo conteúdo e repete. Ajuste scroll_count e a duração da espera para corresponder ao site. Compare document.body.scrollHeight antes e depois de cada rolagem para detetar quando não aparece nenhum conteúdo novo.

Clicar em botões e navegar pelas páginas

Os botões «Carregar mais» e os links de paginação requerem interação com o rato. Use splash:select() para localizar o elemento e acionar um clique:

function main(splash, args)
  splash:go(args.url)
  splash:wait(2)
  local btn = splash:select('.load-more-btn')
  if btn then
    btn:mouse_click()
    splash:wait(2)
  end
  return splash:html()
end

Envolva isto num ciclo para páginas com vários gatilhos. Para paginação, selecione o link «Seguinte», clique nele, aguarde a nova página e recolha o HTML em cada etapa.

Executar JavaScript personalizado no Splash

Às vezes, não é necessário um fluxo de trabalho Lua completo. O Splash permite-lhe injetar JavaScript arbitrário com dois métodos: splash:evaljs() (retorna um valor) e splash:runjs() (executa sem retornar).

function main(splash, args)
  splash:go(args.url)
  splash:wait(1)
  local title = splash:evaljs("document.title")
  splash:runjs("document.querySelector('.popup-close').click()")
  splash:wait(0.5)
  return {html = splash:html(), title = title}
end

Isto é útil para fechar banners de cookies, fechar janelas modais ou extrair um valor calculado antes de capturar o HTML da página. Também pode passar JavaScript através do js_source parâmetro num SplashRequest padrão (sem necessidade de Lua), que executa o JS após o carregamento da página, mas antes de ser obtida a captura de ecrã do HTML.

Usar proxies com o Scrapy Splash

A rotação do seu endereço IP ajuda a evitar bloqueios em qualquer volume significativo de scraping. Encaminhe os pedidos através de um proxy do Scrapy Splash, passando os detalhes nos argumentos do SplashRequest:

yield SplashRequest(
    url='https://example.com',
    callback=self.parse,
    args={
        'wait': 2,
        'proxy': 'http://user:pass@proxyhost:port',
    },
)

Também pode configurar o proxy dentro de um script Lua utilizando splash:on_request():

function main(splash, args)
  splash:on_request(function(request)
    request:set_proxy{
      host = "proxyhost",
      port = 8080,
      username = "user",
      password = "pass",
    }
  end)
  splash:go(args.url)
  splash:wait(2)
  return splash:html()
end

A abordagem Lua permite-lhe aplicar proxies diferentes a sub-solicitações diferentes dentro do mesmo carregamento de página. Tenha em mente que o Splash em si não contorna sistemas anti-bot; apenas renderiza a página. Continua a precisar de proxies residenciais ou de centros de dados devidamente alternados para evitar bloqueios ao nível do IP.

Erros comuns e resolução de problemas

É aqui que a maioria dos tutoriais do Scrapy Splash o deixa na mão. Aqui estão os erros que encontrará com mais frequência:

Conexão recusada em localhost:8050. O contêiner Docker do Splash não está em execução. Verifique com docker ps. Se estiver a funcionar mas inacessível, verifique se a porta 8050 não está bloqueada pela sua firewall ou ocupada por outro processo.

504 Tempo de espera do gateway esgotado. A página demorou mais tempo a renderizar do que o tempo de espera permitido. Aumente o timeout argumento no seu SplashRequest. O limite padrão é de aproximadamente 90 segundos. Para renderizações mais demoradas, inicie o contentor com um valor mais elevado --max-timeout (verifique a documentação atual do Splash, pois os detalhes podem variar entre versões).

Erros de script Lua ("argumento inválido", "tentativa de indexar um valor nulo"). Estes erros geralmente significam splash:select() retornados nil porque o elemento ainda não estava no DOM. Adicione uma espera ou um ciclo de sondagem antes de interagir com ele.

Contentor Docker encerrado (OOM). O Splash pode consumir uma quantidade significativa de memória em páginas pesadas. Defina os limites de memória do Docker com --memory 2g e desative o carregamento de imagens (images=0). Para várias instâncias, utilize o Docker Compose com restrições de recursos por contentor.

HTML vazio ou incompleto devolvido. O JavaScript da página pode precisar de mais tempo. Aumente wait. Se os recursos de terceiros forem lentos, defina resource_timeout para os ignorar.

Scrapy Splash vs Scrapy-Playwright vs Selenium

A escolha da ferramenta de renderização certa depende do seu projeto. Aqui está uma comparação das três opções mais comuns no panorama de alternativas ao Scrapy Splash:

Funcionalidade

Scrapy Splash

Scrapy-Playwright

Selenium

Motor do navegador

WebKit (baseado em Qt)

Chromium, Firefox, WebKit

Chrome, Firefox, Edge

Integração com o Scrapy

Nativa (scrapy-splash)

Nativo (scrapy-playwright)

Requer middleware personalizado

Suporte assíncrono

Limitado (API HTTP)

Totalmente assíncrono (baseado em Playwright)

Sincronizado por padrão

Utilização de recursos

Baixo a moderado

Moderado

Elevado

Suporte a JS moderno

Parcial (WebKit mais antigo)

Total (Chromium)

Total

Contorno anti-bot

Nenhum integrado

Nenhum integrado

Nenhum integrado

Ideal para

Renderização JS leve em grande escala

SPAs complexas, sites JS modernos

Projetos não Scrapy, testes

O Splash é a escolha certa quando se pretende uma sobrecarga mínima e as páginas de destino não dependem de APIs de navegador de última geração. Para aplicações modernas de página única, o Scrapy-Playwright com o seu backend Chromium é provavelmente a melhor opção. O Selenium funciona, mas carece de integração nativa com o Scrapy. Nenhuma destas ferramentas lida com a proteção anti-bot por si só, pelo que continuará a precisar de uma camada de proxy para a extração em produção. Use este tutorial do Scrapy Splash como base e explore alternativas quando o projeto assim o exigir.

Pontos-chave

  • O Splash é executado no Docker e liga-se ao Scrapy através de uma API HTTP. Assim que o contentor estiver na porta 8050 e settings.py estiver configurado, os seus spiders podem renderizar páginas JavaScript com uma única chamada SplashRequest.
  • Use scripts Lua quando precisar de interação. Esperas fixas cobrem casos simples, mas a pesquisa de elementos, loops de rolagem e ações de clique requerem o execute ponto final com um script Lua.
  • Os proxies são essenciais para a extração em produção. O Splash renderiza páginas, mas não contorna as proteções anti-bot. Encaminhe os pedidos através de proxies rotativos utilizando argumentos SplashRequest ou splash:on_request() em Lua.
  • O Splash é leve, mas está a ficar desatualizado. Integra-se perfeitamente com o Scrapy, mas o seu motor WebKit não suporta algumas APIs JavaScript modernas. Avalie o Scrapy-Playwright para sites que necessitem de um backend Chromium.
  • Resolva os problemas de forma sistemática. A maioria dos problemas do Splash resume-se a tempos de espera, elementos em falta ou limites de recursos do Docker.

Perguntas frequentes

O Scrapy Splash consegue lidar com aplicações de página única construídas com React ou Vue?

Consegue renderizar muitas aplicações React e Vue, mas os resultados dependem das APIs JavaScript que a aplicação utiliza. O Splash funciona num motor WebKit mais antigo, pelo que as aplicações que dependem de funcionalidades modernas do navegador (como o IntersectionObserver ou a sintaxe ES2020+) podem não ser renderizadas corretamente. Teste o seu URL de destino na interface web do Splash em localhost:8050 antes de criar um spider completo.

Quanta memória precisa um contentor Docker do Splash para a extração em produção?

Preveja pelo menos 1 a 2 GB por instância para cargas de trabalho típicas. Páginas com imagens pesadas ou JavaScript complexo podem aumentar o consumo de memória. Desative o carregamento de imagens com images=0 para reduzir o consumo e defina o sinalizador --memory para impedir que um único contentor esgote os recursos do anfitrião.

O Scrapy Splash ainda é mantido e quais são as alternativas ativas?

O Splash recebe atualizações esporádicas e já não está sob desenvolvimento ativo de funcionalidades. Continua a funcionar para muitos casos de utilização, mas a comunidade mudou em grande parte para o Scrapy-Playwright para novos projetos. O Selenium continua a ser uma opção fora do ecossistema do Scrapy. Cada ferramenta tem vantagens e desvantagens em termos de suporte ao motor do navegador, capacidades assíncronas e utilização de recursos.

Como se passam cookies ou cabeçalhos personalizados através do SplashRequest?

Inclua uma cookies chave no args ou defina cabeçalhos utilizando o headers . Num script Lua, utilize splash:set_custom_headers() antes de chamar splash:go(). Os cookies do cookie jar do Scrapy são encaminhados automaticamente quando SplashCookiesMiddleware estiver ativado nas suas configurações.

Conclusão

Este tutorial do Scrapy Splash guiou-o ao longo de todo o fluxo de trabalho: criar um contentor Splash, configurar o seu projeto Scrapy, escrever spiders com SplashRequest, criar scripts Lua para rolagem e cliques e configurar proxies. Os padrões de resolução de problemas abordados aqui devem poupar-lhe horas de depuração.

O Splash lida bem com tarefas de renderização simples, mas, neste momento, é uma tecnologia mais antiga. Se os seus alvos ultrapassam os limites do JavaScript moderno, avalie alternativas baseadas no Chromium. Independentemente da ferramenta de renderização que escolher, o verdadeiro gargalo na extração de dados em produção raramente é o navegador; é ultrapassar as defesas anti-bot e gerir a infraestrutura de proxies em escala.

Se preferir evitar completamente as dores de cabeça relacionadas com a infraestrutura, a WebScrapingAPI gere a rotação de proxies, a resolução de CAPTCHAs e a renderização de JavaScript por trás de um único ponto de extremidade da API, para que se possa concentrar na análise de dados em vez de lutar com a parte técnica.

Sobre o autor
Ștefan Răcilă, Desenvolvedor Full Stack @ WebScrapingAPI
Ștefan RăcilăDesenvolvedor Full Stack

Stefan Racila é engenheiro de DevOps e Full Stack na WebScrapingAPI, onde desenvolve funcionalidades do produto e mantém a infraestrutura que garante a fiabilidade da plataforma.

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.