4 points par GN⁺ 2023-11-03 | 1 commentaires | Partager sur WhatsApp
  • Bear Blog a implémenté son propre système d’analytics, qui fonctionne sans JavaScript côté client, en raison de contraintes de vitesse, d’efficacité et de stabilité
  • Les scripts d’analytics classiques peuvent être bloqués par les bloqueurs de publicité, et les seuls logs serveur peuvent mélanger crawlers, scrapers et parseurs basés sur GPT, ce qui peut fausser les statistiques de visite
  • Quand body:hover se produit dans le CSS de chaque page, border-image appelle l’endpoint /hit/{{ post.id }}/, utilisant le survol ou le scroll mobile comme signal de lecture
  • Le serveur vérifie via le user-agent s’il s’agit d’un bot, ainsi que le navigateur et la plateforme ; l’adresse IP ne sert qu’à déterminer le pays, puis un hash IP+date supprime les lectures en double
  • Si plusieurs appareils lisent depuis la même IP le même jour, cela compte pour une seule fois, mais cela permet d’agréger simplement le nombre de lectures uniques par page sans stocker d’informations d’identification

Créer un événement de lecture avec CSS hover

  • Le système d’analytics de Bear Blog respecte la contrainte de ne pas utiliser de JavaScript côté client
  • Les outils d’analytics classiques peuvent, via JavaScript côté client, évaluer en partie l’authenticité du trafic ou la présence de bots, mais de nombreux bloqueurs de publicité bloquent non seulement Google Analytics, mais aussi des scripts d’analytics comme Fathom ou Plausible
  • Se contenter de parser les logs serveur peut mélanger les crawlers de moteurs de recherche, les scrapers et les parseurs basés sur GPT au trafic ordinaire, créant une vision biaisée
  • Bear insère le CSS suivant dans chaque page afin que body:hover se déclenche lorsque l’utilisateur place le curseur sur la page ou fait défiler sur mobile
body:hover {
  border-image: url("/hit/{{ post.id }}/?ref={{ request.META.HTTP_REFERER }}");
}
  • Quand body:hover est déclenché, l’URL de hit de l’article concerné est appelée
  • L’information explicitement ajoutée à nouveau dans cette requête est le referrer, et l’orthographe HTTP_REFERER est une faute d’orthographe restée comme une forme standard
  • En exploitant le fait que les bots ne survolent pas la page, l’appel basé sur body:hover sert de signal de lecteur humain
  • Ensuite, le serveur vérifie si le user-agent est un bot et extrait le navigateur et la plateforme depuis la chaîne user-agent

Supprimer les lectures en double sans informations d’identification

  • La deuxième contrainte consiste à ne pas stocker, dans les cookies du navigateur ni sur le serveur, d’informations permettant d’identifier le lecteur
  • L’adresse IP n’est utilisée que pour déterminer le pays et, avant stockage, l’adresse IP et la date sont hashées ensemble
  • Les requêtes ultérieures vers la même page sont comparées au hash adresse IP + date et rejetées s’il s’agit de doublons
  • Avec cette méthode, une adresse IP compte pour un seul read par page sur une journée
  • L’adresse IP brute n’est pas stockée, et le hash incluant la date produit un effet d’expiration à la journée
user_agent = httpagentparser.detect(self.request.META.get('HTTP_USER_AGENT', None))
if user_agent.get('bot', False):
    print('Bot traffic')
    return

ip_hash = hashlib.md5(f"{client_ip(self.request)}-{timezone.now().date()}".encode('utf-8')).hexdigest()
country = get_user_location(client_ip(self.request)).get('country_name', '')
device = user_agent.get('platform', {}).get('name', '')
browser = user_agent.get('browser', {}).get('name', '')
referrer = self.request.GET.get('ref', '')

if referrer:
    referrer = urlparse(referrer)
    referrer = '{uri.scheme}://{uri.netloc}/'.format(uri=referrer)

