Voltar ao blogue
Guias
Mihai MaximLast updated on May 13, 202618 min read

Raspagem da Web com Scrapy: Manual 2026

Raspagem da Web com Scrapy: Manual 2026
Resumo: Este é um guia completo e com uma visão pessoal sobre web scraping com o Scrapy em 2026. Irá instalar o Scrapy, criar protótipos de seletores no terminal, construir um spider de comércio eletrónico com várias páginas, limpar itens com Item Loaders, guardar dados numa base de dados, reforçar as configurações contra bloqueios e integrar o Scrapy-Playwright para páginas renderizadas em JavaScript.

O Scrapy tem sido a espinha dorsal do crawling sério em Python há mais de uma década e, apesar de uma onda de bibliotecas assíncronas mais recentes, continua a merecer o seu lugar. Se estiver a fazer web scraping com o Scrapy hoje em dia, terá uma estrutura com uma abordagem bem definida que resolve as partes enfadonhas (agendamento de pedidos, deduplicação, novas tentativas, pipelines de itens) para que se possa concentrar nas partes que realmente falham: seletores, anti-bot e armazenamento.

Este guia está estruturado em torno do ciclo de vida das solicitações e respostas, em vez de uma abordagem cronológica. Cada secção corresponde a um componente do Scrapy com o qual irá lidar em produção, desde o motor e os middlewares de download até aos carregadores de itens e exportações de feeds. Utilizamos um único alvo ao longo de todo o guia, o site de prática pública books.toscrape.com, para que cada bloco de código se encaixe num único modelo mental.

No final, terá um spider executável que pagina um catálogo, valida e limpa itens, grava tanto em JSON Lines como em SQLite, faz novas tentativas em 429 casos de erros e recorre a um navegador real quando uma página necessita de JavaScript. Também iremos assinalar as partes da estrutura que os novatos utilizam incorretamente de forma consistente, com correções que podem ser copiadas.

Por que é que o Scrapy continua a dominar a extração em produção em 2026

É tentador recorrer a httpx mais selectolax e dar o assunto por encerrado. Para um script pontual, essa é a escolha certa. Para um rastreador que tem de ser executado todas as noites, deduplicar URLs, sobreviver a uma falha parcial e escrever em dois destinos, é necessária uma estrutura. O Scrapy continua a ser o padrão da indústria para a extração de dados em grande escala no momento em que este artigo foi escrito, e a razão é simples: vem com o agendador, o filtro de duplicados, o middleware de repetição, o limitador, os sinais e as exportações de feeds já integrados.

Em comparação com a combinação de requests e do `BeautifulSoup`, o Scrapy é assertivo de uma forma útil. Ele é executado no loop de eventos do Twisted, pelo que um único processo pode distribuir centenas de pedidos simultâneos sem a sobrecarga cognitiva de async/await. Não é necessário escrever o ciclo de rastreamento. Basta declarar os URLs de entrada e a lógica de análise, e o motor trata da fila. É esse contrato que faz com que o Scrapy valha a pena, apesar da curva de aprendizagem mais acentuada.

Como funciona o web scraping com o Scrapy: o ciclo de vida da solicitação e da resposta

Antes de escrever um spider, internalize o ciclo de vida. Uma execução do Scrapy tem o seguinte aspeto:

  1. O motor retira uma Request do agendador.
  2. A solicitação passa pelos middlewares do downloader (por ordem de prioridade). É aqui que os cabeçalhos são definidos, os cookies são anexados, os proxies são alternados e as tentativas de repetição são acionadas.
  3. O downloader emite a chamada HTTP e devolve um Response.
  4. A resposta volta a passar pelos middlewares do downloader no caminho de entrada, depois pelos middlewares do spider e, por fim, para o callback do seu spider (normalmente parse).
  5. A sua callback yieldretorna mais Request objetos (que voltam para o agendador) ou Item(que fluem para os pipelines de itens).
  6. Os pipelines validam, transformam, descartam ou persistem cada item.
  7. Tudo o que sobreviver é entregue ao exportador de feeds, que grava em disco, no S3 ou no stdout.

Dois termos que irá encontrar nos callbacks: callback é a função que o Scrapy executa quando um pedido é bem-sucedido, e errback é a função que executa quando um pedido falha. Os spiders são normalmente escritos como geradores Python, produzindo pedidos e itens de forma preguiçosa para que o motor possa intercalar o trabalho.

