Um monitor de preços que não pode comprar, e o comprador ao lado

Duas ferramentas em Python, uma linha deliberada entre elas.

Um vídeo de vinte segundos: uma página de ingressos que mostra um preço se abre revelando os dezenove do payload, o monitor dispara um alerta de nova mínima no Telegram, uma linha vertical separa price-watcher de ticket-autobuy com a única chave de configuração que a cruza, o comprador percorre um checkout até um pedido concluído, e os números param em 424 testes, 7 noites e 1 pedido real

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

Ver o monitorCódigo-fonte

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.