Artur Sousadev
Home Find me About Curriculum

A evolução do Web Scraping: Do bs4 e Selenium aos Agentes

O que mudou no web scraping, pelo menos na minha visão?

O formato das páginas web continua parecido, mas a forma de extrair os dados mudou bastante. Antes o foco era muito manual, mapeando cada elemento. Hoje, com o mercado focado em permitir que agentes e LLMs "leiam" sites, surgiram soluções que abstraem grande parte do HTML em formatos mais limpos.

Como era há alguns anos

Lembro de quando comecei a estudar sobre extração de dados e começar a brincar com isso, a stack usadas em tutoriais e artigos era quase sempre a mesma: BeautifulSoup (bs4), Scrapy ou Selenium.

O fluxo de trabalho costumava ser bem rígido:

  • Inspecionar a página: Eu abria o DevTools do navegador e ficava procurando as divs, spans, IDs e classes CSS específicas que continham a informação que eu queria.

  • Lidando com sites dinâmicos: Se a página fosse estática, usar a biblioteca requests junto com o bs4 resolvia facilmente. Mas se o conteúdo dependesse de JavaScript para carregar, o comum era subir um navegador via código com o Selenium (Uma opção que descobri depois foi a biblioteca requests-html que permitia isso de forma simplificada). O script acabava dependendo de tempos de espera (time.sleep) para garantir que a página carregasse antes de extrair o dado.

  • Manutenção contínua: Se o desenvolvedor do site alvo decidisse mudar a classe do preço de .product-price-v2 para .price-container-new, o script parava de funcionar e eu precisava atualizar o mapeamento. Com alguns projetos era bem comum, eu deixar funcionando, voltar 1 mes depois e simplesmente ter quebrado.

O fluxo comum

Exemplo com bs4

import requests
from bs4 import BeautifulSoup

url = "https://site-alvo.com/artigo"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

# Dependendo de as classes permanecerem as mesmas
titulo = soup.find('h1', class_='post-title-main').text
paragrafos = soup.find_all('p', class_='content-text-v3')

conteudo = "\n".join([p.text for p in paragrafos])

A mudança do mercado e a demanda por contexto

Acredito que o que impulsionou o avanço nessa área recentemente não foi apenas a busca por ferramentas com foco em DX (Developer Experience), mas também uma mudança de foco do próprio mercado.

Com a popularização das Inteligências Artificiais, começou a haver uma necessidade muito grande de fornecer dados e contexto para LLMs e Agentes. Um modelo de linguagem não lê uma página da mesma forma que um humano; ja que ele não precisa de menus laterais, banners de anúncios ou pop-ups de cookies. Só precisa da informação limpa, preferencialmente em Markdown ou em um JSON estruturado.

Acredito que exatamente essa demanda do mercado por integrar a web às IAs que fez surgirem novas abordagens e soluções para o scraping.

O cenário atual: Firecrawl e Crawl4AI

Com essa nova necessidade do mercado, comecei a testar soluções modernas e é absurdo o quanto elas resolvem os problemas que antes me tomavam horas.

Um exemplo que me impressionou muito foi o Firecrawl. Ele é basicamente uma API que resolve tudo por você. Em vez de subir um navegador ou mapear seletores, você apenas pede para a API processar a URL.

A simplicidade do Firecrawl

from firecrawl import FirecrawlApp

app = FirecrawlApp(api_key="MINHA_API_KEY")

# A API lida com JS, proxies e anti-bots e já devolve o Markdown limpo
resultado = app.scrape_url(
    '[https://site-alvo.com/artigo](https://site-alvo.com/artigo)', 
    params={'formats': ['markdown']}
)

print(resultado['markdown'])

O resultado é um texto totalmente limpo, um formato pronto para ser usado em um pipeline de RAG (Retrieval-Augmented Generation) ou jogado no prompt de um LLM.

