Suivre les prix des supermarchés avec Playwright
(sakisv.net)- En décembre 2022, en pleine forte inflation, un pipeline pricewatcher.gr a été mis en place pour suivre chaque jour l’évolution des prix des trois plus grandes chaînes de supermarchés grecques
- Comme les listes de produits apparaissaient après rendu JavaScript et défilement infini, les données ont été extraites du DOM en pilotant un navigateur avec Playwright plutôt qu’avec
curlourequests.get() - Comme l’exécution était lente sur un vieux portable et qu’AWS EC2 coûtait cher, l’approche a été changée pour créer un serveur Hetzner uniquement au moment nécessaire, lancer 3 scrapers en parallèle, puis l’éteindre
- Pour le supermarché dont Akamai bloquait les IP non résidentielles, un exit node Tailscale a permis de faire passer le trafic par l’IP du domicile, et les échecs ont été détectés via des alertes e-mail, des transformations strictes et des heuristiques sur le nombre de produits
- Un serveur 8 vCPU et le blocage des requêtes d’images ont réduit le temps d’exécution ; sur les 31 exécutions les plus récentes, le coût s’élève à environ €4.94 pour les serveurs Hetzner, €0.09 pour l’IPv4 et €0.00 pour le stockage Cloudflare R2
Scraper des sites de supermarchés basés sur JavaScript
- En décembre 2022, dans un contexte de forte inflation, pricewatcher.gr a été créé pour suivre l’évolution des prix des trois plus grandes chaînes de supermarchés grecques
- Les e-shops des trois supermarchés utilisaient tous un rendu JavaScript, et certaines zones chargeaient davantage de produits au défilement, comme l’infinite scroll des réseaux sociaux
- Il était impossible de récupérer les données produits avec un simple
curlourequests.get(), d’où l’utilisation de Playwright - Playwright permet de piloter un navigateur par programmation et gère les opérations suivantes via son API
- ouvrir un nouvel onglet et naviguer vers une URL
- inspecter le DOM
- récupérer les détails d’un élément
- intercepter et inspecter les requêtes
- Playwright prend en charge Chromium, Safari et Firefox, et peut être utilisé avec Node, Java, .NET et Python
- Le scraper repère l’élément
load morede l’infinite scroll, continue à faire défiler la page, puis exclut de la listeli.product-itemles produits marqués comme en rupture de stock - Enfin, il parse pour chaque produit le nom, le prix, la photo, le lien et d’autres informations, puis répète la même opération pour la catégorie suivante
Choisir un environnement d’exécution quotidien
- Sur un M1 MacBook Pro, traiter entièrement un supermarché prenait entre 50 minutes et 2 heures 30, et exécuter les 3 scrapers en parallèle ne faisait pas de différence notable
- Le portable suffisait pour le développement et les tests, mais un environnement permanent était nécessaire pour l’exécution quotidienne
-
Exécution seule sur un vieux portable
- Le premier essai a été fait avec un vieux portable de 2013
- Sa configuration comprenait un processeur série M double cœur à 2.20 GHz et 4 Go de RAM, ensuite portés à 12 Go
- Mais même le supermarché “rapide” demandait plus de 2 heures, loin des performances espérées
-
Exécution dans le cloud
- Une instance EC2 AWS de niveau 4 cœurs et 8 Go de RAM a été envisagée, mais son coût était trop élevé pour un side project
- Au moment de la rédaction, une
c5a.xlargedanseu-north-1coûtait $0.1640 par heure, soit $118.08 par mois ou $1,416.96 par an - Le serveur équivalent
cpx31chez Hetzner coûtait $17.22 (€15.72) par mois, soit $206.64 par an, environ 7 fois moins cher qu’AWS - Hetzner a donc été retenu comme environnement d’exécution final
Un pipeline quotidien orchestré avec Concourse
- Le vieux portable n’effectue pas directement le scraping ; il joue le rôle de serveur CI qui délègue le travail à un serveur Hetzner
- L’outil de CI utilisé est Concourse
- Concourse se présente comme “a continuous thing-doer”
- Avec son modèle de pipeline déclaratif et le versioning des entrées, il vise des builds reproductibles
- Le pipeline s’exécute chaque nuit dans l’ordre suivant
- création du serveur de scraping
- exécution en parallèle des tâches de scraping des 3 supermarchés
- arrêt du serveur après la fin de toutes les tâches pour réduire les coûts
- transmission de la sortie brute de chaque scraper à une tâche de transformation
- chargement des données transformées dans pricewatcher.gr
- en cas d’échec à n’importe quelle étape, envoi d’une alerte e-mail
Contourner les restrictions IP avec un exit node Tailscale
- Le supermarché utilisé pour les tests fonctionnait correctement, mais un autre était protégé par Akamai avec une règle de pare-feu bloquant les requêtes provenant d’IP non résidentielles
- L’architecture nécessaire était l’inverse d’un VPN classique : faire en sorte que les requêtes semblent partir de la véritable IP du domicile
- Tailscale a servi à relier plusieurs appareils comme s’ils étaient sur le même réseau
- Dans Tailscale, il est possible de désigner un appareil comme exit node afin que les requêtes des autres appareils sortent via ce nœud
- Le vieux portable assurait aussi le rôle d’exit node pour le trafic de scraping
- Le fait que le FAI utilise le CGNAT, où l’IP publique n’est pas liée à une seule personne mais partagée avec d’autres clients, reste aussi un facteur à prendre en compte
Types d’échecs et détection
- Cette configuration a tourné pendant un an et demi et s’est révélée globalement fiable
- Les projets de scraping restent inévitablement dépendants des changements effectués par les développeurs des sites ciblés
- Les échecs se répartissent en deux grandes catégories
-
Changements cassants
- Ce sont les cas où le scraper échoue immédiatement après une modification du site
- Exemples
- l’ajout d’un sondage oblige à cliquer une fois de plus sur un bouton
- la mise en page change complètement et nécessite un refactoring important du scraper
-
Changements non cassants
- Plus délicats, ils laissent le scraper tourner normalement mais faussent l’interprétation des données
- Par exemple, si le format d’affichage du prix change et que la partie décimale est séparée dans ``, un paquet de chips à €1.99 peut être parsé comme €199
- Pour détecter ce type de changement, l’étape de transformation valide les entrées aussi strictement que possible
- Comme l’exécution est quotidienne, il y a le temps de constater le problème, mais si cela casse pendant les vacances, cela reste une source d’inquiétude
Optimiser le temps d’exécution et la fiabilité
- L’architecture générale a très peu changé depuis le départ, mais plusieurs éléments ont été ajustés pour améliorer la fiabilité et réduire le travail manuel
- Parmi les améliorations apportées
- alertes e-mail en cas d’échec
- heuristiques qui envoient une alerte quand le nombre de produits est anormalement élevé ou faible pour un supermarché donné
- timeouts
- retries sans repartir depuis le début
- Le principal goulot d’étranglement était le temps d’exécution du scraping
- plus c’est long, plus le coût augmente
- et quand il faut relancer après un échec depuis le début, c’est d’autant plus pénible
-
Utiliser un plus gros serveur
- Le serveur est passé de 4 vCPU et 16 Go de RAM à 8 vCPU et 16 Go de RAM
- Le temps d’exécution a diminué d’environ 20 %, atteignant un niveau similaire à celui obtenu sur le MBP
- Le serveur de scraping n’étant utilisé qu’environ 2 heures, l’écart de prix restait négligeable
-
Récupérer moins de données
- Les requêtes d’images ont été bloquées via
page.routede Playwright - Les requêtes
.png,.jpgsont annulées pour éviter de télécharger les images pendant le chargement des produits - Cette méthode accélère le scraping et peut aussi réduire légèrement la bande passante ainsi que les coûts du site cible
- Les requêtes d’images ont été bloquées via
Coût sur 31 exécutions
- D’après la facture Hetzner la plus récente, le coût est le suivant
- 31 serveurs créés : €4.94
- 31 adresses IPv4 attribuées à ces serveurs : €0.09
- Les données de scraping sont stockées dans Cloudflare R2
- Le seuil du free tier de 10 Go de Cloudflare R2 n’ayant pas encore été dépassé, le coût de stockage est de €0.00
- L’ensemble du pipeline fonctionne ainsi comme une architecture combinant Playwright, Hetzner, Concourse, Tailscale et Cloudflare R2 pour suivre chaque jour l’évolution des prix dans les supermarchés
1 commentaires
Avis de Hacker News
Je fais quelque chose de similaire pour la Nouvelle-Zélande depuis le début de l’année. Je récupère les données avec Playwright/Typescript et les stocke sous forme de fichiers Parquet dans un stockage cloud, mais je ne les affiche pas encore à l’écran.
L’essentiel du travail consiste à contourner des services de reverse proxy comme Akamai ou Cloudflare. Quand j’ai commencé, je pensais que personne ne faisait ça, mais je sais maintenant qu’au moins trois startups font la même chose en NZ. L’inflation semble avoir déclenché pas mal d’innovation ici.
Les schémas sont ceux auxquels on s’attend. Les supermarchés utilisent leurs méthodes habituelles pour rendre les prix aussi complexes que possible, et des variations de prix en « dents de scie » pour distinguer les personnes qui manquent de temps de celles qui manquent d’argent. Ils segmentent aussi souvent les clients fidèles à une marque et les clients sensibles au prix ; s’il y a trois marques de chocolat populaires, une seule des trois est vendue à un prix correct chaque semaine.
La partie « ils segmentent les clients fidèles à une marque et les clients sensibles au prix. S’il y a trois marques de chocolat populaires, une seule des trois est vendue à un prix correct chaque semaine » m’intrigue particulièrement.
En AU, beaucoup de scrapers de ce type apparaissent puis disparaissent, mais en général les grandes chaînes de supermarchés finissent par les bloquer. C’est un cycle où l’on se dit à la fois que c’est utile et « pourquoi ça n’existe pas ? », alors qu’en réalité cela a déjà existé plusieurs fois.
Il m’a fallu moins de 20 heures pour construire le crawler et l’infrastructure qui le soutient.
En plus, c’est d’autant plus utile qu’il n’existe qu’une seule place de marché qui possède les prix de plusieurs magasins.
Bon article. J’ai rencontré des problèmes similaires avec mon site de comparaison de prix de lentilles de contact, https://lenspricer.com/, que j’exploite dans une trentaine de pays. Je confirme que les sites qui modifient leur HTML sont un vrai casse-tête.
L’un des plus gros obstacles au début a été de faire correspondre les mêmes produits sur plus de 100 sites web. On pourrait croire que les noms de produits sont uniques, mais tout le monde les écrit à sa sauce, avec de petites variations. La plupart des cas se traitent avec des expressions régulières, mais beaucoup ont dû être mappés manuellement ; j’ai utilisé de l’IA pour certains, mais tout a été vérifié à la main.
Construire les scrapers et l’infrastructure est relativement facile. Le plus difficile, c’est de maintenir tous les scrapers, et de déterminer, lorsqu’un produit disparaît d’un site, s’il s’agit d’une erreur du scraper, d’un blocage, d’un changement du site ou d’une maintenance du site au moment du crawl.
C’est un projet amusant, mais parfois difficile, avec beaucoup de problèmes pénibles à corriger.
Les prix varient énormément, et la prise en charge se fait généralement sous forme de remboursement postal d’environ 30 %.
Le nombre de boutiques semble aussi très faible ; peut-être même que tous les liens sont sponsorisés. J’ai trouvé des prix plus bas sur idealo.de.
Costco est assez connu pour ça : presque tous les produits électroniques vendus en magasin, ainsi que beaucoup d’autres produits, ont des SKU personnalisés. La configuration du produit diffère aussi souvent légèrement.
J’ai créé un site web similaire qui a suscité pas mal d’intérêt dans ma ville. Il récupère les données des apps et des sites web, et tourne sur un unique serveur Linode avec 2 Go de RAM, 5 IPv4 et 1 000 IPv6 gratuites.
Tous les produits sont collectés au maximum toutes les 40 minutes, avec une moyenne d’environ 25 minutes. J’utilise curl-impersonate et je récupère du JSON autant que possible. 90 % du marché fournit les prix via des appels Ajax, et pour les 10 % restants, le HTML se parse facilement avec des expressions régulières.
C’est visible sur https://www.economizafloripa.com.br.
https://www.economizafloripa.com.br/?q=parceria-comercial
En voyant cette page, le projet passe de « un outil utile pour les gens qui reprennent le contrôle face aux entreprises vendant des produits essentiels » à « encore une tentative de gagner de l’argent ». Bien sûr, c’est votre liberté, mais en lisant la page d’accueil, je m’attendais à une motivation plus éthique.
Bon article.
Je pense qu’il vaut mieux séparer le scraping et le parsing dans des processus distincts. Si l’on conserve le JSON ou le HTML brut, on peut toujours revenir en arrière, corriger le parseur et le réappliquer.
Dans le cadre de mon projet de master, j’ai créé un système et un site web similaires pour les Pays-Bas : https://www.superprijsvergelijker.nl/
La majeure partie du scraping de mon projet consiste à envoyer de simples appels HTTP à des API JSON. Certains sites web utilisent une instance Playwright pour obtenir un cookie de session valide et contourner les protections anti-bot et les CAPTCHA. Le reste des crawlers/scrapers, des parseurs et de l’API est écrit en Haskell et tourne sur AWS ECS. Le site web est en NextJS.
Le principal défi que j’essaie encore de résoudre est de faire correspondre les produits de différents supermarchés afin d’afficher leurs prix sur un même écran. Exemple ici : https://www.superprijsvergelijker.nl/supermarkt-aanbieding/6...
Quand un produit fournit au moins un bon numéro de code-barres, ça fonctionne plutôt bien.
En Australie, je pense que les deux grands groupes de supermarchés pourraient créer une structure de duopole à tarification anticoncurrentielle rien qu’en faisant tourner des algorithmes d’IA d’analyse des prix de part et d’autre. Les algorithmes finiront probablement par coopérer pour maximiser les profits.
C’est faisable légalement avec seulement les prix obtenus publiquement, et illégalement si l’on partage les coûts d’approvisionnement ou les données de marge par produit. Le résultat serait sans doute similaire.
Deux IA entraînées feraient une maximisation des profits étrange via une analyse de régression multidimensionnelle, voire hyperdimensionnelle, et les consommateurs paieraient les profits maximisés d’entreprises qui, en apparence, sont concurrentes. Si l’on peut obtenir les données de prix de cette manière, il ne faut pas grand-chose pour faire tourner deux implémentations de machine learning centrées sur un duopole.
Leur logique est que si tous les prix sont publics, les consommateurs finissent paradoxalement par payer plus cher. Les supermarchés aligneraient leurs prix sur le point où tout le monde maximise ses profits.
Ici, les supermarchés emploient depuis des années des « chasseurs de prix » : des personnes qui vont dans les magasins concurrents et notent les prix de tous les produits.
En Norvège, on voit souvent que si le supermarché A met un article particulier en promotion une semaine, le supermarché B propose une réduction similaire la semaine suivante ou celle d’après pour attirer les clients.
Pour détecter ce genre de changements, on pourrait ajouter des contrôles automatiques. Par exemple, ne pas synchroniser les changements de prix/produits si une vérification de bon sens échoue.
On pourrait fixer des règles comme : aucun prix ne doit varier de plus de 100 %, et le nombre de produits actifs ne doit pas varier de plus de 20 %.
À la place, j’ai utilisé ce type d’heuristique pour vérifier que le scraping avait réussi. Par exemple, vérifier si le nombre de produits scrapés aujourd’hui se situe dans une marge d’environ 10 % par rapport à la moyenne des 7 derniers jours.
Ce qui est difficile, ce n’est pas le scraping en soi, mais le contournement de mécanismes de blocage de plus en plus sophistiqués.
Il faut faire tourner en continu des proxys résidentiels, utiliser ceux qui ont de bons scores, et éviter de révéler des schémas de scraping de données. Certains supermarchés ne montrent pas les requêtes réseau dans l’onglet Réseau, donc on ne peut pas simplement récupérer les réponses d’API.
Même en essayant de faire une attaque de type man-in-the-middle sur l’application mobile pour voir les requêtes réseau et les données, on se fait bloquer si l’on ne se camoufle pas correctement.
J’ai essayé, mais j’ai conclu que ça n’en valait pas la peine à cause du coût et du travail de développement continu. En pratique, certains services de comparaison de prix de supermarchés emploient simplement de la main-d’œuvre à bas coût pour scraper.
Comme c’est un projet secondaire, si les contraintes de temps deviennent trop fréquentes, j’arrêterai simplement et je publierai tout le code et toutes les données.
En revanche, je me demande comment il est possible que les requêtes réseau n’apparaissent pas dans l’onglet Réseau.
Pour moi, la partie la plus difficile est de relier et comparer les produits entre supermarchés.
Ce serait bien d’avoir de la transparence sur les prix des produits. On pourrait suivre ce processus beaucoup plus facilement par magasin et par région.
Par exemple, comparer le prix du lait d’avoine, un substitut du lait, par code postal et par épicerie. On pourrait aussi suivre la « shrinkflation », quand le prix reste le même mais que la quantité diminue.
Dans cette optique, il semble que les prix soient suivis, mais je me demande si le coût au gramme ou à l’once est également vérifié. Un fabricant ou un magasin peut garder le même prix tout en donnant moins de produit au consommateur ; je me demande si l’outil peut détecter cela.
Cela reste une fonctionnalité tout à fait ajoutable, et s’il y a eu de la shrinkflation, on pourrait montrer le moment où elle s’est produite.
Sur le marché suédois, nous faisons cela depuis plus de 8 ans. Il existe un site web appelé https://www.matspar.se/, où les clients peuvent parcourir tous les produits des principaux magasins en ligne, comparer les prix, puis ajouter les produits souhaités à leur panier
À la fin, ils peuvent comparer le montant total du panier, frais de livraison inclus, puis exporter le panier vers le magasin de leur choix pour passer commande
En tant que cofondateur et CTO actuel, j’ai beaucoup travaillé sur le scraping et la maintenance au fil des années. Nous scrappons chaque jour plus de 30 millions de prix
D’après les données publiques, le chiffre d’affaires de l’entreprise est d’environ 400 000 dollars et elle compte 6 employés : https://www.allabolag.se/5590076351/matspar-i-sverige-ab
Quand j’ai déménagé dans une nouvelle région, j’ai suivi les prix, mais aujourd’hui je trouve qu’il est bien plus simple de faire mes courses dans un marché ou deux grandes enseignes qui maintiennent toujours des prix bas
En Europe, ce serait probablement Aldi/Lidl
Aux États-Unis, ce pourrait être Costco/Trader Joe’s
En ligne, il y a CamelCamelCamel/Amazon. Ce n’est pas pour l’alimentaire, plutôt pour la santé/beauté et certains produits électroniques
Quand on peut acheter directement auprès du fabricant, c’est parfois encore mieux. Par exemple, j’ai acheté en gros sur un site de vente en gros une marque de savon précise que j’aime bien, et cela revenait à moins de la moitié du prix de détail. Acheter directement du shampoing au gallon était aussi bien moins cher que chez n’importe quel détaillant
Trader Joe’s est de meilleure qualité, mais généralement plus cher