Conhecer este ciclo é a diferença entre «o meu spider funciona» e «o meu spider escala». Quando as páginas voltam vazias, a resposta está quase sempre na camada de middleware do downloader. Quando os itens desaparecem, a resposta está num pipeline. Quando a paginação falha, é o seu callback. Relacione o sintoma com a fase e, em seguida, corrija o componente certo.

Um guia mais detalhado encontra-se na documentação oficial da arquitetura do Scrapy, que vale a pena marcar como favorito.

Instalar o Scrapy e iniciar um projeto

O Scrapy destina-se ao Python 3 moderno (consulte o guia de instalação oficial para saber a versão mínima no momento da instalação). A documentação recomenda vivamente um ambiente virtual dedicado para que as dependências fixadas do Scrapy não entrem em conflito com os pacotes do sistema.

python -m venv .venv
source .venv/bin/activate     # Windows: .venv\Scripts\activate
pip install --upgrade pip
pip install scrapy
scrapy version

Assim que scrapy version imprimir uma string de versão, crie a estrutura de um projeto:

scrapy startproject bookstore
cd bookstore

Agora tem uma árvore de projeto que se parece com todas as outras em todas as bases de código do Scrapy no mundo, o que é exatamente o objetivo. Sempre que entrar num novo repositório do Scrapy, já saberá onde estão os spiders, onde se encontram as configurações e qual o ficheiro que contém os pipelines. Essa repetibilidade é metade do valor de usar uma estrutura, para começar. Resista à tentação de simplificar o layout: ferramentas a jusante como scrapyd e scrapy crawl dependem disso.

Dentro de um projeto Scrapy: o que cada ficheiro faz

scrapy startproject produz cinco ficheiros e uma pasta com os quais irá lidar diariamente.

  • scrapy.cfg é a configuração de nível superior do projeto. Dá nome ao projeto e indica scrapyd onde encontrar o módulo de configurações.
  • items.py é a camada de esquema. Define Product, Article, ou quaisquer outras classes que desejar, cada uma herdando de scrapy.Item. Trate isto como uma classe de dados para a saída extraída.
  • pipelines.py é onde os itens extraídos são limpos, validados, descartados ou gravados numa base de dados. Cada pipeline é uma classe simples com um process_item método.
  • middlewares.py contém middlewares de downloader e spider. Este é o ficheiro onde se alternam user agents, se associam proxies ou se encaminham pedidos através de uma API de scraping gerida.
  • settings.py é o objeto de configuração central: concorrência, limitação, tentativas, pipelines, middlewares e exportações de feeds ficam todos aqui.
  • spiders/ é a pasta onde residem os ficheiros individuais do spider. Um spider por site de destino é um padrão recomendável.

Prototipagem de seletores no Scrapy Shell

O shell do Scrapy é a arma secreta de que ninguém fala o suficiente. Antes de escrever uma única linha de código de spider, abra o shell com um URL real e itere sobre os seletores de forma interativa. Poupa horas.

scrapy shell "https://books.toscrape.com/catalogue/page-1.html"

Dentro do shell, obtém-se um response pré-carregado com a página. Três comandos são importantes:

  • fetch("https://example.com") troca por uma nova resposta sem sair do shell.
  • view(response) abre o HTML descarregado no teu navegador predefinido, o que te permite confirmar que estás a trabalhar com o mesmo DOM que o spider vê, e não com o renderizado que o teu navegador normalmente mostraria.
  • response.css(...) e response.xpath(...) permite-lhe testar seletores na resposta ativa.

Experimente isto no site de treino:

>>> response.css("article.product_pod h3 a::attr(title)").getall()[:3]
['A Light in the Attic', 'Tipping the Velvet', 'Soumission']
>>> response.xpath("//article[@class='product_pod']//p[@class='price_color']/text()").get()
'£51.77'

Repita até que ambos os seletores retornem dados válidos. Só então mova a expressão para o seu spider. O custo de depurar um XPath com erros dentro de um rastreamento de 5 minutos é muito maior do que o custo de uma sessão de shell.

Escrever o seu primeiro spider para web scraping com o Scrapy

Gere um esboço de spider para o seu domínio de destino:

scrapy genspider books books.toscrape.com

