2 pontos por GN⁺ 2024-08-07 | 1 comentários | Compartilhar no WhatsApp
  • Em dezembro de 2022, em meio à inflação alta, foi criado o pipeline pricewatcher.gr para acompanhar diariamente as variações de preços dos três maiores supermercados da Grécia
  • Como as listas de produtos ficavam por trás de renderização JavaScript e rolagem infinita, em vez de curl ou requests.get(), foi usado o Playwright para controlar o navegador e extrair dados do DOM
  • Como o ambiente de execução era lento usando apenas um notebook antigo e AWS EC2 era caro, a abordagem mudou para criar servidores Hetzner somente quando necessário, executar três scrapers em paralelo e depois encerrá-los
  • Para um supermercado em que a Akamai bloqueava IPs não residenciais, foi usado um exit node do Tailscale para rotear pelo IP de casa; falhas eram detectadas com alertas por e-mail, transformações rigorosas e heurísticas de contagem de produtos
  • Com servidor de 8 vCPUs e bloqueio de requisições de imagens, o tempo de execução foi reduzido; nas 31 execuções mais recentes, os custos ficaram em cerca de €4,94 pelos servidores Hetzner, €0,09 por IPv4 e €0,00 de armazenamento no Cloudflare R2

Raspando sites de supermercados baseados em JavaScript

  • Em dezembro de 2022, em um cenário de inflação alta, foi criado o pricewatcher.gr, que acompanha as variações de preços dos três maiores supermercados da Grécia
  • Todos os três e-shops dos supermercados usavam renderização JavaScript, e algumas áreas carregavam mais produtos ao rolar a página, como a rolagem infinita das redes sociais
  • Não era possível obter os dados dos produtos com um simples curl ou requests.get(), então foi usado o Playwright
  • O Playwright controla o navegador programaticamente e trata as seguintes tarefas por API
    • Abrir uma nova aba e navegar para uma URL
    • Inspecionar o DOM
    • Consultar detalhes de elementos
    • Interceptar e inspecionar requisições
  • O Playwright oferece suporte a Chromium, Safari e Firefox, e pode ser usado em Node, Java, .NET e Python
  • O scraper encontra o elemento load more da rolagem infinita e continua rolando; depois, na lista li.product-item, exclui produtos marcados como fora de estoque
  • Por fim, faz o parsing de nome do produto, preço, foto, link etc. de cada produto `` e repete o mesmo processo para a próxima categoria de produtos

Escolhendo o ambiente de execução para rodar diariamente

  • Em um M1 MacBook Pro, processar um supermercado inteiro levava de 50 minutos a 2 horas e 30 minutos, e executar os três scrapers em paralelo não fazia diferença perceptível
  • O notebook era suficiente para desenvolvimento e testes, mas era necessário um ambiente permanente para execução diária
  • Execução apenas em um notebook antigo

    • A primeira tentativa foi com um notebook antigo de 2013
    • As especificações eram um processador série M dual-core de 2,20 GHz e 4 GB de RAM, depois ampliados para 12 GB de RAM
    • Porém, mesmo o supermercado “rápido” levava mais de 2 horas para ser processado, ficando aquém do desempenho esperado
  • Execução na nuvem

    • Foram avaliadas instâncias EC2 na AWS com cerca de 4 cores e 8 GB de RAM, mas o custo pesava para um projeto paralelo
    • No momento da escrita, a c5a.xlarge em eu-north-1 custava US$ 0,1640 por hora, o equivalente a US$ 118,08 por mês ou US$ 1.416,96 por ano
    • O servidor equivalente da Hetzner, cpx31, custava US$ 17,22 (€15,72) por mês, ou US$ 206,64 por ano, cerca de 7 vezes mais barato que a AWS
    • O ambiente final de execução escolhido foi a Hetzner

Pipeline diário configurado com Concourse

  • O notebook antigo não executa a raspagem diretamente; ele atua como um servidor de CI que delega o trabalho a servidores Hetzner
  • A ferramenta de CI usada é o Concourse
    • O Concourse se apresenta como “a continuous thing-doer”
    • Com um modelo declarativo de pipelines e versionamento de entradas, ele busca builds reproduzíveis
  • Todas as noites, o pipeline roda na seguinte ordem
    • Criar o servidor de scraping
    • Executar em paralelo as tarefas de scraping dos 3 supermercados
    • Após a conclusão de todas as tarefas, encerrar o servidor para reduzir custos
    • Enviar a saída bruta de cada scraper para uma tarefa de transformação
    • Carregar os dados transformados no pricewatcher.gr
    • Se qualquer etapa falhar, enviar alerta por e-mail

