requests + BeautifulSoup vs. uma scraping API: quando o DIY é suficiente
Para sites estáticos sem anti-bot, requests e BeautifulSoup não custam nada e funcionam bem. Aqui está exatamente onde isso deixa de ser verdade.

Existe uma versão deste post que tenta vender uma API para um problema que requests e BeautifulSoup já resolvem. Este não é esse post. Se o site que você está fazendo scraping é HTML estático sem anti-bot na frente, o caminho DIY não custa nada e funciona bem, e você não deveria pagar nada para fazer isso.
Quando o DIY é realmente suficiente
Sem rodeios: para uma página estática, um script e uma tarefa pouco frequente, essa é a solução inteira.
import requests
from bs4 import BeautifulSoup
resp = requests.get("https://example.com/products", timeout=30)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
for card in soup.select(".product-card"):
name = card.select_one(".product-name").get_text(strip=True)
price = card.select_one(".price").get_text(strip=True)
print(name, price)
Nenhuma dependência além de duas bibliotecas bem conhecidas, nenhuma conta de proxy, nenhum crédito gasto. Isso é correto para: um site que renderiza HTML completo no servidor, sem CAPTCHA ou bloqueio por IP depois de algumas requisições, e uma tarefa que você roda ocasionalmente, não continuamente contra milhares de URLs. Um script pontual para um projeto de faculdade, uma ferramenta interna que verifica um punhado de páginas por dia, um acompanhamento pessoal de preço em algumas páginas de produto: tudo isso é território DIY, e colocar uma API na frente disso é puro overhead.
Onde isso para de funcionar
Quatro coisas quebram essa configuração, e elas quebram de forma independente, por isso “só adiciona proxies” ou “só adiciona um wait()” só remenda um sintoma de cada vez:
Anti-bot. Desafios do Cloudflare, PerimeterX, rate limiting que escala para um CAPTCHA ou um 403 direto depois de um punhado de requisições. requests não tem fingerprint de navegador, nenhum comportamento de cookie jar que imite uma sessão real, e nenhuma forma de resolver um desafio. Isso não é um problema de headers que você resolve copiando uma string de User-Agent; sites que se importam com isso detectam especificamente a ausência de um navegador real.
Renderização JS. Se o dado que você quer é injetado por JavaScript depois que o HTML inicial carrega (um app React ou Vue, conteúdo carregado via uma chamada XHR em segundo plano, scroll infinito), resp.text simplesmente não contém isso. BeautifulSoup faz parsing do que o servidor enviou, não do que o navegador renderizaria. Esse é o motivo mais comum de um scraper que funcionava passar a retornar resultados vazios da noite para o dia: o site não mudou o bloqueio, mudou a renderização.
Escala. Uma dezena de páginas por dia é um script. Dez mil páginas por dia é infraestrutura: gerenciamento de concorrência, lógica de retry, um pool de proxies dimensionado para o volume de requisições, monitoramento da taxa de banimento subindo. Nada disso é difícil individualmente, mas juntos é uma pequena infraestrutura que agora você possui e precisa manter rodando.
Manutenção. Seletores apodrecem. Um redesign de site muda .product-card para .product-tile e seu parser silenciosamente passa a não retornar nada, ou pior, retorna o campo errado mapeado para o nome certo. Esse é o custo que não aparece na primeira semana e nunca desaparece de vez.
A tabela de decisão
| Sinal | DIY (requests + BeautifulSoup) | Scraping API |
|---|---|---|
| HTML estático, sem bloqueio, baixo volume | Sim | Exagero |
| Tarefa ocasional / pontual | Sim | Exagero |
| Site retorna 403/CAPTCHA depois de poucas requisições | Não | Sim (premium=true) |
| Conteúdo carregado via JavaScript depois do carregamento da página | Não | Sim (dynamic=true) |
| Milhares de páginas, sem supervisão, em um cronograma | Doloroso de manter | Sim |
| Parser precisa sobreviver a redesigns sem você perceber | Nenhuma garantia de qualquer forma | Campos JSON estruturados reduzem (não eliminam) isso |
Quando você cruza a linha
Na FastAF, /scrape custa 1 crédito para uma página estática, exatamente o caso acima, mas sem você mesmo rodar a requisição:
import requests
API_KEY = "faf_live_..."
resp = requests.get(
"https://api.fastaf.co/v1/scrape",
params={"url": "https://example.com/products"},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60,
)
resp.raise_for_status()
A mesma chamada com dynamic=true (5 créditos) renderiza a página com um navegador real primeiro, então o conteúdo injetado por JavaScript fica presente na resposta. premium=true (10 créditos) roteia através de uma rede residencial para sites que bloqueiam por reputação de IP. Os dois juntos (25 créditos) cobrem uma página renderizada em JS que também precisa parecer um visitante residencial real, que é a combinação mais difícil e mais cara, e que só vale a pena buscar quando o site realmente exige isso.
O objetivo dos níveis de crédito é que você paga exatamente pelo problema que tem. Uma página estática nunca precisa custar mais de 1 crédito, e pagar por premium=true em um site sem anti-bot é dinheiro gasto em um problema que você não tem.
Resumindo
Comece com requests e BeautifulSoup. É de graça e é suficiente para boa parte das tarefas de scraping. Recorra a uma API especificamente quando você bater em um dos quatro modos de falha acima, anti-bot, renderização JS, escala, ou um peso de manutenção que você não quer carregar, e pague por URL exatamente por esse problema em vez de construir um pool de proxies e um hábito de manutenção de parser em torno disso. O plano gratuito te dá 500 créditos sem cartão se você quiser testar onde seus alvos reais caem nessa tabela.