Hit.objects.get_or_create(
    post_id=self.pk,
    ip_address=ip_hash,
    referrer=referrer,
    country=country,
    device=device,
    browser=browser)
  • Le hash IP ne sert qu’à empêcher les hits en double pendant une journée, et toutes les consultations de pages sont par défaut traitées comme uniques
  • À la fin de chaque journée, une tâche en arrière-plan supprime les hashes des logs de hits afin d’éviter tout conflit avec une interprétation excessivement stricte du RGPD
  • Le fait que plusieurs appareils lisant depuis la même adresse IP le même jour ne soient comptés que comme un seul read constitue une limite de cette approche
  • Bear estime que ces cas ne représentent qu’une petite partie du trafic, et considère que l’approche basée sur CSS fournit un nombre de lectures plus concis, plus simple et plus exact que de nombreuses méthodes de collecte d’analytics

1 commentaires

 
GN⁺ 2023-11-03
Avis sur Hacker News
  • Je suis l’auteur. Le hachage de l’adresse IP, dans ce contexte, sert uniquement à éviter les vues en double au cours d’une même journée.
    Il sert à rendre chaque vue de page unique par défaut, et à la fin de chaque journée une tâche worker vide le hachage tout en conservant les informations de consultation. J’ai ajouté une modification à l’article pour le préciser.

    • Je me demande si tu as envisagé de servir réellement une petite image transparente avec un en-tête de cache privé expirant à minuit, sans jamais stocker l’IP.
    • Si 10 utilisateurs, partout dans le monde, utilisent la même IP via un VPN partagé et visitent le site, est-ce qu’ils ne comptent que pour 1 ? Il y a aussi le cas des réseaux d’entreprise : l’IP est un mauvais indicateur.
  • Quand j’ai vu pour la première fois l’idée d’utiliser des requêtes déclenchées par CSS pour l’analytics, j’ai trouvé ça vraiment brillant.
    Quelqu’un sur Twitter avait superposé à une page une grille de rectangles invisibles et, au survol de chaque cellule, une image de fond unique se chargeait pour faire du suivi de la souris. Chaque image de fond envoyait une requête spécifique au serveur, que celui-ci interprétait ensuite.
    Pour m’amuser, un été, j’ai poussé cette idée plus loin en créant un « chat web asynchrone uniquement en CSS », sans JavaScript : https://github.com/kkuchta/css-only-chat

  • Dire qu’on anonymise l’adresse IP en ne hachant que la date et l’IP, c’est simplement du théâtre de sécurité.
    Les hachages cryptographiques sont conçus pour être calculés rapidement. Avec hashcat, on peut calculer 6 milliards de hachages MD5 par seconde sur un MacBook M1 Pro, et il n’y a que 4 milliards d’adresses IPv4. On peut retrouver l’adresse IP en brute-forçant tout l’espace, ce qui revient en pratique à inverser le hachage.
    Même en utilisant un hachage sûr comme SHA-256 au lieu de MD5, qui est cassé, le problème reste le même.

    • Indépendamment du fait qu’il soit techniquement trivial de retrouver l’IP à partir du hachage, les autorités de protection des données de l’UE ont été très claires : hacher des données personnelles ne constitue pas une anonymisation.
      Même si vous hachez le nom complet de quelqu’un, vous pouvez répondre plus tard à la question : « ce hachage correspond-il à ce nom complet précis ? ». Pouvoir répondre à cette question signifie que le processus d’anonymisation est réversible.
    • Je suis l’auteur. Je l’ai aussi écrit plus bas, mais cela semble plus pertinent dans ce fil.
      Le hachage de l’adresse IP, dans ce contexte, sert uniquement à éviter les vues en double au cours d’une même journée. Il sert à rendre chaque vue de page unique par défaut, et à la fin de chaque journée une tâche worker supprime les hachages d’IP devenus inutiles.
    • Pour référence, le même problème avait été soulevé dans une discussion où Storybook faisait quelque chose de similaire avec la télémétrie[0], et même sans aucune optimisation, il me faut environ deux heures sur mon ordinateur portable personnel pour calculer les hachages salés de toutes les adresses IPv4.
      [0] https://news.ycombinator.com/item?id=37596757
    • Il faut ajouter un sel au hachage. Avec un sel, ça va ; sans sel, ça ne va pas.
      Le fait de conserver le sel de façon permanente ou de le renouveler régulièrement relève de détails d’implémentation ; l’essentiel, quand on sale un hachage à des fins d’analytics, est que le sel ne quitte jamais le client.
      D’après la description de l’article, on dirait qu’il n’y a pas de sel. Ou alors la date du jour semble servir de sel, mais ce n’est pas un sel aléatoire : toute personne voulant savoir « l’IP x.y.z.w a-t-elle visité le site à la date aa-mm-jj ? » peut facilement le deviner.
      Du point de vue d’un attaquant, ce genre de problème est facile à évaluer. Avec les données disponibles, comment pourrais-je apprendre quelque chose sur une personne donnée ? Si ce n’est pas possible, il est généralement assez probable que ces données puissent être stockées sans problème.
    • On pourrait peut-être utiliser un sel secret ou un sel tournant ? Il n’y en a pas dans l’exemple de code, donc la remarque me semble juste. Cela dit, en en ajoutant simplement un, on pourrait rendre le système raisonnablement sûr.
      Je crains toutefois que ce genre de théâtre de sécurité suffise malgré tout à passer de nombreuses lois et réglementations sur les données personnelles.
  • Ça a l’air malin, mais body:hover risque de passer presque totalement à côté des utilisateurs au clavier uniquement et des agents utilisateurs qui n’utilisent pas de dispositif de pointage, c’est-à-dire des utilisateurs de technologies d’assistance.
    Même si ces groupes peuvent être marginaux, voir des personnes exclues d’une manière ou d’une autre est toujours un très mauvais signal.
    Je ne sais pas s’il existe, avec du CSS de base, une manière fiable à 100 % de détecter dans tous les agents utilisateurs qu’« un vrai utilisateur est en train de lire cet article » et d’envoyer une requête HTTP ; j’en doute même. Certains ne prennent pas du tout en charge CSS, ou peuvent avoir désactivé le chargement des images décoratives via CSS.
    Parmi les sélecteurs modernes qui pourraient aider, il y a :root:focus-within, mais l’utilisateur doit effectivement donner le focus à un élément interactif, et tous les agents utilisateurs ne le garantissent pas. Les animations dernier cri liées au défilement, comme @scroll-timeline, pourraient aussi être une possibilité, mais les lecteurs braille risquent toujours d’être exclus.

    • Marginaux ? Cela ne concerne-t-il pas les téléphones et tablettes qui ne prennent pas en charge :hover, soit plus de 50 % des agents utilisateurs ? À condition, bien sûr, qu’aucune souris ne soit branchée.
    • Tel que c’est écrit, sur les appareils dotés d’un pointeur, cela dépend de la position du pointeur. Si le pointeur se trouve dans la zone de 760 px au centre, c’est activé ; s’il est en dehors, non.
      Cette zone correspond à la largeur de la colonne de contenu plus 20 px de padding de chaque côté. Résultat : certains utilisateurs au clavier seront détectés, tandis que certains utilisateurs de souris, en particulier ceux qui utilisent un grand viewport, ne le seront pas.
  • Dire que « non seulement les choses néfastes comme Google Analytics, mais aussi Fathom et Plausible ont du mal à enregistrer l’activité dans les navigateurs avec bloqueur de publicité », c’est, à mon avis, parce qu’ils essaient en réalité de survivre dans un désert toxique
    Les utilisateurs comme nous en ont assez de tout ce concept, et donc si l’analytics via CSS devient populaire, il y aura aussi des tentatives pour le contourner

    • Pourquoi ?
      J’ai déverrouillé manuellement Piwik/Matomo, Plausible et Fathom dans uBlock. Je ne vois pas en quoi ce qu’ils suivent, ni la manière dont ils le font, est nuisible. Et cela donne aux exploitants de sites des informations utiles pour « améliorer le service »
      Par exemple, Plausible collecte moins d’informations sur moi que des logs nginx ou Apache classiques. Pour un blogueur, il est important de voir si un billet est arrivé sur HN, s’il a été lié quelque part, quels contenus sont jugés utiles et lesquels sont ignorés. C’est ce qui permet d’écrire ce que les gens veulent vraiment lire, et de le diffuser via les canaux où ils peuvent effectivement le découvrir
    • Envoyer l’access.log du serveur web dans un service d’analytics ne bloque rien
      Au contraire, comme il est pratiquement impossible de filtrer tout le trafic de bots uniquement à partir du user-agent, les chiffres peuvent être gonflés
    • Ce qui me donne l’impression d’un désert toxique sur le Web, ce sont toutes les publicités. Le tracking est un problème bien plus subtil : à long terme, le dommage possible est qu’on puisse créer un jumeau numérique sur lequel expérimenter afin de trouver la meilleure façon de me manipuler
      Je ne sais pas combien de personnes en ont réellement peur. Les réactions iraient sans doute de « oui, c’est glaçant » à « n’importe quoi, c’est juste de la SF »
    • Ça me fait penser à uMatrix, qui pouvait même bloquer le chargement du CSS
    • Cette méthode n’est pas plus difficile à bloquer que l’approche JavaScript. Au final, il s’agit seulement de bloquer des requêtes vers un certain motif d’URL
  • C’est connu depuis des décennies sous le nom de pixel de suivi

    • C’est aussi utilisé dans les e-mails. Charger une image transparente de 1×1 est plus fiable que de déclencher un événement hover, mais les bloqueurs de publicité bloquent souvent ce genre d’images
    • Exact. Cela dit, le faire en CSS présente quelques aspects intéressants. Avec :hover, on peut filtrer les bots qui n’utilisent pas un WebDriver complet, c’est-à-dire la plupart des bots
      À certains égards, il serait peut-être préférable de charger via @import, avec supports, un fichier .css presque vide. Les bloqueurs de publicité repèrent très bien les pixels de suivi transparents de 1 px, mais ils sont moins susceptibles de bloquer des fichiers .css pour éviter de casser la mise en page. En revanche, on perd alors l’avantage astucieux de :hover
  • Vraie question, mais j’ai peur qu’elle soit lue comme un avis méprisant : quel est l’objectif de collecte de données d’analytics sur un blog personnel non commercial qui ressemble à Bearblog ?

    • Je peux répondre du point de vue de quelqu’un qui blogue régulièrement depuis environ 2000 et qui s’est toujours beaucoup intéressé aux « statistiques »
      La principale raison de s’intéresser à l’analytics est de voir si les textes sont lus. En surface, et aussi d’une certaine manière, c’est de la vanité, mais en réalité cela relève du lien entre l’auteur et ses lecteurs. On veut vraiment savoir à quoi les lecteurs réagissent, et leur en donner davantage. Ce « davantage » peut être un sujet, un ton, une longueur. Cela aide à ajuster les idées à son lectorat. Au fond, je peux écrire sur une douzaine de sujets de vingt-quatre façons différentes. Bien sûr, j’écris ce qui me plaît, mais je l’affine pour que cela résonne mieux auprès des lecteurs
      En ce sens, l’analytics est aussi une façon d’apprendre à connaître ses lecteurs. Sur les blogs très actifs, l’analytics donnait une sorte de portrait flou du lectorat. On pouvait voir non seulement ce qu’il aimait, mais aussi quand il l’aimait. S’ils lisaient dès le matin, pendant la pause déjeuner ou tard le soir ; cela aidait à décider de publier à certains moments, ou à conforter cette décision. Bien sûr, tout cela restait flou, mais cela aidait vraiment à créer un lien plus actif avec les lecteurs
    • C’est à cause de la boucle de rétroaction. Contrairement à ce que beaucoup pensent, l’analytics ne sert pas uniquement à la publicité ou à la vente de données : il sert à analyser les performances d’un site et de ses contenus
      Bien sûr, cela peut être utilisé pour la publicité et peut être abusé, mais si l’on veut du feedback sur ce que l’on fait, c’est indispensable
      Qu’un site soit lu par 12 personnes ou par 12 000, cela peut n’avoir aucune valeur financière. Mais d’un point de vue personnel, il est agréable de savoir ce que les gens veulent lire chez soi, de sentir que le temps passé à écrire a été bien utilisé, et, si on le souhaite, d’ajuster vers ce qui est plus populaire
    • Probablement par curiosité ? J’ai envie de savoir si quelqu’un lit ce que j’écris. Il est aussi utile de savoir ce qui intéresse les gens
      Même un blogueur personnel peut vouloir adapter son contenu à son lectorat. C’est appréciable de savoir qu’un billet sur tel sujet a été lu par 500 personnes, tandis qu’un autre n’a été lu que par 3
  • J’ai essayé de faire quelque chose comme ça plus tôt cette année, mais j’ai perdu ma motivation en construisant l’interface web. Mon approche ne reposait pas sur CSS, mais consistait simplement à charger une fausse image via une balise
    https://github.com/nolytics

  • Pourquoi ne pas simplement obtenir ces informations depuis le serveur HTTP ?

    • Le billet de blog l’explique ainsi
      « Il reste toujours l’option de parser les logs serveur, ce qui permet de se faire une idée approximative des types de trafic qui accèdent au serveur. Mais, en général, tout le trafic serveur se ressemble. Techniquement, les bots devraient avoir un user-agent qui les identifie comme tels, mais comme ils essaient de scraper les informations comme le ferait une “personne” utilisant un navigateur, ils s’identifient rarement ainsi. En substance, si l’on utilise uniquement les logs serveur pour l’analytics, les crawlers de moteurs de recherche, les scrapers et désormais les parseurs basés sur GPT sont si nombreux qu’ils faussent la vision du trafic »
    • Tous les bots sont mélangés
    • C’est difficile si l’on fonctionne en serverless
  • Comment stocker les données d’analyse ?
    Imaginons qu’on ait un site e-commerce et des produits à vendre. En plus de l’analytics, on décide de journaliser directement certaines actions, comme la visite d’une page de détail produit par un utilisateur connecté. On veut donc stocker des éléments comme l’ID utilisateur, l’ID produit et l’horodatage.
    En pratique, comment faut-il les stocker ? Naïvement, je pensais qu’il suffisait de les mettre dans une table. Le DBA a demandé combien de temps les données devaient être conservées, et j’ai répondu au moins un mois. Il a dit d’accord, et j’imagine qu’il a prévu une tâche pour déplacer les données plus anciennes vers une autre table.
    En réalité, comment stocke-t-on ce type de logs, et combien de temps les conserve-t-on ?

    • Si l’échelle n’est pas énorme, les mettre dans une table Postgres est tout à fait acceptable. Même à grande échelle, on peut partitionner la table par date ou selon un autre attribut approprié, afin d’éviter d’avoir à gérer des index gigantesques.
      Je l’ai déjà fait ainsi, et je n’ai même pas eu besoin de penser au partitionnement avant d’atteindre environ 1 milliard de lignes. Cela dit, mieux vaut partitionner plus tôt. Cette expérience n’a pas été agréable.
    • Une base de données analytique est préférable. Des outils comme ClickHouse ou BigQuery.
      Ils permettent de faire des agrégations beaucoup plus rapidement et gèrent bien de nombreuses colonnes clairsemées. Par exemple, un événement paid aura un attribut amount, tandis qu’un événement page_view aura un attribut url.
    • Nous conservons 13 ans de données dans MySQL, avec un volume de 5 millions de visiteurs par an. Les requêter là-bas est pénible, donc nous maintenons aussi une copie dans ClickHouse. ClickHouse est vraiment très pratique pour les requêtes.
    • Nous utilisons Postgres et TimescaleDB. Si votre site e-commerce n’est pas à l’échelle d’amazon.com, cela fonctionne bien.
      L’intérêt de TimescaleDB, c’est qu’il prend en charge automatiquement la création de vues matérialisées pour les agrégats qui vous intéressent, comme le nombre de vues produit par heure. Si vous avez beaucoup d’événements et voulez éviter que la base de données ne devienne trop volumineuse, vous pouvez aussi choisir de “jeter” les événements eux-mêmes et de ne conserver que les agrégats.
    • ClickHouse