Contornando restrições de IP e exit node do Tailscale

  • O supermercado usado nos testes funcionava normalmente, mas outro supermercado ficava atrás da Akamai e tinha uma regra de firewall ativada que bloqueava requisições de IPs não residenciais
  • A estrutura necessária era o inverso de uma VPN comum: fazer com que as requisições parecessem sair do IP real de casa
  • Com o Tailscale, vários dispositivos foram conectados como se estivessem na mesma rede
  • Ao designar um dispositivo como exit node no Tailscale, é possível configurar as requisições de outros dispositivos para saírem por esse nó
  • O notebook antigo também passou a atuar como exit node para o tráfego de scraping
  • O fato de o ISP usado empregar CGNAT, de modo que o IP público não fica vinculado apenas a uma pessoa específica e é compartilhado com outros clientes do ISP, continua sendo uma variável

Tipos de falha e formas de detecção

  • Essa configuração operou por um ano e meio e, no geral, funcionou de forma confiável
  • Projetos de scraping inevitavelmente ficam sujeitos a mudanças feitas pelos desenvolvedores dos sites-alvo
  • As falhas se dividem em dois tipos principais
  • Mudanças que quebram tudo

    • São casos em que uma mudança no site faz o scraper falhar imediatamente
    • Alguns exemplos são
      • Uma pesquisa ser adicionada, exigindo clicar em mais um botão
      • O layout mudar completamente, exigindo uma grande refatoração do scraper
  • Mudanças que não quebram tudo

    • Casos em que o scraper continua rodando normalmente, mas a interpretação dos dados fica errada, são mais complicados
    • Por exemplo, se o formato de exibição de preços mudar e separar a parte decimal com ``, uma batata chips de €1,99 pode ser parseada como €199
    • Para capturar esse tipo de mudança, a etapa de transformação foi configurada para validar a entrada da forma mais rigorosa possível
    • Como a execução é diária, há tempo para verificar o problema, mas se algo quebrar durante férias, isso pode virar uma fonte de preocupação

Otimizando tempo de execução e estabilidade

  • A arquitetura geral se manteve quase igual desde o início, mas várias partes foram alteradas para aumentar a confiabilidade e reduzir trabalho manual
  • As melhorias aplicadas incluíram
    • Alertas por e-mail em caso de falha
    • Heurísticas que enviam alerta quando a contagem de produtos de um supermercado específico fica alta ou baixa demais
    • Timeouts
    • Retentativas que não recomeçam tudo do zero
  • O maior gargalo era o tempo de execução do scraping
    • Quanto mais demorado, maior o custo
    • Quando era preciso tentar de novo após uma falha, o incômodo aumentava
  • Usar um servidor maior

    • O servidor foi trocado de 4 vCPUs e 16 GB de RAM para 8 vCPUs e 16 GB de RAM
    • O tempo de execução caiu cerca de 20%, chegando a um nível parecido com o desempenho obtido no MBP
    • Como o servidor de scraping é usado por cerca de apenas 2 horas, a diferença de preço era desprezível
  • Buscar menos coisas

    • Foi usado page.route do Playwright para bloquear requisições de imagens
    • Requisições .png e .jpg são abortadas, de modo que imagens não sejam baixadas durante o carregamento dos produtos
    • Essa abordagem torna o scraping mais rápido e também pode reduzir um pouco a largura de banda e os custos do site-alvo

Custos em 31 execuções

  • Segundo a fatura recente da Hetzner, os custos foram os seguintes
    • 31 servidores criados: €4,94
    • 31 endereços IPv4 recebidos por esses servidores: €0,09
  • Os dados de scraping são armazenados no Cloudflare R2
  • Como ainda não passou do nível gratuito de 10 GB do Cloudflare R2, o custo de armazenamento é €0,00
  • O pipeline completo funciona combinando Playwright, Hetzner, Concourse, Tailscale e Cloudflare R2 para acompanhar diariamente as variações de preços de supermercados