Isso cria spiders/books.py. Substitua o seu conteúdo pelo spider abaixo. Este faz o scraping da página inicial do catálogo, extrai o título, preço e classificação de cada livro e, em seguida, gera um dicionário Python por livro. Iremos atualizá-lo para itens reais numa secção posterior.

import scrapy

class BooksSpider(scrapy.Spider):
    name = "books"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/catalogue/page-1.html"]

    def parse(self, response):
        for card in response.css("article.product_pod"):
            yield {
                "title": card.css("h3 a::attr(title)").get(),
                "price": card.css("p.price_color::text").get(),
                "rating": card.css("p.star-rating::attr(class)").get(),
                "url": response.urljoin(card.css("h3 a::attr(href)").get()),
            }

Execute-o a partir da raiz do projeto:

scrapy crawl books -o books.jsonl

Deve ver o Scrapy a registar um pedido para a página 1, vinte itens extraídos e, em seguida, um encerramento normal. Abra books.jsonl e confirme um objeto JSON por linha.

Algumas coisas a ter em conta. start_urls é o ponto de entrada; o motor agenda cada URL automaticamente. parse é o callback padrão. response.urljoin resolve um href em relação à página atual, para que não fique com links quebrados. O rating campo ainda contém ruído como "star-rating Three", que é exatamente o tipo de limpeza que os Carregadores de Itens irão tratar mais tarde.

Nota de produção: executar com -o é adequado para um teste rápido, mas nunca confie nisso numa tarefa agendada. Configure a FEEDS configuração em settings.py para que o destino de saída, o formato e o comportamento de sobrescrita sejam controlados por versão. Vamos ligar isso a um pipeline de base de dados na secção de persistência. Trate o sinalizador da CLI como um atalho de desenvolvimento, não como um artefacto de implementação.

CSS vs XPath: Escolhendo Seletores que Não Quebram

Ambos os motores de seleção vêm com o Scrapy e ambos funcionam com a mesma árvore analisada. Use aquele que for mais curto e claro para a tarefa. Como regra geral, o CSS ganha em consultas baseadas em classes e estruturais, enquanto o XPath ganha quando é necessário percorrer a árvore por conteúdo de texto, por irmãos ou por ancestrais.

# CSS: short, idiomatic, fast to write
response.css("article.product_pod p.price_color::text").get()

# XPath equivalent
response.xpath("//article[@class='product_pod']//p[@class='price_color']/text()").get()

O XPath ganha o seu lugar quando o CSS não consegue expressar o que precisa:

# "Find the <td> that follows the <th> whose text is 'Stock'"
response.xpath("//th[normalize-space()='Stock']/following-sibling::td/text()").get()

# "Find all links whose visible text contains 'Next'"
response.xpath("//a[contains(., 'Next')]/@href").getall()

Alguns hábitos que mantêm os seletores estáveis: prefira seletores de atributos em vez dos frágeis seletores posicionais (nth-child(3) acabam por falhar), normalize os espaços em branco quando comparar texto (normalize-space()), e combine .get() para uma única correspondência com .getall() para uma lista, nunca indexe o resultado de .getall() cegamente. Para uma comparação mais aprofundada sobre quando cada mecanismo é a escolha certa, o nosso guia Seletores XPath vs CSS é uma boa leitura complementar.

Nota de produção: quando um seletor retorna None em produção mas funciona no shell, a página foi provavelmente renderizada por JavaScript. Confirme com view(response) antes de culpar o seletor.

Itens e carregadores de itens: padrões de limpeza reutilizáveis

Gerar dicionários simples é adequado para dez linhas de código. Em escala, é preferível um esquema tipado para que um erro ortográfico num nome de campo falhe rapidamente, em vez de produzir silenciosamente linhas inválidas. Defina um Item em items.py:

import scrapy
from itemloaders.processors import MapCompose, TakeFirst, Join

def to_float(value):
    return float(value.replace("£", "").replace("$", "").strip())

def normalize_rating(value):
    # "star-rating Three" -> "Three"
    parts = value.split()
    return parts[1] if len(parts) > 1 else value

class ProductItem(scrapy.Item):
    title = scrapy.Field(input_processor=MapCompose(str.strip), output_processor=TakeFirst())
    price = scrapy.Field(input_processor=MapCompose(str.strip, to_float), output_processor=TakeFirst())
    rating = scrapy.Field(input_processor=MapCompose(normalize_rating), output_processor=TakeFirst())
    description = scrapy.Field(input_processor=MapCompose(str.strip), output_processor=Join(" "))

