Bot de Monitoramento de Precos com Apify
Ideia de um bot autohospedavel para monitorar precos em marketplaces, normalizar resultados e enviar alertas de oportunidades.
Bot de Monitoramento de Preços com Apify + Scrapers Próprios
Projeto: bot pessoal/autohospedável para monitorar preços, encontrar oportunidades e alertar quando algo desejado aparecer abaixo de um limite.
Ideia central: usar a Apify para tudo que ela já cobre bem, porque seria burrice gastar semanas recriando scrapers que já existem. Para as fontes que não têm Actor confiável ou têm cobertura ruim, criamos scrapers próprios, pequenos, isolados e fáceis de substituir.
1. Resumo executivo
A ideia é construir um sistema chamado, provisoriamente, GarimpoBot.
Ele permite cadastrar coisas que você quer comprar, por exemplo:
- Kindle Paperwhite até R$ 600;
- Bigme B751C até R$ 1.700;
- livros específicos na Estante Virtual;
- câmera, tablet, notebook, celular usado;
- itens raros em OLX, Enjoei ou Mercado Livre;
- qualquer produto que apareça no Google Shopping.
O usuário cadastra:
- nome do desejo;
- termos de busca;
- preço máximo;
- preço mínimo opcional, para evitar golpe ou lixo;
- palavras obrigatórias;
- palavras proibidas;
- fontes onde procurar;
- frequência de busca;
- canal de alerta.
O sistema consulta as fontes, normaliza os resultados, calcula preço total, remove duplicados, filtra porcaria, compara com o limite e envia alerta por Telegram, e-mail ou outro canal.
A V1 deve ser simples, feia e funcional. Nada de construir uma plataforma gigante antes de validar se os dados chegam direito.
2. Princípio do projeto
A regra é:
Apify onde existir Actor bom. Scraper próprio onde não existir. API oficial apenas quando for claramente melhor.
Isso evita dois erros comuns:
- tentar fazer tudo do zero e perder tempo brigando com HTML, bloqueio, CAPTCHA e layout quebrando;
- depender 100% de ferramenta externa e ficar preso se algum Actor sumir, ficar caro ou começar a falhar.
A arquitetura precisa permitir trocar uma fonte sem quebrar o sistema inteiro.
3. Fontes cobertas por Apify
3.1 Mercado Livre
Existe cobertura boa na Apify para Mercado Livre Brasil.
Exemplos encontrados:
- Mercado Livre Product Scraper & Price Monitor;
- Mercado Livre Brasil Scraper Completo;
- Actors capazes de extrair título, preço, avaliações, vendedor, frete e links.
Uso no projeto: usar Apify na V1, embora a API oficial do Mercado Livre também seja uma alternativa boa.
Motivo: começar rápido. Depois podemos substituir por API oficial se fizer sentido reduzir custo ou aumentar estabilidade.
Campos desejados:
- título;
- preço;
- frete;
- preço total;
- URL;
- imagem;
- vendedor;
- reputação;
- condição;
- localização, se disponível;
- ID do anúncio.
Prioridade: alta.
3.2 OLX Brasil
A Apify tem vários Actors para OLX Brasil. Alguns aceitam keyword, categoria ou URL, e retornam título, preço, fotos, localização, vendedor, atributos e até dados úteis para monitoramento de preço.
Uso no projeto: Apify desde a V1.
Motivo: OLX é uma fonte muito importante para usados, mas scraping próprio tende a ser chato. Melhor validar com Actor pronto antes.
Campos desejados:
- título;
- preço;
- URL;
- fotos;
- cidade;
- bairro;
- estado;
- data do anúncio;
- vendedor;
- descrição;
- atributos;
- categoria.
Prioridade: altíssima.
OLX é onde aparecem bons achados, mas também muito lixo. Precisa de filtro pesado.
3.3 Amazon
A Apify tem diversos Actors para Amazon capazes de extrair produtos por keyword, ASIN ou URL, incluindo preço, avaliações, disponibilidade, vendedor, imagem e categoria.
Uso no projeto: Apify na V1 ou V2.
Motivo: a Amazon tem Product Advertising API, mas ela exige mais burocracia e regras. Para um bot pessoal, Apify pode acelerar muito.
Campos desejados:
- título;
- ASIN;
- preço;
- disponibilidade;
- vendedor;
- avaliação;
- quantidade de reviews;
- URL;
- imagem;
- categoria.
Prioridade: alta.
Observação importante: Amazon pode dar falso positivo com marketplace, variações, produto internacional, preço sem imposto/frete e itens indisponíveis. O parser precisa ser conservador.
3.4 Enjoei
Existe Actor para Enjoei na Apify, com extração de listagens, preço, vendedor, imagens e detalhes.
Uso no projeto: testar na V2.
Motivo: existe cobertura pronta, mas os sinais de maturidade parecem menores que Mercado Livre/OLX/Amazon. Não dá para confiar cegamente sem teste.
Campos desejados:
- título;
- preço;
- URL;
- imagens;
- vendedor;
- categoria;
- descrição;
- estado do produto;
- data, se disponível.
Prioridade: média/alta.
Risco: Actor quebrar, ser pouco mantido ou não extrair exatamente o que queremos.
3.5 Google Shopping
A Apify tem Actors para Google Shopping que extraem produtos por busca, com preço, loja, rating, reviews, URL e país/idioma.
Uso no projeto: Apify desde a V1 ou V2.
Motivo: scraping direto do Google é pedir CAPTCHA e dor de cabeça. Usar Apify ou SerpApi é mais sensato.
Campos desejados:
- produto;
- preço;
- loja;
- URL;
- imagem;
- avaliação;
- quantidade de reviews;
- frete, se existir;
- disponibilidade;
- país/idioma.
Prioridade: alta.
Uso ideal: encontrar lojas que não estavam no radar. O Google Shopping serve como radar amplo, não necessariamente como fonte perfeita.
3.6 Google Search geral
A Apify também tem scraper para resultados gerais do Google.
Uso no projeto: opcional.
Motivo: útil para descobrir páginas de produtos, sebos obscuros e lojas pequenas. Mas para monitoramento recorrente de preço, Google Search geral é menos limpo que Google Shopping.
Prioridade: média.
Uso ideal: modo descoberta.
Exemplo:
"kindle paperwhite usado" site:.br
"bigme b751c" "R$"
"kobo libra" "venda"
4. Fontes fora da Apify ou com cobertura ruim
4.1 Estante Virtual
Não encontrei uma solução Apify madura para Estante Virtual.
Existem alternativas parciais:
- Spider.cloud com scraper genérico para estantevirtual.com.br;
- projetos GitHub antigos ou pequenos;
- API Marketplace da Estante Virtual, aparentemente voltada mais para vendedores/livreiros do que para comprador monitorando busca pública.
Decisão: criar scraper próprio.
Motivo: Estante Virtual é muito relevante para livros, e o scraping tende a ser possível. Melhor construir um módulo específico para busca de livros, preço, sebo, condição e frete.
Campos desejados:
- título do livro;
- autor;
- preço;
- frete, se possível;
- estado/conservação;
- sebo/vendedor;
- cidade/estado do sebo;
- URL;
- imagem;
- edição/editora/ano, se disponível;
- disponibilidade.
Prioridade: alta para livros.
4.2 Buscapé / Zoom
Para Buscapé, existe uma solução pronta fora da Apify: Bright Data tem um Buscapé Scraper dedicado, prometendo extrair nome, marca, categoria, preço, desconto, avaliação, SKU, descrição, disponibilidade, URL e imagens.
Decisão: duas opções.
Opção A: usar Bright Data para Buscapé.
Opção B: criar scraper próprio se o custo da Bright Data for alto demais.
Minha recomendação: testar Bright Data primeiro, mas não tornar o sistema dependente disso.
Campos desejados:
- nome do produto;
- preço;
- loja;
- URL;
- disponibilidade;
- avaliação;
- imagem;
- categoria;
- histórico, se disponível.
Prioridade: média/alta.
Buscapé/Zoom são bons para preço de produto novo, mas menos úteis para usados e raridades.
4.3 Sites pequenos, sebos e lojas independentes
Aqui entra o scraper genérico configurável.
O usuário cadastra:
- nome do site;
- URL de busca;
- seletor CSS do item;
- seletor CSS do título;
- seletor CSS do preço;
- seletor CSS da URL;
- seletor CSS da imagem;
- modo HTTP ou Playwright.
Exemplo de configuração:
site: "Sebo Exemplo"
source_type: "generic_css"
search_url: "https://exemplo.com.br/busca?q=kindle"
item_selector: ".product-card"
title_selector: ".product-title"
price_selector: ".price"
url_selector: "a"
image_selector: "img"
rendering: "http"
Prioridade: média.
Isso transforma o sistema em uma ferramenta flexível, sem exigir que cada lojinha tenha um scraper codado do zero.
5. Arquitetura geral
+------------------------+
| Painel Web |
| Cadastro de desejos |
+-----------+------------+
|
v
+------------------------+
| Backend FastAPI |
| Regras, filtros, API |
+-----------+------------+
|
v
+------------------------+
| Scheduler / Worker |
| Roda buscas periódicas |
+-----------+------------+
|
v
+------------------------+
| Fontes |
| Apify + próprios |
+-----------+------------+
|
v
+------------------------+
| Normalizador |
| Padroniza resultados |
+-----------+------------+
|
v
+------------------------+
| PostgreSQL |
| Itens + histórico |
+-----------+------------+
|
v
+------------------------+
| Motor de alerta |
| Telegram/e-mail/etc. |
+------------------------+
6. Stack recomendada
Backend
- Python;
- FastAPI;
- Pydantic;
- SQLAlchemy ou SQLModel;
- PostgreSQL;
- Redis;
- RQ, Celery ou APScheduler.
Scraping próprio
- httpx;
- selectolax;
- BeautifulSoup, se necessário;
- Playwright para sites com JavaScript pesado;
- tenacity para retry;
- fake-useragent ou headers controlados;
- rate limit por domínio.
Integração Apify
- Apify API Client;
- chamada por Actor ID;
- coleta do dataset;
- conversão para formato interno.
Interface
Para V1:
- FastAPI + templates Jinja;
- HTMX;
- Bootstrap simples ou PicoCSS.
Para V2:
- Next.js, se realmente precisar.
Não faz sentido começar com frontend sofisticado. O gargalo é dado confiável, não botão bonito.
Notificações
V1:
- Telegram Bot.
Depois:
- e-mail;
- WhatsApp via API externa;
- ntfy;
- Discord;
- webhook genérico.
7. Estrutura de pastas
garimpobot/
├── docker-compose.yml
├── .env.example
├── README.md
├── app/
│ ├── main.py
│ ├── config.py
│ ├── database.py
│ ├── models/
│ │ ├── watchlist.py
│ │ ├── source.py
│ │ ├── item.py
│ │ ├── price_history.py
│ │ └── alert.py
│ ├── api/
│ │ ├── watchlists.py
│ │ ├── items.py
│ │ ├── sources.py
│ │ └── alerts.py
│ ├── workers/
│ │ ├── scheduler.py
│ │ ├── run_watch.py
│ │ └── queue.py
│ ├── sources/
│ │ ├── base.py
│ │ ├── apify/
│ │ │ ├── mercadolivre.py
│ │ │ ├── olx.py
│ │ │ ├── amazon.py
│ │ │ ├── enjoei.py
│ │ │ ├── google_shopping.py
│ │ │ └── google_search.py
│ │ ├── custom/
│ │ │ ├── estante_virtual.py
│ │ │ ├── buscape.py
│ │ │ └── generic_css.py
│ │ └── brightdata/
│ │ └── buscape.py
│ ├── normalize/
│ │ ├── money.py
│ │ ├── text.py
│ │ ├── dedupe.py
│ │ └── scoring.py
│ ├── notifications/
│ │ ├── telegram.py
│ │ ├── email.py
│ │ └── webhook.py
│ └── templates/
│ ├── layout.html
│ ├── watchlists.html
│ ├── items.html
│ └── alerts.html
└── data/
├── screenshots/
└── html_cache/
8. Modelo de dados
watchlists
Representa algo que você quer comprar.
CREATE TABLE watchlists (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
max_price NUMERIC(12,2),
min_price NUMERIC(12,2),
max_shipping NUMERIC(12,2),
required_words TEXT[],
blocked_words TEXT[],
preferred_words TEXT[],
enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
watch_sources
Representa onde e como buscar aquele desejo.
CREATE TABLE watch_sources (
id SERIAL PRIMARY KEY,
watchlist_id INTEGER REFERENCES watchlists(id),
source_name TEXT NOT NULL,
source_type TEXT NOT NULL,
search_query TEXT,
search_url TEXT,
apify_actor_id TEXT,
config JSONB,
interval_minutes INTEGER DEFAULT 180,
enabled BOOLEAN DEFAULT TRUE,
last_checked_at TIMESTAMP,
created_at TIMESTAMP DEFAULT now()
);
scraped_items
Resultado normalizado vindo de qualquer fonte.
CREATE TABLE scraped_items (
id SERIAL PRIMARY KEY,
source_name TEXT NOT NULL,
source_item_id TEXT,
title TEXT NOT NULL,
normalized_title TEXT,
url TEXT NOT NULL,
image_url TEXT,
price NUMERIC(12,2),
shipping NUMERIC(12,2),
total_price NUMERIC(12,2),
currency TEXT DEFAULT 'BRL',
seller TEXT,
seller_rating TEXT,
location TEXT,
condition TEXT,
availability TEXT,
raw JSONB,
content_hash TEXT,
first_seen_at TIMESTAMP DEFAULT now(),
last_seen_at TIMESTAMP DEFAULT now()
);
price_history
Histórico de preço.
CREATE TABLE price_history (
id SERIAL PRIMARY KEY,
scraped_item_id INTEGER REFERENCES scraped_items(id),
price NUMERIC(12,2),
shipping NUMERIC(12,2),
total_price NUMERIC(12,2),
checked_at TIMESTAMP DEFAULT now()
);
alerts
Alertas enviados.
CREATE TABLE alerts (
id SERIAL PRIMARY KEY,
watchlist_id INTEGER REFERENCES watchlists(id),
scraped_item_id INTEGER REFERENCES scraped_items(id),
channel TEXT NOT NULL,
reason TEXT,
sent_at TIMESTAMP DEFAULT now()
);
ignored_items
Itens ignorados manualmente para evitar alerta repetido de lixo.
CREATE TABLE ignored_items (
id SERIAL PRIMARY KEY,
watchlist_id INTEGER REFERENCES watchlists(id),
item_url TEXT,
content_hash TEXT,
reason TEXT,
created_at TIMESTAMP DEFAULT now()
);
9. Formato interno padronizado
Todos os scrapers, Apify ou próprios, devem retornar o mesmo contrato.
{
"source_name": "olx",
"source_item_id": "abc123",
"title": "Kindle Paperwhite 11ª geração 16GB",
"url": "https://...",
"image_url": "https://...",
"price": 520.00,
"shipping": 0.00,
"total_price": 520.00,
"currency": "BRL",
"seller": "João",
"seller_rating": null,
"location": "Belo Horizonte, MG",
"condition": "used",
"availability": "available",
"raw": {}
}
Sem esse contrato, o projeto vira bagunça. Cada fonte pode ser caótica internamente, mas a saída precisa ser uniforme.
10. Cadastro de desejos
Exemplo real:
name: "Kindle Paperwhite"
description: "Quero um Kindle Paperwhite usado ou novo, mas não acessórios."
max_price: 650
min_price: 250
max_shipping: 80
required_words:
- kindle
blocked_words:
- capa
- case
- película
- caixa
- defeito
- quebrado
- peças
- assistência
preferred_words:
- paperwhite
- 11ª geração
- 16gb
sources:
- mercado_livre
- olx
- amazon
- google_shopping
- enjoei
Outro exemplo:
name: "Bigme B751C"
max_price: 1700
min_price: 900
required_words:
- bigme
blocked_words:
- capa
- caneta
- película
- case
preferred_words:
- b751c
- color
sources:
- mercado_livre
- olx
- google_shopping
- amazon
Exemplo para livros:
name: "O Castelo dos Cárpatos - Júlio Verne"
max_price: 80
required_words:
- castelo
- cárpatos
blocked_words:
- resumo
- pdf
- apostila
preferred_words:
- júlio verne
- estante virtual
sources:
- estante_virtual
- mercado_livre
- google_shopping
11. Motor de filtros
O motor precisa evitar alertas inúteis.
11.1 Regras obrigatórias
Um item só pode virar alerta se:
- tiver preço válido;
- tiver URL válida;
- não estiver na lista de ignorados;
- não tiver sido alertado recentemente;
- estiver abaixo do preço máximo;
- estiver acima do preço mínimo, se definido;
- não contiver palavras bloqueadas;
- contiver palavras obrigatórias;
- tiver preço total aceitável.
11.2 Preço total
O sistema nunca deve alertar apenas pelo preço do produto.
preço_total = preço + frete
Se o frete não estiver disponível, o item pode ser marcado como:
shipping_unknown = true
Nesse caso, o alerta deve avisar:
Preço abaixo do limite, mas frete não identificado.
11.3 Palavras proibidas padrão
Para eletrônicos:
capa
case
película
carregador
cabo
caixa
manual
defeito
quebrado
peças
retirada de peças
bloqueado
não liga
assistência
sucata
Para livros:
pdf
resumo
apostila
digital
xerox
cópia
Para roupas/acessórios:
réplica
primeira linha
inspirado
similar
12. Deduplicação
O mesmo produto pode aparecer várias vezes ou mudar levemente de título.
Deduplicação deve usar:
- URL canônica;
- ID da fonte;
- hash do título + preço + vendedor;
- similaridade de texto;
- imagem, futuramente;
- ASIN, quando Amazon;
- ID do anúncio, quando Mercado Livre ou OLX.
Exemplo:
Kindle Paperwhite 11 geração 16gb
Kindle Paperwhite 16 GB 11ª geração
Amazon Kindle Paperwhite 2021
Podem ser o mesmo item ou produtos equivalentes. O sistema não precisa resolver isso perfeitamente na V1, mas precisa evitar spam óbvio.
13. Score de oportunidade
Cada resultado pode receber uma nota de 0 a 100.
Critérios:
- preço abaixo do limite;
- desconto em relação ao histórico;
- vendedor confiável;
- frete baixo;
- título bate bem com o desejo;
- descrição não contém sinais ruins;
- anúncio é recente;
- fonte é confiável;
- produto não parece acessório.
Exemplo:
Score 92: forte candidato
Score 70: bom, mas verificar
Score 45: possível falso positivo
Score 20: lixo provável
O alerta só deve ser automático acima de certo score.
14. Uso de IA
IA não deve ser usada para fazer scraping. Isso é caro, lento e desnecessário.
IA deve entrar em três pontos:
14.1 Expandir busca
Usuário digita:
quero um e-reader colorido barato
IA sugere termos:
bigme b751c
bigme color
kobo colour
boox color
onyx boox color
kindle colorsoft
14.2 Classificar candidato
A IA recebe título, descrição e preço e responde:
{
"matches_intent": true,
"is_accessory": false,
"risk": "low",
"reason": "Parece ser o produto principal, não acessório, e está abaixo do limite."
}
14.3 Explicar alerta
Em vez de só mandar link, o bot envia:
Achei um Kindle Paperwhite por R$ 520 na OLX.
Parece bater com seu desejo porque o título menciona Paperwhite e não tem sinais de ser capa, caixa ou produto quebrado.
Atenção: frete não identificado. Verifique antes de comprar.
Importante: IA não deve inventar preço médio. Preço médio só pode vir do histórico real coletado.
15. Alertas
15.1 Telegram na V1
Formato recomendado:
🚨 Achado encontrado
Kindle Paperwhite 11ª geração 16GB
Fonte: OLX
Preço: R$ 520,00
Frete: não identificado
Local: Belo Horizonte, MG
Limite configurado: R$ 650,00
Score: 88/100
Motivo:
Produto parece bater com sua busca e está abaixo do preço máximo.
Abrir:
https://...
Ações:
/ignorar abc123
/comprado abc123
/silenciar kindle-paperwhite 24h
15.2 E-mail
Útil para resumo diário.
15.3 WhatsApp
Possível, mas eu deixaria para depois. WhatsApp sempre adiciona complexidade desnecessária no início.
15.4 ntfy
Boa opção simples e autohospedável.
16. Frequência de busca
Não seja burro de colocar tudo a cada 5 minutos. Isso custa dinheiro, gera bloqueio e polui alerta.
Sugestão inicial:
| Fonte | Frequência padrão |
|---|---|
| OLX | 30 a 120 minutos |
| Mercado Livre | 1 a 3 horas |
| Google Shopping | 3 a 6 horas |
| Amazon | 6 a 12 horas |
| Enjoei | 2 a 6 horas |
| Estante Virtual | 2 a 6 horas |
| Buscapé/Zoom | 6 a 12 horas |
| Sites pequenos | 12 a 24 horas |
Para itens muito desejados, permitir frequência especial.
17. Integração com Apify
Fluxo:
1. Worker pega uma watch_source do tipo apify.
2. Monta input do Actor.
3. Executa Actor via Apify API.
4. Aguarda conclusão.
5. Lê dataset de saída.
6. Normaliza campos.
7. Salva no banco.
8. Roda filtros.
9. Envia alerta se necessário.
Exemplo conceitual em Python:
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
run = client.actor("ACTOR_ID").call(run_input={
"query": "kindle paperwhite",
"maxItems": 50,
"country": "BR"
})
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
Cada fonte Apify terá um adaptador, porque cada Actor retorna campos diferentes.
18. Adaptadores Apify
18.1 Base
class BaseApifySource:
source_name: str
actor_id: str
def build_input(self, watch_source):
raise NotImplementedError
def normalize_item(self, raw_item):
raise NotImplementedError
18.2 Mercado Livre
class ApifyMercadoLivreSource(BaseApifySource):
source_name = "mercado_livre"
actor_id = "viralanalyzer/mercadolivre-scraper"
18.3 OLX
class ApifyOlxSource(BaseApifySource):
source_name = "olx"
actor_id = "haketa/olx-brasil-scraper"
18.4 Amazon
class ApifyAmazonSource(BaseApifySource):
source_name = "amazon"
actor_id = "eccentric_layout/amazon-scraper"
18.5 Enjoei
class ApifyEnjoeiSource(BaseApifySource):
source_name = "enjoei"
actor_id = "ribtools/enjoei-scraper"
18.6 Google Shopping
class ApifyGoogleShoppingSource(BaseApifySource):
source_name = "google_shopping"
actor_id = "automation-lab/google-shopping-scraper"
Observação: os Actor IDs devem ser configuráveis no banco ou .env, não fixos para sempre no código. Actor de marketplace pode sumir, mudar preço ou ficar ruim.
19. Scrapers próprios
19.1 Estante Virtual
O scraper da Estante Virtual deve ter dois modos:
- busca por termo;
- monitoramento de URL específica.
Fluxo:
1. Monta URL de busca.
2. Baixa HTML.
3. Tenta extrair dados estruturados embutidos, se existirem.
4. Se falhar, faz parse do HTML.
5. Se HTML vier incompleto, usa Playwright.
6. Normaliza livros encontrados.
7. Salva histórico.
Campos especiais:
- autor;
- editora;
- ano;
- edição;
- estado de conservação;
- nome do sebo;
- localização do sebo.
Esse scraper precisa ser tratado como módulo importante, não gambiarra.
19.2 Buscapé/Zoom
Primeiro caminho: Bright Data.
Segundo caminho: scraper próprio.
Fluxo scraper próprio:
1. Recebe termo ou URL de busca.
2. Extrai cards de produto.
3. Captura menor preço, loja e disponibilidade.
4. Entra na página de ofertas se necessário.
5. Normaliza resultado.
Risco: páginas podem ter JS, anti-bot ou estrutura variável.
19.3 Generic CSS Scraper
Serve para lojas pequenas.
Entrada:
{
"url": "https://site.com/busca?q=kindle",
"item_selector": ".product-card",
"title_selector": ".title",
"price_selector": ".price",
"url_selector": "a",
"image_selector": "img",
"rendering": "http"
}
Isso não substitui scraper dedicado, mas permite adicionar fontes rapidamente.
20. Dashboard
A interface mínima precisa ter:
20.1 Tela de desejos
- listar desejos ativos;
- criar novo desejo;
- editar preço máximo;
- pausar desejo;
- executar busca manual;
- ver última execução.
20.2 Tela de fontes
- fonte;
- tipo: Apify, próprio, Bright Data, genérico;
- intervalo;
- status;
- última execução;
- último erro;
- custo estimado, se disponível.
20.3 Tela de resultados
- título;
- fonte;
- preço;
- frete;
- total;
- score;
- data;
- botão abrir;
- botão ignorar;
- botão marcar como comprado.
20.4 Tela de histórico
- gráfico de preço;
- menor preço visto;
- maior preço visto;
- média;
- última queda;
- fonte mais barata.
21. Docker Compose
services:
app:
build: ./app
restart: unless-stopped
env_file: .env
ports:
- "8088:8000"
depends_on:
- postgres
- redis
worker:
build: ./app
restart: unless-stopped
command: python -m app.workers.run_worker
env_file: .env
depends_on:
- postgres
- redis
scheduler:
build: ./app
restart: unless-stopped
command: python -m app.workers.scheduler
env_file: .env
depends_on:
- postgres
- redis
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: garimpobot
POSTGRES_USER: garimpobot
POSTGRES_PASSWORD: garimpobot
volumes:
- ./data/postgres:/var/lib/postgresql/data
redis:
image: redis:7
restart: unless-stopped
volumes:
- ./data/redis:/data
22. Variáveis de ambiente
APP_URL=http://localhost:8088
DATABASE_URL=postgresql://garimpobot:garimpobot@postgres:5432/garimpobot
REDIS_URL=redis://redis:6379/0
APIFY_TOKEN=
BRIGHTDATA_TOKEN=
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
OPENAI_API_KEY=
ENABLE_AI_CLASSIFIER=false
23. Fases de implementação
Fase 0 — prova de conceito
Objetivo: provar que conseguimos buscar dados da Apify e enviar alerta.
Escopo:
- uma watchlist fixa no código;
- Mercado Livre via Apify;
- OLX via Apify;
- alerta Telegram;
- SQLite ou Postgres simples.
Critério de sucesso:
- rodar busca manual;
- salvar resultados;
- alertar item abaixo do preço.
Fase 1 — MVP utilizável
Escopo:
- painel web básico;
- cadastro de desejos;
- Mercado Livre via Apify;
- OLX via Apify;
- Google Shopping via Apify;
- Telegram;
- PostgreSQL;
- histórico de preço;
- filtros de palavras obrigatórias/proibidas;
- evitar alerta duplicado.
Critério de sucesso:
- cadastrar 5 desejos reais;
- rodar por uma semana;
- receber alertas úteis;
- ignorar falsos positivos.
Fase 2 — expansão de fontes
Escopo:
- Amazon via Apify;
- Enjoei via Apify;
- Estante Virtual scraper próprio;
- Buscapé via Bright Data ou scraper próprio;
- generic CSS scraper;
- logs melhores;
- screenshot/html cache em erro.
Critério de sucesso:
- cobrir os principais tipos de produto: usados, novos, livros e marketplaces.
Fase 3 — inteligência
Escopo:
- score de oportunidade;
- deduplicação melhor;
- IA opcional para classificar falso positivo;
- expansão automática de termos de busca;
- resumo diário.
Critério de sucesso:
- reduzir spam;
- melhorar qualidade dos alertas;
- descobrir oportunidades que a busca literal não pegaria.
Fase 4 — produto bonito
Escopo:
- dashboard refinado;
- gráficos;
- múltiplos usuários;
- categorias;
- importação/exportação;
- API pública;
- mobile-friendly.
Só faz sentido depois que o motor funcionar. Interface bonita em sistema burro é maquiagem em cadáver.
24. Ordem prática de desenvolvimento
- Criar projeto Docker com FastAPI, Postgres e Redis.
- Criar modelos principais.
- Criar integração Telegram.
- Criar adaptador Apify genérico.
- Integrar Mercado Livre.
- Integrar OLX.
- Criar normalizador.
- Criar motor de filtros.
- Criar alerta.
- Criar painel mínimo.
- Integrar Google Shopping.
- Integrar Amazon.
- Integrar Enjoei.
- Criar scraper Estante Virtual.
- Criar Buscapé/Bright Data.
- Criar scraper genérico CSS.
- Criar score.
- Criar IA opcional.
25. Riscos reais
25.1 Actor da Apify quebrar
Mitigação:
- Actor ID configurável;
- adaptadores isolados;
- permitir múltiplos Actors por fonte;
- fallback para scraper próprio.
25.2 Custo da Apify subir
Mitigação:
- cache;
- frequência controlada;
- só rodar fonte útil;
- substituir fontes caras por scraper próprio.
25.3 Falso positivo demais
Mitigação:
- palavras proibidas;
- min_price;
- score;
- botão ignorar;
- IA opcional.
25.4 Bloqueios em scraper próprio
Mitigação:
- rate limit;
- Playwright só quando necessário;
- cache;
- retry com backoff;
- não insistir quando detectar CAPTCHA.
25.5 Preço sem frete
Mitigação:
- marcar frete desconhecido;
- não fingir precisão;
- permitir regra de alerta mesmo com frete desconhecido.
26. O que não fazer
Não fazer:
- frontend complexo na V1;
- WhatsApp na primeira versão;
- scraping direto do Google no braço;
- Selenium para tudo;
- busca a cada 5 minutos em todas as fontes;
- IA analisando todo item bruto;
- scraper monolítico;
- salvar apenas preço atual sem histórico;
- alertar sem dedupe;
- ignorar frete.
Se fizer isso, o projeto vira brinquedo quebrado.
27. Fontes recomendadas por prioridade
V1 obrigatória
Mercado Livre via Apify
OLX via Apify
Google Shopping via Apify
Telegram
V2
Amazon via Apify
Enjoei via Apify
Estante Virtual próprio
V3
Buscapé via Bright Data ou próprio
Zoom próprio ou via Buscapé
Sites pequenos via Generic CSS
28. Exemplo de alerta bom
🚨 GarimpoBot encontrou algo
Kindle Paperwhite 11ª geração 16GB
Fonte: OLX
Preço: R$ 520,00
Frete: não identificado
Local: Belo Horizonte, MG
Limite: R$ 650,00
Score: 88/100
Por que parece bom:
- contém Kindle e Paperwhite;
- está abaixo do limite;
- não parece capa, caixa ou acessório;
- preço está dentro de uma faixa plausível.
Atenção:
- frete não identificado;
- confirme estado do produto com o vendedor.
Abrir anúncio:
https://...
29. Fontes pesquisadas
- Apify — Mercado Livre Product Scraper & Price Monitor: https://apify.com/viralanalyzer/mercadolivre-scraper
- Apify — Mercado Livre Brasil Scraper Completo: https://apify.com/leadercorp/mercadolivre-scraper-br-pro
- Apify — OLX Brazil Scraper: https://apify.com/haketa/olx-brasil-scraper
- Apify — OLX Brazil Scraper, solidcode: https://apify.com/solidcode/olx-brazil-scraper
- Apify — Enjoei Scraper: https://apify.com/ribtools/enjoei-scraper
- Apify — Google Shopping Scraper: https://apify.com/automation-lab/google-shopping-scraper
- Apify — Amazon Product Scraper: https://apify.com/eccentric_layout/amazon-scraper
- Bright Data — Buscapé Scraper: https://brightdata.com.br/products/web-scraper/buscape
- Spider.cloud — Estante Virtual generic scraper: https://spider.cloud/scrapers/estantevirtual-com-br-scraper
- GitHub — estantevirtual_scrapper: https://github.com/aquillesf/estantevirtual_scrapper
30. Conclusão
A ideia correta é construir o GarimpoBot como um agregador de fontes de preço, não como “um scraper gigante”.
O núcleo do produto não é scraping. O núcleo é:
- cadastrar desejos;
- consultar fontes;
- normalizar resultados;
- filtrar lixo;
- comparar preço;
- manter histórico;
- alertar rápido;
- aprender o que deve ser ignorado.
Apify resolve boa parte do trabalho pesado em Mercado Livre, OLX, Amazon, Enjoei e Google Shopping. Para Estante Virtual e algumas fontes brasileiras específicas, criamos scrapers próprios. Para Buscapé, testamos Bright Data ou fazemos nosso módulo.
A melhor decisão técnica é começar pequeno, mas com arquitetura limpa:
Apify primeiro.
Scrapers próprios onde necessário.
Tudo normalizado no mesmo formato.
PostgreSQL para histórico.
Telegram para alerta.
Painel simples.
Sem firula.
Esse projeto é totalmente viável. O risco não é técnico. O risco é escopo. Se tentar nascer como “monitor universal de tudo”, vai morrer. Se nascer como “bot pessoal que monitora 5 fontes bem e alerta sem encher o saco”, tem grande chance de funcionar e evoluir.