Mas o que me fez achar essa ferramenta realmente um absurdo é o pacote completo de funcionalidades focadas no que realmente precisa hoje em dia:

  • Mapeamento de domínio (Map / Crawl): Antes, se eu quisesse pegar todos os links de um blog ou de uma documentação, eu precisava escrever um crawler recursivo, lidar com links duplicados e torcer para não cair em um loop infinito. Com o Firecrawl, você passa a URL principal e usa o comando de map ou crawl. A própria API navega pelo site, entende a estrutura e te devolve todas as subpáginas relevantes de uma vez.

  • Extração com LLM nativo: Se você não quiser o texto todo, não precisa baixar o Markdown para depois processar. Você pode enviar um prompt ou um schema na própria requisição, e o Firecrawl já faz o trabalho de ler, resumir ou estruturar os dados em JSON (usando IA por trás) antes mesmo de te devolver a resposta.

  • Metadados e Branding: Além do conteúdo da página, a resposta deles traz um nível de detalhe muito bom. Eles extraem automaticamente informações de SEO, favicon, links de redes sociais e o "branding" geral do site. Tudo isso já vem mastigado nos metadados do JSON de retorno, poupando um trabalho enorme de buscar essas tags no <head> do HTML.

O poder do Open-Source: Rodando local com Crawl4AI

Mas nem sempre eu quero depender apenas de uma API fechada para fazer o trabalho inteiro (sinceramente, o Firecrawl é bem caro para clientes como eu). Quando preciso rodar algo localmente, tendo mais controle sobre o fluxo e os custos, ferramentas open-source como o Crawl4AI tem sido bem úteis.

O legal dele é que ele não te obriga a usar Inteligência Artificial para tudo. Se você quiser fazer um scraping rápido, ele faz o "básico" muito bem: roda de forma assíncrona, renderiza o JavaScript e já vem com várias opções de filtros nativos. Você pode extrair o Markdown limpo, focar apenas na tag <main>, remover links e imagens, ou buscar elementos específicos por CSS, tudo isso sem precisar chamar a API de um LLM.

Extração Estruturada

Porém, quando preciso de dados muito específicos, a extração estruturada guiada por IA é algo que simplifica muito o trabalho antigo.

Em vez de usar o BeautifulSoup para procurar a classe do preço de um produto, o foco passa a ser o formato do dado que eu quero retornar. Eu posso subir o crawler e usar um modelo de linguagem para extrair exatamente a estrutura que eu defini via código (usando Pydantic, por exemplo).

Veja como funciona hoje:

import asyncio
from pydantic import BaseModel, Field
from crawl4ai import AsyncWebCrawler
from crawl4ai.extraction_strategy import LLMExtractionStrategy

# 1. Eu defino a estrutura do dado que eu quero
class Produto(BaseModel):
    nome: str = Field(description="Nome do produto")
    preco: float = Field(description="Preço atual do produto")
    em_estoque: bool = Field(description="O produto está disponível?")

async def extrair_dados():
    # 2. Inicio o crawler localmente
    async with AsyncWebCrawler(verbose=True) as crawler:
        resultado = await crawler.arun(
            url="https://loja.com/produto-123",
            # 3. Passo a minha estrutura e digo o que o LLM deve fazer
            extraction_strategy=LLMExtractionStrategy(
                provider="meumodel/muito-bom",
                api_token="MINHA_API_KEY",
                schema=Produto.model_json_schema(),
                instruction="Extraia as informações principais deste produto."
            )
        )
        
        print(resultado.extracted_content)

asyncio.run(extrair_dados())

O que acontece aqui?

Se o layout do site mudar amanhã, o script continua funcionando. A ferramenta analisa a página, junta o HTML (já pré-limpo pelos filtros nativos dela) com a instrução do LLM e tenta preencher os valores do meu JSON com base no contexto.

Tudo isso sem depender de uma classe CSS específica que o desenvolvedor do site pode alterar a qualquer momento.

Conclusão

Isso significa que o BeautifulSoup ou o Selenium perderam sua utilidade? Não. Para scripts menores, testes ou automações em sites mais simples, eles continuam sendo ótimas opções e eu ainda os utilizo.

Mas o ecossistema de ferramentas de fato cresceu. Com o mercado criando tantas soluções focadas em IA, o trabalho de scraping hoje ficou muito menos voltado a "mapear seletores" e muito mais focado em "definir estruturas de dados".