--- title: Bot de Monitoramento de Precos com Apify date: 2026-07-04 summary: Ideia de um bot autohospedavel para monitorar precos em marketplaces, normalizar resultados e enviar alertas de oportunidades. tags: [apify, scraping, automacao, precos, bot] flowerColor: "#e4f0ff" revision: "Deixa claro que o limite de preço vale para o preço com frete, com um exemplo, e onde fica a frequência de cada fonte." revision_kind: expansion maturity: sprout --- # 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: 1. tentar fazer tudo do zero e perder tempo brigando com HTML, bloqueio, CAPTCHA e layout quebrando; 2. 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: ```text "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: ```yaml 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 ```text +------------------------+ | 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 ```text 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. ```sql 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. ```sql 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. ```sql 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. ```sql 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. ```sql 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. ```sql 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. ```json { "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: ```yaml 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: ```yaml 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: ```yaml 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. ```text preço_total = preço + frete ``` Se o frete não estiver disponível, o item pode ser marcado como: ```text shipping_unknown = true ``` Nesse caso, o alerta deve avisar: > Preço abaixo do limite, mas frete não identificado. O mesmo vale para o limite: `max_price` é comparado com o preço total, não com o preço do anúncio. Um Kindle anunciado por R$ 600 com R$ 80 de frete custa R$ 680 e não passa num limite de R$ 650. O `max_shipping` serve para outra coisa: descartar anúncios baratos com frete desproporcional, mesmo quando o total ainda cabe no limite. ### 11.3 Palavras proibidas padrão Para eletrônicos: ```text 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: ```text pdf resumo apostila digital xerox cópia ``` Para roupas/acessórios: ```text 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: ```text 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: ```text 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: ```text quero um e-reader colorido barato ``` IA sugere termos: ```text 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: ```json { "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: ```text 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: ```text 🚨 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. Essa frequência mora em `watch_sources.interval_minutes`, com padrão de 180 minutos. Como cada desejo tem uma linha por fonte, o mesmo Kindle pode ser checado de hora em hora na OLX e duas vezes por dia na Amazon. --- ## 17. Integração com Apify Fluxo: ```text 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: ```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 ```python 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 ```python class ApifyMercadoLivreSource(BaseApifySource): source_name = "mercado_livre" actor_id = "viralanalyzer/mercadolivre-scraper" ``` ### 18.3 OLX ```python class ApifyOlxSource(BaseApifySource): source_name = "olx" actor_id = "haketa/olx-brasil-scraper" ``` ### 18.4 Amazon ```python class ApifyAmazonSource(BaseApifySource): source_name = "amazon" actor_id = "eccentric_layout/amazon-scraper" ``` ### 18.5 Enjoei ```python class ApifyEnjoeiSource(BaseApifySource): source_name = "enjoei" actor_id = "ribtools/enjoei-scraper" ``` ### 18.6 Google Shopping ```python 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: 1. busca por termo; 2. monitoramento de URL específica. Fluxo: ```text 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: ```text 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: ```json { "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 ```yaml 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 ```env 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 1. Criar projeto Docker com FastAPI, Postgres e Redis. 2. Criar modelos principais. 3. Criar integração Telegram. 4. Criar adaptador Apify genérico. 5. Integrar Mercado Livre. 6. Integrar OLX. 7. Criar normalizador. 8. Criar motor de filtros. 9. Criar alerta. 10. Criar painel mínimo. 11. Integrar Google Shopping. 12. Integrar Amazon. 13. Integrar Enjoei. 14. Criar scraper Estante Virtual. 15. Criar Buscapé/Bright Data. 16. Criar scraper genérico CSS. 17. Criar score. 18. 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 ```text Mercado Livre via Apify OLX via Apify Google Shopping via Apify Telegram ``` ### V2 ```text Amazon via Apify Enjoei via Apify Estante Virtual próprio ``` ### V3 ```text Buscapé via Bright Data ou próprio Zoom próprio ou via Buscapé Sites pequenos via Generic CSS ``` --- ## 28. Exemplo de alerta bom ```text 🚨 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: ```text 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.