MapCompose transformadores de cadeias, TakeFirst colapsa uma lista de correspondências para um único valor e Join junta vários parágrafos num só. Use um carregador no spider para que este se mantenha legível:

from scrapy.loader import ItemLoader
from bookstore.items import ProductItem

def parse(self, response):
    for card in response.css("article.product_pod"):
        loader = ItemLoader(item=ProductItem(), selector=card)
        loader.add_css("title", "h3 a::attr(title)")
        loader.add_css("price", "p.price_color::text")
        loader.add_css("rating", "p.star-rating::attr(class)")
        yield loader.load_item()

A vantagem é a reutilização. Uma vez to_float reside em items.py, todos os itens com preço em todas as spiders podem chamá-lo. A lógica de limpeza deixa de ser copiada e colada em callbacks.

Existem duas formas idiomáticas de rastrear várias páginas no Scrapy. Escolha com base na previsibilidade da estrutura de links.

A paginação manual é a escolha certa quando há um único link "próximo" a seguir. Adicione isto no final de parse:

next_page = response.css("li.next a::attr(href)").get()
if next_page:
    yield response.follow(next_page, callback=self.parse)

response.follow lida com URLs relativas e reutiliza o mesmo callback, que é exatamente o que a paginação do tipo catálogo precisa. O rastreamento pára naturalmente quando o link "próximo" desaparece na página final.

O CrawlSpider é a escolha certa quando se pretende percorrer um site inteiro através da correspondência de padrões de URL. Utiliza Rule e LinkExtractor para descobrir e seguir links automaticamente:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class BooksCrawl(CrawlSpider):
    name = "books_crawl"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/"]
    rules = (
        Rule(LinkExtractor(restrict_css=".pager a")),  # follow pagination
        Rule(LinkExtractor(restrict_css="h3 a"), callback="parse_book"),
    )

    def parse_book(self, response):
        yield {
            "title": response.css("h1::text").get(),
            "price": response.css("p.price_color::text").get(),
        }

O RFPDupeFilter garante que a mesma URL não seja colocada na fila duas vezes, pelo que não precisa de acompanhar os links visitados manualmente. Defina DEPTH_LIMIT em settings.py quando rastrear um site com muitas subpáginas e quiser uma paragem definitiva.

Nota de produção: para sites compatíveis com sitemap, SitemapSpider é ainda mais simples. Ele lê /sitemap.xml diretamente e permite-lhe filtrar padrões de URL com sitemap_rules.

Persistência de resultados: FEEDS e um pipeline de base de dados

A extração da Web com o Scrapy oferece duas camadas de persistência, e normalmente vai querer ambas. A configuração FEEDS lida com exportações estruturadas gratuitamente, enquanto um pipeline controla destinos personalizados, como uma base de dados relacional.

Configure feeds em settings.py. Consulte a documentação de exportações de feeds do Scrapy para ver a sintaxe atual, mas uma configuração moderna fica mais ou menos assim:

FEEDS = {
    "data/books.jsonl": {
        "format": "jsonlines",
        "encoding": "utf-8",
        "overwrite": True,
    },
    "data/books.csv.gz": {
        "format": "csv",
        "postprocessing": ["scrapy.extensions.postprocessing.GzipPlugin"],
    },
}

JSON Lines é a predefinição correta: transmissível, fácil de acrescentar e de carregar no Pandas ou num armazém de dados. CSV com gzip é adequado para transferência para analistas. Ambos falham em consultas relacionais, e é aí que entram os pipelines.

Um pipeline SQLite que é executado após um validador:

# pipelines.py
import sqlite3
from itemadapter import ItemAdapter

class SqlitePipeline:
    def open_spider(self, spider):
        self.conn = sqlite3.connect("data/books.db")
        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS products (title TEXT, price REAL, rating TEXT)"
        )

    def close_spider(self, spider):
        self.conn.commit()
        self.conn.close()

    def process_item(self, item, spider):
        a = ItemAdapter(item)
        self.conn.execute(
            "INSERT INTO products(title, price, rating) VALUES (?, ?, ?)",
            (a["title"], a["price"], a["rating"]),
        )
        return item

Registe-o com uma prioridade. Os números mais baixos são executados primeiro, pelo que um validador com prioridade 100 é acionado antes do gravador de base de dados com prioridade 200:

