Um monitor de preços que não pode comprar, e o comprador ao lado
Duas ferramentas em Python, uma linha deliberada entre elas.

Construí um monitor de preços para sites arbitrários e, ao lado dele, uma ferramenta que reserva um anúncio quando o preço cai e entrega a um humano o código para pagar. O monitor usa só a biblioteca padrão e estruturalmente não compra; o comprador carrega o browser e o risco. Os dois conversam por uma única chave num arquivo de configuração que o monitor lê e ignora. Rodou sozinho contra um site de terceiro por onze dias, e o que vale ler são as três falhas que ele me escondeu enquanto reportava sucesso.
- Função
- Sole author and operator — architecture, the adapter contract, the alert-rule semantics, the headless checkout automation, both test suites, and the failure analysis that followed the system into production
- Período
- set. de 2026 – set. de 2026
- Stack
- PythonOpen Source
- Evidência
- Código
- Stack de produto
Eu queria um ingresso. O que construí foi um par de ferramentas que sustenta uma linha entre observar um mercado e agir sobre ele — e depois passei onze dias aprendendo, do meu próprio sistema em produção, de quantas formas um programa consegue falhar enquanto avisa que está tudo bem.
Projeto pessoal, sem cliente e sem prazo, mas com um prazo real: um show de sete noites cujo mercado secundário se movia a cada minuto. Os monitores de preço genéricos não cobrem um site assim, porque só integram com os marketplaces com quem têm acordo. Então a pergunta nunca foi "dá para raspar" — foi que forma uma coisa deve ter quando precisa rodar sozinha, todo minuto, contra um site que não me deve nada.
O reconhecimento matou três planos antes de qualquer código
O primeiro plano era interceptar a requisição de dados da página. Ela não existe — o site é um front-end Next.js sobre um backend Bubble, e todo preço é renderizado no servidor já na primeira resposta, alcançável por um curl simples, sem browser e sem cookies. O segundo plano supunha que uma página dessas não carrega dados estruturados; ela carrega um JSON-LD schema.org Event completo. O terceiro era não escrever nada e apontar uma ferramenta pronta para o site.
Esse terceiro é o que vale contar. O changedetection.io tem um modo de reposição, e seu extrator de preço é uma expressão JSONPath que casa com um campo chamado exatamente `price`. Esta página publica `lowPrice` e `highPrice`. Verifiquei rodando a expressão real contra o payload real com uma perna de controle ao lado — um objeto sintético com um campo `price` devolveu valor, e a página real não devolveu nada. Como a disponibilidade ainda resolve, a ferramenta nunca levanta erro de extração. Ela produz um monitor que parece saudável e ignora silenciosamente todo limite de preço que você configurar, e o único jeito de descobrir seria nunca receber um alerta.
A linha é o projeto
O price-watcher mantém dois invariantes. Não depende de nada fora da biblioteca padrão do Python — sem virtualenv, sem instalação, nada para manter atualizado — e nunca compra: sem checkout, sem cartão guardado, sem login, sem automação de fila. Não são limitações de v1 esperando para serem removidas. São o que torna seguro deixá-lo rodando todo minuto de graça, porque um processo sem sessão e sem credencial não tem o que vazar nem o que gastar.
Tudo o que o monitor recusa vive na segunda ferramenta. O ticket-autobuy carrega o browser, a sessão autenticada e o risco. Os dois se comunicam por exatamente uma coisa: um bloco `buy` dentro de um arquivo de target, que o monitor carrega, valida e então ignora. Nada mais cruza. O comprador lê o histórico de preços que o monitor acabou de escrever, então checar o mercado custa ao site zero requisições adicionais, e a rede só é tocada no momento de uma compra.
- Ele reserva; nunca paga. A retenção de cerca de dez minutos de um Pix não pago é o portão humano, medida num pedido real e não suposta.
- Ele nunca faz login sozinho. A sessão é estabelecida por uma pessoa num browser real, porque automatizar isso significaria guardar uma senha — e um login roteirizado é a coisa menos comum que esta ferramenta poderia fazer no momento em que mais quer parecer comum.
- Toda regra de alerta é disparada por transição, nunca por estado, para que um job rodando a cada sessenta segundos não re-alerte para sempre e te ensine a ignorá-lo.
Três falhas que reportavam sucesso
As três estavam em código que eu escrevi, as três foram encontradas com o sistema no ar, e nenhuma parecia uma falha vista de fora.
Um único ponto fora da curva matou uma regra para sempre
Um anúncio apareceu a R$ 66,00 numa noite que negociava perto de R$ 220,00, ficou por uma única leitura e sumiu. Levou o recorde de mínima histórica — e a regra de mínima histórica não expira, então ela estava agora permanentemente morta justamente na noite para a qual tinha sido construída, e a regra de janela móvel ficou muda junto. As duas estavam armadas. As duas pareciam saudáveis. Nenhuma jamais voltaria a falar, e nada em lugar nenhum diria isso.
A correção foi um piso de anomalia fazendo dois trabalhos com um número só: alertar na hora sobre qualquer coisa abaixo dele, e excluir qualquer coisa abaixo dele de toda linha de base. O que importa é que a exclusão é aplicada na leitura do histórico, não na escrita. Isso significa que armar o piso conserta um histórico já envenenado, e o log continua um registro fiel do que o site de fato serviu — um histórico que omite discretamente a linha ruim não pode ser auditado. Reprocessada sobre cerca de 1.100 execuções gravadas em sete targets, a regra dispara exatamente uma vez: no R$ 66,00 real.
Um desarme fail-closed ficou vinte e cinco horas em silêncio
O comprador chegou a um estado ambíguo, fez a coisa certa — recusou-se a agir e desarmou a noite — e depois não disse nada sobre isso por vinte e cinco horas. Seu único rastro foi uma linha no stderr. O arquivo de log ganhou zero bytes ao longo de cerca de 1.500 execuções agendadas enquanto o keepalive da sessão reportava saúde a cada vinte minutos, e sessenta e sete leituras dentro da janela de compra estavam no teto ou abaixo dele, incluindo quatro minutos seguidos a R$ 220,00 com 178 disponíveis.
Pior: o desarme estava errado, e a ferramenta tinha a evidência para saber disso. A rotina que lê a página de pedidos devolvia um resultado vazio para quatro causas diferentes, uma das quais era "a página renderizou e não há pedidos pendentes" — caso que o próprio código comenta como significando que não existe pedido. Quarenta e cinco segundos de verificação bem-sucedida estavam sendo classificados como ignorância. O conserto foi tornar o silêncio impossível: uma noite fechada agora se anuncia, uma pendente insiste num intervalo controlado, e recusar-se a agir exige dois sinais independentes em vez de um único vazio ambíguo. Havia um teste afirmando o silêncio.
Um timeout que nunca poderia disparar onde disparar era permitido
Um orçamento de sessenta segundos deveria abortar uma execução longa demais. Medido, o trabalho que ele deveria limitar levava cerca de trinta segundos, e os quarenta e sete restantes ficavam inteiramente depois do ponto sem retorno — depois do clique que cria um pedido. Ou seja, o orçamento só poderia disparar onde abortar já era impossível, e nada além de um print jamais o leu. Foi substituído por um orçamento de quarenta e cinco segundos imposto ao trecho anterior ao clique, e só a ele. Na mesma passagem, o percurso medido caiu de 29,65 para cerca de 17–19 segundos.
- 424
- testes verdes, offline — 228 monitor, 196 comprador
- 7
- noites acompanhadas ao vivo, em duas cadências de cron
- 5,49s
- para extrair o código Pix, após duas falhas de 47 segundos
- 18,37s
- de percurso no checkout, contra um teto de 45 segundos
O que custou, e o que vale
O resumo honesto é que o sistema funcionou e a operação não. Duas das sete noites foram compradas na mão por mais de R$ 300 porque a automação nunca fechou a compra a tempo, e a noite para a qual tudo foi finalmente apontado passou sem ninguém. A prova de ponta a ponta chegou na última tarde, num target armado especificamente para testá-la: um pedido real, um código Pix real em 5,49 segundos, deixado sem pagar de propósito e verificado como expirado treze minutos depois. Depois disso tudo foi desligado — zero jobs agendados ativos, os oito targets desarmados.
Evidência
424 testes verdes offline, sete noites lidas de ponta a ponta e um pedido concluído contra o site real — deixado sem pagar de propósito, porque o desenho inteiro da ferramenta é que quem paga é um humano. Também não fez aquilo para que foi construída: dois ingressos foram comprados na mão por mais de R$ 300 porque a automação nunca fechou a tempo. Os dois números pertencem ao mesmo parágrafo. O que eu de fato colocaria na frente de um cliente não é nenhum dos dois — são as três seções acima, onde um sistema que eu escrevi reportava sucesso sem fazer nada, e foi instrumentação, não palpite, que achou.