
Arachne — o scraper que virou plataforma
Contexto
O Arachne começou como um script Python de ~200 linhas. Literalmente um scraper.py que baixava página, jogava no BeautifulSoup, extraía texto e salvava em JSON. Nada mais.
O problema é que esse script quebrava toda semana. Site mudava o seletor, Cloudflare começava a bloquear, a página carregava via JS e o BeautifulSoup pegava vazio. Eu passava mais tempo apagando incêndio do que extraindo dado.
A gota d’água foi quando perdi uma base de dados inteira porque o scrapers de um e-commerce mudou o HTML sem aviso. 2000+ registros corrompidos. Ali eu decidi: vou construir algo que não quebre.
O que eu não sabia é que esse “algo” ia virar uma plataforma com 3763 arquivos, 740 módulos Python, 1460 rotas de API, 2698 testes, e 36 ferramentas ReAct. Mas bora por partes.
O scraper de 200 linhas
O primeiro commit era ridiculamente simples:
import requests
from bs4 import BeautifulSoup
def scrape(url):
resp = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'})
soup = BeautifulSoup(resp.text, 'html.parser')
return {
'title': soup.title.text if soup.title else '',
'text': soup.get_text(separator='\n', strip=True),
'links': [a['href'] for a in soup.find_all('a', href=True)]
}
Funcionava pra sites estáticos. Mas sites reais são tudo menos estáticos: Cloudflare, CAPTCHA, renderização JS, rate limiting, anti-bot. Cada obstáculo virava um try/except novo. O código virou uma monstruosidade de 2000 linhas em 3 meses.
Aí veio a decisão: ou eu pagava serviço de scraping caro (ScrapingBee, ScrapingAnt — tipo R$200-500/mês), ou usava ferramentas pesadas (Scrapy — 15 minutos pra configurar projeto), ou construía do meu jeito.
Eu escolhi a terceira opção. Óbvio.
A evolução para multi-engine
Em vez de um scraper só, criei camadas progressivas de fallback:
| Engine | Quando usar | Latência | Cobertura |
|---|---|---|---|
| Trafilatura (std) | Páginas estáticas, blogs, docs | ~0.5s | 70% dos sites |
| Crawl4AI (sidecar) | SPA, React, Vue, JS pesado | ~1.5s | 85% |
| Playwright (stealth) | Cloudflare, antibot leve | ~4s | 92% |
| Camoufox (full evasão) | WAF agressivo, Captcha | ~8s | 98% |
class ProgressiveFetcher:
def __init__(self):
self.engines = [
TrafilaturaEngine(),
Crawl4AIEngine(),
PlaywrightStealthEngine(),
CamoufoxEngine(),
]
def fetch(self, url):
for engine in self.engines:
result = engine.try_fetch(url)
if result and not result.error:
return result
raise AllEnginesFailed(url)
Cada engine tem um try_fetch() que retorna None se falhar. O segredo não é ter um scraper perfeito — é ter 4 que juntos cobrem 98% dos casos.
O resultado? Zero manutenção manual de fallback. Se o Trafilatura toma 403, o Crawl4AI tenta com JS. Se o Crawl4AI toma block, o Playwright tenta com stealth. Se falha, Camoufox entra com fingerprinting real de Firefox.
RAG com chunking semântico
Com os dados vindo, o próximo problema era achar o que importava. Ter 10 mil páginas extraídas não adianta se você não consegue pesquisar.
Aí veio o RAG — mas não o RAG genérico de “joga tudo no vector store”. Cada etapa foi pensada:
| Etapa | Técnica | Resultado |
|---|---|---|
| Chunking | 6 estratégias (recursivo, semântico, por parágrafo, sliding window, por tópico, markdown-aware) | 98% de recall |
| Embedding | sentence-transformers + nomic-embed-text (local) | Latência ~200ms |
| Busca | Híbrida FTS5 + vetorial (sqlite-vec) | Precisão 94% vs 78% só vetorial |
| Reranking | Cross-encoder | Acurácia top-3: 96% |
# Seis estratégias de chunking, uma linha cada
strategies = {
'recursive': RecursiveCharacterTextSplitter(500, 50),
'semantic': SemanticChunker(embedding_model),
'paragraph': ParagraphSplitter(min_chars=200),
'sliding_window': SlidingWindowChunker(300, 100),
'topic': TopicAwareSplitter(),
'markdown': MarkdownStructureSplitter(),
}
O pulo do gato foi a busca híbrida. Vector search sozinho é bom pra similaridade semântica mas péssimo pra busca por keyword exata. FTS5 sozinho é o oposto. Juntando os dois com ponderação adaptativa (FTS5 60% + vetorial 40%) a precisão subiu de 78% pra 94%.
def hybrid_search(query, kb_id, top_k=10):
fts5_results = search_fts5(query, kb_id) # keyword match
vector_results = search_vector(query, kb_id) # semantic match
merged = merge_ranked(fts5_results, vector_results,
fts5_weight=0.6, vector_weight=0.4)
return rerank_cross_encoder(query, merged[:top_k])
Pipelines: do CLI ao visual builder
Com extração + RAG funcionando, veio a inevitável demanda automatizar fluxos. Nasceu o pipeline executor: 38 handlers que se encadeiam.
Registry de handlers:
├── Entrada: url, text, file, webhook, cron, api
├── Extração: scrape, extract, vision, transcribe
├── Processamento: chunk, embed, summarize, translate, classify
├── Transformação: format, template, aggregation, filter
├── Saída: save, export, notify, webhook_callback
No começo era só YAML config. Depois veio o builder visual — drag & drop dos handlers. Depois o scheduler cron integrado. Depois a criação de pipeline via chat (“cria um pipeline que toda semana extrai notícias de AI e salva em JSON”).
| Feature | Como era antes | Como é agora |
|---|---|---|
| Criar pipeline | YAML manual | Chat, drag & drop ou YAML |
| Executar | CLI manual | CRON, webhook ou manual |
| Monitorar | logs no terminal | Dashboard com gráficos |
| Testar | impossível | Sandbox + step debug |
MCP Tools: o scraper vira servidor de ferramentas
A cereja do bolo foi transformar tudo em MCP Server (Model Context Protocol). Isso significa que qualquer agente de IA (Claude, Cursor, Hermes) pode usar as ferramentas do Arachne sem configurar nada — só conectar no stdio ou SSE.
# run_mcp.py — modo auto-detect: SSE se PORT definida, stdio se não
import os, sys
from app.mcp.server import MCPServer
server = MCPServer()
server.register_tools([
arachne_scrape, arachne_browser_extract,
arachne_search, arachne_query,
arachne_transcribe, arachne_vision,
arachne_calc, arachne_screenshot,
arachne_format_converter, arachne_record,
arachne_metrics, arachne_plan,
arachne_capabilities,
])
if 'PORT' in os.environ:
server.run_sse(port=int(os.environ['PORT']))
else:
server.run_stdio()
13 ferramentas MCP hoje. De um scraper de 200 linhas a um servidor de ferramentas que roda dentro do Cursor, Claude e Hermes. Cada ferramenta tem fallchain própria, timeouts, cache, e evasão automática.
As métricas reais
Nada de teoria — os números de produção hoje:
| Métrica | Valor | Nota |
|---|---|---|
| Latência média (std) | 1.8s | Trafilatura direto |
| Latência (stealth) | 4.2s | Playwright com evasão |
| Latência (full) | 7.5s | Camoufox com fingerprint |
| Acurácia extração | 94.7% | Conteúdo principal vs ruído |
| Cobertura (std) | 71% | Sites sem bloqueio |
| Cobertura (4 engines) | 98.2% | Com fallback progressivo |
| Cobertura (com browser-run) | 99.1% | Com interação manual |
| Precisão RAG top-3 | 96% | Híbrido + cross-encoder |
| Testes | 2.698 | 740 arquivos Python |
| Handlers de pipeline | 38 | 6 categorias |
| Ferramentas ReAct | 36 | Agent layer |
| Chaves i18n | 1.131 | PT/EN sem diff |
O que aprendi nessa jornada
1. Fallback progressivo > scraper perfeito
Gastei meses tentando fazer UM scraper que resolvesse tudo. A solução real foi ter 4 scrapers que se complementam. 98.2% de cobertura vs 70% de qualquer engine isolada. A melhor engenharia é não depender de um único ponto de falha.
2. RAG sem chunking inteligente é lixo
No começo usei chunk fixo de 500 caracteres. O resultado era bizarro — frases cortadas no meio, contexto perdido, embedding inútil. 6 estratégias de chunking com detecção automática do tipo de conteúdo resolveu. Pra blog: markdown-aware. Pra PDF: parágrafo. Pra código: sliding window.
3. FTS5 + vetorial > qualquer um sozinho
Vector search é hype, mas FTS5 ganha em busca por termo exato por uma margem enorme. A combinação híbrida com cross-encoder reranker no topo deu 96% de acurácia. Sem o reranker, caía pra 89%.
4. API-first desde o começo
Cada feature nova no Arachne nasce como API. Só depois vem UI, CLI, MCP tool. Isso força design limpo e permite qualquer frontend consumir. O MCP Server foi de graça porque as APIs já existiam.
# Toda ferramenta MCP é uma API existente + wrapper
@router.post("/scrape")
async def scrape_endpoint(url: str = Body(...)):
engine = select_engine(url) # lógica real
result = await engine.extract(url) # lógica real
return result # reuso total
5. 740 arquivos Python não é feature — é responsabilidade
O Arachne cresceu mais do que eu planejei. 3763 arquivos no total. Isso cobra seu preço em manutenção. O segredo foi manter a arquitetura em camadas desde o início: API → agente → pipeline → scraper/RAG. Cada camada sabe o mínimo sobre a outra.
O que vem a seguir
O Arachne não é mais um scraper — é uma plataforma de inteligência de dados. Mas ainda tem chão:
- SDK Python público (
pip install arachne-sdk) — em andamento - Bot Platform — builder no-code de bots
- BYO LLM — traga seu próprio modelo
- Diretório MCP oficial — submeter pros servidores oficiais modelcontextprotocol
De 200 linhas de Python a 3763 arquivos, 36 ferramentas ReAct e 13 MCP tools. Tudo porque um BeautifulSoup quebrou num dia de chuva e eu tive uma ideia idiota de “vou fazer melhor”.
TL;DR: Um scraper de 200 linhas virou plataforma multi-engine com 98% de cobertura, RAG com chunking semântico (96% precisão), pipeline visual com 38 handlers, e MCP Server com 13 ferramentas. Tudo em Python + FastAPI, open source, rodando local. A lição: fallback progressivo vence scraper perfeito, busca híbrida vence vector-only, e API-first paga dividendos pra sempre.