ITEM_PIPELINES = {
    "bookstore.pipelines.PriceRangeValidator": 100,
    "bookstore.pipelines.SqlitePipeline": 200,
}

Agora, os preços inválidos são descartados antes mesmo de chegarem à base de dados.

Reforçar o settings.py: AutoThrottle, Retries e Caching

As configurações padrão funcionam em desenvolvimento e fazem com que seja banido em produção. As poucas abaixo são as que mais importam. Verifique os padrões exatos em relação à sua versão instalada do Scrapy.

# settings.py
ROBOTSTXT_OBEY = True            # respect the site's policy unless you have a contract
CONCURRENT_REQUESTS = 8          # global cap; lower for fragile sites
CONCURRENT_REQUESTS_PER_DOMAIN = 4
DOWNLOAD_DELAY = 0.5             # base delay; AutoThrottle adjusts dynamically

AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0
AUTOTHROTTLE_START_DELAY = 1.0
AUTOTHROTTLE_MAX_DELAY = 30.0

RETRY_ENABLED = True
RETRY_TIMES = 5
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 408, 522, 524]

HTTPCACHE_ENABLED = True         # huge time-saver during development
HTTPCACHE_EXPIRATION_SECS = 3600
HTTPCACHE_IGNORE_HTTP_CODES = [429, 500, 502, 503, 504]

AutoThrottle é a funcionalidade chave aqui. Em vez de adivinhar um DOWNLOAD_DELAY, define uma concorrência alvo e o Scrapy abranda quando a latência aumenta. Isso por si só evita a maioria das situações acidentais de DDoS em sites lentos.

HTTPCACHE_ENABLED é uma configuração que facilita a vida durante o desenvolvimento: enquanto itera sobre seletores, pedidos idênticos são recuperados do disco, para que deixe de sobrecarregar o alvo. Desative-a em produção.

Para uma pressão anti-bot real, as configurações por si só não são suficientes, e o nosso guia sobre por que razão os scrapers são bloqueados aborda os padrões mais profundos. Seja como for, a camada seguinte são os middlewares.

Middlewares de downloader: cabeçalhos, proxies e APIs geridas

Quando um site começa a devolver 403s, a solução está quase sempre num middleware de downloader. A estrutura é simples:

# middlewares.py
import random

class RandomUserAgentMiddleware:
    UAS = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 ...",
    ]
    def process_request(self, request, spider):
        request.headers["User-Agent"] = random.choice(self.UAS)

Regista-o em settings.py. O Scrapy vem com uma pilha de middlewares padrão preenchida (mais de dez ativados de fábrica), e os números de prioridade dos middlewares são normalmente expressos num intervalo de inteiros documentado. As orientações da comunidade colocam o middleware anti-bot personalizado antes do RetryMiddleware, cuja prioridade padrão é 550, para que as tentativas de repetição vejam a sua identidade rotacionada.

DOWNLOADER_MIDDLEWARES = {
    "bookstore.middlewares.RandomUserAgentMiddleware": 400,
    "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,  # disable default
}

Para rotação de proxy, defina request.meta["proxy"] em process_request. Existem plugins da comunidade tanto para proxies rotativos como para user agents aleatórios (e para rastreamento distribuído, cache persistente e monitorização), mas verifique o estado de manutenção atual de cada projeto antes de depender dele em produção para web scraping com o Scrapy em qualquer escala significativa.

A verdade nua e crua: a certa altura, gerar os teus próprios cabeçalhos, IPs residenciais e resolução de CAPTCHA transforma-se num projeto paralelo. É aí que uma API Scraper gerida se encaixa perfeitamente. Implementa um middleware que reescreva request.url para apontar para o endpoint da API e adicione a sua chave de API como um cabeçalho, e o resto do seu spider não muda.

Scrapy-Playwright: A saída de emergência do JavaScript

O Scrapy não executa JavaScript por si só, por isso sites construídos com Angular, React ou qualquer framework do lado do cliente devolvem o HTML de base e não os dados que se vêem no navegador. A solução mais limpa em 2026 para web scraping com o Scrapy em páginas dinâmicas é o scrapy-playwright, que troca o downloader padrão por um Chromium headless real quando se opta por isso em cada pedido.

Instale-o e verifique a sintaxe atual de registo do manipulador em relação ao README do scrapy-playwright no momento da instalação:

# settings.py
DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