1 comentários

 
GN⁺ 2024-08-07
Opiniões no Hacker News
  • Estou fazendo algo parecido para a New Zealand desde o começo deste ano. Uso Playwright/Typescript para raspar os dados e salvá-los como arquivos Parquet em armazenamento na nuvem, mas ainda não os exibi em uma interface.
    A maior parte do trabalho vai para contornar serviços de proxy reverso como Akamai e Cloudflare. Quando comecei, achei que ninguém estivesse fazendo isso, mas agora sei de pelo menos 3 startups na NZ fazendo a mesma coisa. Parece que a inflação desencadeou bastante inovação por aqui.
    O padrão é o esperado. Os supermercados usam suas táticas habituais para tornar os preços o mais complexos possível e, com variações de preço em “dente de serra”, separam quem tem pouco tempo de quem tem pouco dinheiro. Muitas vezes também separam clientes fiéis à marca de clientes sensíveis a preço; se há 3 marcas populares de chocolate, a cada semana só uma delas fica a um preço razoável.

    • Tenho curiosidade sobre como supermercados abusam da segmentação de clientes por meio de atualizações de preços. Também gostaria de saber como pessoas com pouco tempo e pessoas realmente sem dinheiro costumam reagir.
      Fiquei especialmente curioso com a parte “separam clientes fiéis à marca de clientes sensíveis a preço. Se há 3 marcas populares de chocolate, a cada semana só uma delas é vendida a um preço razoável”.
    • Na Australia, a legalidade desse tipo de abordagem é ambígua. Imagino que na NZ seja parecido.
      Na AU, muitos desses scrapers aparecem e desaparecem, mas normalmente os grandes supermercados acabam bloqueando. É um ciclo recorrente de utilidade e de “por que isso não existe?”, quando na verdade já existiu várias vezes.
    • Criei https://bbdeals.in/ para a India. Uso principalmente para comprar frutas, e ele economiza cerca de 20% dos meus gastos, o que não é nada mau em tempos como estes.
      Levei menos de 20 horas para criar o crawler e a infraestrutura de suporte.
    • Como kiwi, fico curioso se é possível tornar esse projeto, ou o seu próprio projeto, público. Tenho bastante interesse.
    • Quem pede entrega de supermercado online pode se beneficiar da comparação de preços, porque consegue comprar em várias lojas ao mesmo tempo.
      Além disso, é ainda mais útil porque há apenas um marketplace que reúne os preços de várias lojas.
  • Ótimo texto. Passei por problemas parecidos no site de comparação de preços de lentes de contato que opero, https://lenspricer.com/, que funciona em cerca de 30 países. Concordo que sites mudando o HTML são uma enorme dor de cabeça.
    Um dos maiores obstáculos no começo foi associar o mesmo produto em mais de 100 sites. Você imaginaria que o nome do produto seria único, mas todo mundo escreve com pequenas variações próprias. A maior parte é tratada com regex, mas muitos precisaram ser mapeados manualmente; em alguns casos usei IA, mas validei tudo pessoalmente.
    Criar scrapers e a infraestrutura é relativamente fácil. O difícil é manter todos os scrapers e, quando um produto desaparece de um site, descobrir se foi erro do scraper, bloqueio, mudança no site ou se o site estava em manutenção no momento do crawling.
    É um projeto divertido, mas às vezes difícil, com muitos problemas irritantes de corrigir.

    • Você está fazendo algo necessário. Todo ano sofro por causa da seguradora ao comprar um item básico: lentes de contato.
      Os preços variam muito, e a cobertura normalmente é algo como 30% via reembolso pelo correio.
    • Fico curioso se é possível usar lentes de contato no trabalho. Sinto os olhos cansarem quando passo muito tempo olhando para o monitor e gostaria de saber se você encontrou alguma solução.
    • Na página da Germany, abaixo dos preços aparece “alguns links podem ser links patrocinados”, mas não indica quais são. Pergunto-me se isso é legal.
      O número de lojas também parece muito pequeno; talvez todos os links sejam patrocinados. Encontrei preços mais baixos no idealo.de.
    • Nos EUA, pelo menos os grandes varejistas dificultam a comparação de preços pedindo aos fornecedores que criem SKUs ligeiramente diferentes.
      A Costco é bem famosa por isso: quase todos os eletrônicos vendidos nas lojas, e muitos outros produtos, têm SKUs personalizados. Muitas vezes a composição do produto também é um pouco diferente.
    • Acho que este é justamente o tipo de caso de uso em que um LLM pode realmente ajudar.
  • Criei um site parecido que recebeu bastante atenção na minha cidade. Ele raspa dados de apps e sites, e roda em um único servidor da Linode com 2 GB de RAM, 5 IPv4 e 1000 IPv6 gratuitos.
    Todos os produtos são coletados em intervalos de no máximo 40 minutos, com média de cerca de 25 minutos. Uso curl-impersonate e raspo JSON sempre que possível. 90% do mercado fornece preços por chamadas Ajax, e os 10% restantes são fáceis de parsear em HTML com regex.
    Dá para ver em https://www.economizafloripa.com.br

    • Raspa dados de apps e sites e depois tenta vendê-los de volta aos comerciantes, chegando até a sugerir que usem esses dados para treinamento de IA. Também faz parecer que a equipe processa tudo manualmente.
      https://www.economizafloripa.com.br/?q=parceria-comercial
      Ao ver essa página, o projeto passou de “uma ferramenta útil para pessoas retomarem o controle de empresas que vendem itens essenciais” para “mais uma tentativa de ganhar dinheiro”. Claro, isso é escolha deles, mas ao ler a página inicial eu esperava uma motivação mais ética.
    • Fico curioso sobre como a rotação de IPv6 funciona nesse fluxo.
  • Ótimo texto.
    Acho melhor separar scraping e parsing em processos distintos. Se você guardar o JSON ou HTML original, sempre pode voltar, corrigir o parser e reaplicar.
    Como parte do meu projeto de mestrado, criei um sistema e um site semelhantes para os Países Baixos: https://www.superprijsvergelijker.nl/
    A maior parte do scraping do meu projeto é feita enviando chamadas HTTP simples para APIs JSON. Alguns sites usam uma instância do Playwright para obter um cookie de sessão válido e contornar proteções contra bots e CAPTCHA. O restante dos crawlers/scrapers, parsers e da API foi feito em Haskell e roda no AWS ECS. O site é em NextJS.
    O principal desafio que continuo tentando resolver é vincular produtos de diferentes supermercados para listar os preços em uma única tela. Há um exemplo aqui: https://www.superprijsvergelijker.nl/supermarkt-aanbieding/6...
    Se o produto fornecer pelo menos um número de código de barras correto, geralmente funciona bem.

    • Exato, também faço exatamente assim e isso já me ajudou mais vezes do que eu gostaria de admitir. Guardar o JSON/HTML original é realmente útil.
    • Legal. Era isso que eu estava procurando.
  • Acho que os dois grandes supermercados da Austrália poderiam criar uma estrutura de duopólio com precificação anticompetitiva só operando algoritmos de IA de análise de preços em ambos os lados. No fim, é bem provável que os algoritmos caminhem para cooperar a fim de maximizar o lucro.
    Isso pode ser feito legalmente usando apenas preços obtidos publicamente, e também ilegalmente se compartilharem custos de fornecimento ou dados de lucro por produto. O resultado provavelmente seria parecido.
    Duas IAs treinadas fariam uma análise de regressão multidimensional, talvez hiperdimensional, para maximizar o lucro de formas estranhas, e os consumidores acabariam bancando os lucros maximizados de empresas que, na aparência, são concorrentes. Se for possível obter dados de preços desse jeito, não é preciso muita coisa para rodar duas implementações de machine learning focadas em um duopólio.

    • A “autoridade de concorrência” da Noruega (https://konkurransetilsynet.no/norwegian-competition-authori...) costuma criticar informações públicas e transparentes sobre preços de alimentos justamente por esse motivo.
      A lógica é que, se todos os preços forem públicos, os consumidores acabam pagando preços mais altos. Os supermercados passariam a ajustar os preços para o ponto em que todos obtêm lucro máximo.
      Por aqui, os supermercados há anos empregam “caçadores de preços”, pessoas que vão às lojas concorrentes e registram o preço de todos os itens.
      Na Noruega, é comum ver o supermercado A dar desconto em um item específico em uma semana, e então o supermercado B oferecer um desconto parecido na semana seguinte ou na outra para atrair clientes.
    • A palavra que você estava procurando era conluio. Só que não é conluio entre pessoas, e sim feito por software.
  • Para detectar esse tipo de mudança, acho que daria para adicionar verificações automáticas. Por exemplo, não sincronizar mudanças de preço/produto se uma verificação de bom senso falhar.
    Poderia haver critérios como: cada preço não pode variar mais de 100%, e o número de produtos ativos não pode variar mais de 20%.

    • Verificações de bom senso são subestimadas em programação. O custo de desempenho é baixo e elas detectam cedo bugs que, se deixados passar, poluiriam o estado.
    • Também pensei nisso, mas já vi casos em que o preço de um produto subiu mais de 100%.
      Em vez disso, usei esse tipo de heurística para confirmar se o scraping foi bem-sucedido. Por exemplo, verificar se o número de produtos coletados hoje está dentro de cerca de 10% da média dos últimos 7 dias.
  • O difícil não é o scraping em si, mas contornar mecanismos de bloqueio cada vez mais sofisticados.
    É preciso ficar alternando proxies residenciais, usar os bem avaliados e evitar expor padrões de scraping de dados. Alguns supermercados não mostram as requisições de rede na aba Network, então não dá para simplesmente pegar as respostas da API.
    Mesmo tentando fazer um ataque man-in-the-middle no app móvel para ver as requisições de rede e os dados, você é bloqueado se não se disfarçar corretamente.
    Tentei fazer isso, mas concluí que não valia a pena por causa do custo e do trabalho contínuo de desenvolvimento. Na prática, alguns serviços de comparação de preços de supermercados simplesmente usam mão de obra barata para coletar os dados.

    • Seria ótimo se a lei pudesse exigir que os dados de preços fossem publicados por API. Assim não precisaríamos brigar com mecanismos de bloqueio.
    • Felizmente ainda não cheguei a esse ponto.
      Como isto é um projeto paralelo, se a demanda de tempo ficar frequente demais, pretendo simplesmente encerrar e publicar todo o código e os dados.
      Mas fico curioso para saber como é possível que as requisições de rede não apareçam na aba Network.
      A parte mais difícil para mim é vincular e comparar produtos entre os supermercados.
    • Não daria para usar OCR e simplesmente capturar imagens das listas de produtos? Não é o ideal, mas dependendo do método pode ser difícil ou impossível de rastrear.
    • Dá para fazer crowdsourcing com uma extensão de navegador.
  • Seria bom haver transparência de preços dos produtos. Isso tornaria muito mais fácil acompanhar esse processo por loja e por região.
    Por exemplo, daria para comparar o preço do leite de aveia, um substituto do leite, por CEP e por supermercado. Também daria para acompanhar a “reduflação”, quando o preço fica igual, mas a quantidade diminui.
    Nesse sentido, parece que vocês acompanham preços, mas fico curioso se também verificam o custo por grama ou por onça. Fabricantes ou lojas podem manter o preço igual e dar menos quantidade ao consumidor; fico curioso se a ferramenta conseguiria detectar isso.

    • Também acompanhamos o preço por unidade, como kg e L. Pensei se deveria mostrar e plotar esse número em gráficos ou mostrar o preço que a pessoa realmente paga no caixa, mas escolhi o segundo para ficar mais “familiar” em relação ao preço que as pessoas veem.
      Ainda assim, é um recurso que daria para adicionar sem problema e, se tivesse havido reduflação, seria possível mostrar o momento em que ela aconteceu.
    • Pessoalmente, acho realmente irritante quando supermercados não colocam o preço unitário na etiqueta. É difícil imaginar um propósito que não seja hostil ao cliente.
    • Seria bom imaginar a obrigatoriedade de divulgar o preço de custo dos produtos. Eu gostaria de ver que o agricultor recebeu X, o fabricante recebeu Y e o supermercado acrescentou Z.
  • No mercado sueco, fazemos isso há mais de 8 anos. Existe um site chamado https://www.matspar.se/, onde os clientes podem navegar por todos os produtos das principais lojas online, comparar preços e colocar os itens desejados no carrinho
    No fim, eles comparam o valor total do carrinho, incluindo o frete, e podem exportar o carrinho para a loja desejada para fazer o pedido
    Sou cofundador e CTO atual, então fiz muito scraping e manutenção ao longo desse tempo. Estamos raspando mais de 30 milhões de preços por dia

    • Do ponto de vista do negócio, tenho curiosidade sobre o modelo de negócios. Gostaria de saber como geram receita e qual é o objetivo de longo prazo
      Segundo dados públicos, a receita da empresa é de cerca de US$ 400 mil e há 6 funcionários: https://www.allabolag.se/5590076351/matspar-i-sverige-ab
    • Tenho curiosidade se há algum artigo técnico sobre a abordagem de scraping. Gostaria de ler mais sobre quais dificuldades encontraram e como as resolveram
  • Quando me mudei para uma região nova, eu fazia rastreamento de preços, mas hoje acho muito mais fácil comprar em mercados que mantêm preços sempre baixos ou em duas grandes lojas
    Na Europa, provavelmente seriam Aldi/Lidl
    Nos EUA, poderiam ser Costco/Trader Joe's
    Online, há CamelCamelCamel/Amazon. Não para alimentos, mas para saúde/beleza/alguns eletrônicos
    Quando é possível comprar diretamente do fabricante, às vezes isso é melhor. Por exemplo, comprei em grande quantidade uma marca específica de sabonete de que gosto em um site de atacado, e saiu por menos da metade do preço de varejo. Shampoo comprado diretamente em galões também era muito mais barato do que em qualquer varejista

    • Pela minha experiência, nos EUA é Costco/Walmart/Aldi
      Trader Joe's tem qualidade mais alta, mas em geral é mais caro
    • Também existe ALDI nos EUA, mas varia por região. Trader Joe’s é de propriedade da mesma família da ALDI e, até cerca de 10 anos atrás, era difícil ver as duas na mesma região