Ative as solicitações definindo meta:

def start_requests(self):
    yield scrapy.Request(
        "https://example-spa.com/products",
        meta={
            "playwright": True,
            "playwright_page_methods": [
                ("wait_for_selector", "article.product"),
            ],
        },
    )

Marque apenas os URLs que realmente precisam de um navegador. Cada pedido do Playwright é significativamente mais dispendioso do que uma simples recuperação do Scrapy, tanto em termos de CPU como de latência, pelo que um spider híbrido (HTML para listagens, Playwright para detalhes do produto) é normalmente a abordagem correta. Se pretender um guia mais aprofundado ou uma comparação com o backend Splash mais antigo, o nosso tutorial do Scrapy-Playwright aborda os padrões em detalhe.

Registo, Contratos e Implementação

A extração de dados da Web de nível de produção com o Scrapy requer três coisas que os tutoriais geralmente ignoram.

Registo. Defina LOG_LEVEL = "INFO" em settings.py para execuções normais e "DEBUG" apenas quando algo correr mal. Envie os registos para um ficheiro com LOG_FILE ou transmita-os para um backend estruturado.

Contratos de spider. Adicione contratos docstring às chamadas de retorno e execute scrapy check em CI. Um contrato típico define a URL, os campos esperados e a contagem mínima de itens, de modo que uma alteração silenciosa no site quebra a compilação em vez do conjunto de dados.

def parse(self, response):
    """
    @url https://books.toscrape.com/
    @returns items 20 20
    @scrapes title price rating
    """

Agendamento e implementação. scrapyd Executa o seu projeto como um daemon de longa duração que pode ser implementado via scrapyd-client. Para pilhas baseadas em contentores, crie uma imagem Docker leve com o seu projeto e execute-a scrapy crawl numa programação cron (ou num CronJob do Kubernetes). Seja como for, guarde os resultados num armazenamento duradouro, e não no sistema de ficheiros do contentor.

Armadilhas comuns e como depurá-las

  • Seletores vazios. O seletor funcionava no shell, mas retorna None no spider. Quase sempre renderizado em JavaScript. Confirme com view(response) e mude para scrapy-playwright para esse URL.
  • 403 e 429 storms. A sua impressão digital é óbvia. Adicione um middleware User-Agent aleatório, reduza CONCURRENT_REQUESTS_PER_DOMAIN, aumente AUTOTHROTTLE_START_DELAYe confirme RETRY_HTTP_CODES inclui 429.
  • Loops infinitos de paginação. O seletor «próximo» também corresponde à última página. Ancore-o numa classe CSS que desapareça no final, ou defina DEPTH_LIMIT.
  • Itens descartados silenciosamente. Um pipeline gerou DropItem e você nem percebeu. Aumentar LOG_LEVEL para DEBUG, procure no log por Dropped:e valida as tuas verificações de intervalo.
  • URLs duplicadas a passar despercebidas. RFPDupeFilter corresponde por impressão digital, pelo que URLs que diferem apenas pela ordem da string de consulta podem passar despercebidas. Normalize as URLs antes de processar os pedidos.

Pontos-chave

  • A extração de dados da Web com o Scrapy compensa quando precisa de agendamento, deduplicação, novas tentativas, limitação de tráfego e pipelines interligados de forma integrada, e não quando um script de 20 linhas é suficiente.
  • Relacione cada sintoma a uma fase do ciclo de vida: os bloqueios ocorrem nos middlewares do downloader, os itens em falta ocorrem nos pipelines e as falhas do seletor apontam geralmente para a renderização JavaScript.
  • Carregadores de itens com MapCompose, TakeFirst, e Join mantenha a lógica de limpeza reutilizável entre os spiders, em vez de a copiar e colar entre callbacks.
  • Persista com FEEDS para formatos portáteis e um pipeline personalizado para armazenamento relacional. Use ambos, com as prioridades do pipeline a ordenarem a validação antes do gravador de base de dados.
  • Trate AutoThrottle, códigos de repetição de tentativa e uma API de scraping gerida como uma defesa em camadas contra bloqueios. Recorra a scrapy-playwright apenas quando o HTML estiver genuinamente vazio.

Perguntas frequentes

Ainda vale a pena aprender Scrapy em 2026, em comparação com bibliotecas assíncronas mais recentes?

Sim, para rastreamentos com mais de algumas centenas de páginas. Pilhas assíncronas mais recentes, como httpx e outras selectolax são ótimas para scripts pontuais, mas o Scrapy inclui o agendador, o filtro de duplicados, o middleware de repetição, os sinais e as exportações de feeds que, de outra forma, teria de escrever você mesmo. Para um rastreador de produção recorrente, esse design «tudo incluído» continua a ser vantajoso em termos de custos de manutenção.

O Scrapy consegue extrair páginas renderizadas em JavaScript sozinho, ou preciso do Playwright ou do Splash?

Por si só, não. O Scrapy obtém HTML bruto e não executa JavaScript, pelo que as aplicações de página única devolvem marcação shell. A melhor opção atual é scrapy-playwright, que troca o downloader por um Chromium headless real por cada pedido. scrapy-splash ainda funciona para algumas equipas, mas o Playwright tem um suporte de navegadores mais abrangente e manutenção ativa.

Como se compara o Scrapy ao Beautiful Soup e ao Selenium para projetos de diferentes dimensões?

O Beautiful Soup é um analisador, não um rastreador, e combina bem com requests para pequenas extrações estáticas. O Selenium controla um navegador completo e é ideal para fluxos interativos e com estado, como painéis de controlo com sessão iniciada. O Scrapy situa-se entre os dois: uma estrutura de rastreamento de alto rendimento para centenas a milhões de páginas, com renderização do navegador integrada através scrapy-playwright quando necessário.

Como é que implemento um spider Scrapy para ser executado de forma programada em produção?

Três padrões comuns. Execute scrapyd como um daemon e acione tarefas através da sua API HTTP. Crie uma imagem Docker com o seu projeto e agende scrapy crawl <name> através do cron ou de um CronJob do Kubernetes. Ou utilize uma plataforma de scraping gerida que hospeda spiders por si. Em todos os casos, guarde os resultados num armazenamento duradouro como o S3 ou uma base de dados, nunca num sistema de ficheiros de contentor.

Como evito que o meu spider Scrapy seja bloqueado ou tenha o IP banido?

Crie camadas de defesa. Ative AutoThrottlee randomize User-Agent cabeçalhos através de um middleware de downloader, inclua 429 em RETRY_HTTP_CODESe diminua CONCURRENT_REQUESTS_PER_DOMAIN. Para sites mais difíceis, encaminhe os pedidos através de proxies residenciais ou de uma API de scraper gerida que lida com a rotação e a resolução de CAPTCHAs por trás de um único ponto de acesso. Respeite robots.txt e os limites de taxa sempre que possível.

Conclusão

O objetivo do web scraping com o Scrapy não é escrever menos código do que com requests mais BeautifulSoup. Normalmente, escreve-se mais no primeiro dia. O objetivo é que o código que escreve no primeiro dia continue a funcionar no nonagésimo dia, porque o motor, o agendador, o filtro de duplicados, o limitador, a camada de repetição e o contrato de pipeline não mudam por baixo. Adquire-se uma base estável e, em seguida, especializa-se os spiders, os itens e os middlewares para cada site de destino.

Se internalizares uma coisa deste guia, que seja o ciclo de vida da solicitação e da resposta. Todos os bugs do Scrapy que alguma vez encontrarás ocorrem numa fase específica desse ciclo, e identificar a fase é metade da solução. Os seletores falham no callback. Os itens desaparecem no pipeline. Os bloqueios ocorrem no downloader. A paginação entra num loop infinito na tua lógica de callback. Relaciona o sintoma com a fase e a solução torna-se óbvia.

Quando a pressão anti-bot ultrapassa o que pode construir middlewares.py, esse é o momento certo para descarregar a camada de pedidos. Na WebScrapingAPI, criámos a Scraper API exatamente para essa transferência: mantém os teus spiders Scrapy, os teus Itens, os teus pipelines e deixa que um endpoint gerido lide com proxies, resolução de CAPTCHA e renderização de JavaScript. O teu spider continua a ser Scrapy. Os obstáculos passam a ser problema de outra pessoa.

Sobre o autor
Mihai Maxim, Desenvolvedor Full Stack @ WebScrapingAPI
Mihai MaximDesenvolvedor Full Stack

Mihai Maxim é um programador Full Stack na WebScrapingAPI, contribuindo em todas as áreas do produto e ajudando a criar ferramentas e funcionalidades fiáveis para a 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.