4 Punkte von GN⁺ 2023-11-03 | 1 Kommentare | Auf WhatsApp teilen
  • Bear Blog hat wegen Einschränkungen bei Geschwindigkeit, Effizienz und Stabilität ein eigenes Analytics-System implementiert, das ohne clientseitiges JavaScript auskommt
  • Gängige Analytics-Skripte können von Adblockern blockiert werden, und reine Server-Logs können durch Crawler, Scraper und GPT-basierte Parser verzerrte Besuchsstatistiken liefern
  • Wenn im CSS jeder Seite body:hover ausgelöst wird, ruft border-image den Endpunkt /hit/{{ post.id }}/ auf und nutzt Hover bzw. mobiles Scrollen als Lesesignal
  • Der Server prüft anhand des User-Agents, ob es sich um einen Bot handelt, sowie Browser und Plattform; die IP-Adresse wird nur zur Länderbestimmung genutzt und anschließend per IP+Datum-Hash zur Deduplizierung von Lesevorgängen verwendet
  • Liest dieselbe IP am selben Tag über mehrere Geräte, wird dies als ein einziger Lesevorgang gezählt; zugleich lassen sich ohne Speicherung identifizierender Informationen einfach Unique Reads pro Seite erfassen

Lese-Events per CSS-Hover erzeugen

  • Das Analytics-System von Bear Blog folgt der Einschränkung, clientseitig kein JavaScript zu verwenden
  • Gängige Analytics-Tools können mit clientseitigem JavaScript teilweise einschätzen, ob Traffic echt ist oder von Bots stammt; viele Adblocker blockieren jedoch nicht nur Google Analytics, sondern auch Analytics-Skripte wie Fathom oder Plausible
  • Allein das Parsen von Server-Logs kann eine verzerrte Sicht erzeugen, weil Suchmaschinen-Crawler, Scraper und GPT-basierte Parser wie normaler Traffic darin auftauchen
  • Bear fügt jeder Seite das folgende CSS hinzu, sodass body:hover ausgelöst wird, wenn Nutzer den Cursor über die Seite bewegen oder auf Mobilgeräten scrollen
body:hover {
  border-image: url("/hit/{{ post.id }}/?ref={{ request.META.HTTP_REFERER }}");
}
  • Wenn body:hover getriggert wird, wird die Hit-URL des jeweiligen Beitrags aufgerufen
  • Die Information, die dieser Anfrage explizit erneut hinzugefügt wird, ist der Referrer; die Schreibweise HTTP_REFERER ist eine falsch geschriebene Form, die wie ein Standard erhalten geblieben ist
  • Bear nutzt den Umstand, dass Bots nicht hovern, und wertet den Aufruf auf Basis von body:hover als Signal für menschliche Leser
  • Anschließend prüft der Server, ob der User-Agent ein Bot ist, und extrahiert Browser und Plattform aus dem User-Agent-String

Deduplizierung von Lesevorgängen ohne identifizierende Informationen

  • Die zweite Einschränkung besteht darin, keine Informationen, mit denen Leser identifiziert werden können, in Browser-Cookies oder auf dem Server zu speichern
  • Die IP-Adresse wird nur zur Bestimmung des Landes verwendet; vor dem Speichern werden IP-Adresse und Datum gemeinsam gehasht
  • Nachfolgende Anfragen an dieselbe Seite werden mit dem Hash aus IP-Adresse + Datum verglichen und verworfen, wenn sie Duplikate sind
  • Bei diesem Verfahren zählt eine IP-Adresse innerhalb eines Tages für eine Seite als ein einziger Read
  • Die IP-Adresse im Klartext wird nicht gespeichert, und der Hash mit enthaltenem Datum hat faktisch eine Ablaufwirkung von einem Tag
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)
  • Der IP-Hash dient nur dazu, doppelte Hits innerhalb eines Tages zu verhindern; alle Seitenaufrufe werden grundsätzlich als eindeutig behandelt
  • Am Ende jedes Tages entfernt ein Hintergrundjob die Hashes aus den Hit-Logs, um nicht mit übermäßig strengen Auslegungen der DSGVO in Konflikt zu geraten
  • Ein Nachteil dieses Ansatzes ist, dass mehrere Geräte hinter derselben IP-Adresse am selben Tag nur als ein einziger Read gezählt werden
  • Bear geht davon aus, dass solche Fälle nur einen kleinen Teil des Traffics ausmachen, und sieht den CSS-basierten Ansatz im Vergleich zu mehreren anderen Analytics-Erfassungsmethoden als knapper, einfacher und dennoch als Quelle genauer Lesezahlen an

1 Kommentare

 
GN⁺ 2023-11-03
Meinungen auf Hacker News
  • Ich bin der Autor. Der Hash der IP-Adresse dient in diesem Kontext nur dazu, doppelte Aufrufe innerhalb eines Tages zu verhindern.
    Er soll im Grunde jeden Seitenaufruf eindeutig machen, und am Ende jedes Tages leert ein Worker-Job den Hash, während die Aufrufinformationen erhalten bleiben. Ich habe dem Artikel eine Änderung hinzugefügt, um das klarzustellen.

    • Ich frage mich, ob du auch überlegt hast, tatsächlich ein kleines transparentes Bild mit einem privaten Cache-Header auszuliefern, der um Mitternacht abläuft, und die IP gar nicht zu speichern.
    • Wenn 10 Nutzer über ein weltweit genutztes gemeinsames VPN dieselbe IP verwenden und die Website besuchen, zählt das dann nur als 1 Nutzer? Dasselbe gilt für Firmennetzwerke; IP ist eine schlechte Metrik.
  • Als ich die Idee zum ersten Mal sah, CSS-ausgelöste Requests für Analytics zu nutzen, fand ich sie wirklich großartig.
    Jemand auf Twitter legte ein unsichtbares Raster aus Rechtecken über die Seite und ließ beim Hover über jede Zelle ein eindeutiges Hintergrundbild laden, um damit Maus-Tracking zu machen. Jedes Hintergrundbild schickt einen bestimmten Request an den Server, den der Server dann interpretiert.
    Aus Spaß habe ich diese Idee in einem Sommer erweitert und einen „CSS-only asynchronen Webchat“ ohne JavaScript gebaut: https://github.com/kkuchta/css-only-chat

  • Nur Datum und IP zu hashen und damit zu behaupten, die IP-Adresse zu anonymisieren, ist schlicht Security Theater.
    Kryptografische Hashes sind darauf ausgelegt, schnell berechnet zu werden. Mit hashcat kann man auf einem MacBook M1 Pro 6 Milliarden MD5-Hashes pro Sekunde berechnen, und es gibt nur 4 Milliarden IPv4-Adressen. Man kann den gesamten Bereich per Brute Force durchprobieren und die IP-Adresse finden; praktisch ist das dasselbe, als würde man den Hash zurückrechnen.
    Selbst wenn man statt des kaputten MD5 einen sicheren Hash wie SHA-256 verwendet, gilt das genauso.

    • Abgesehen davon, dass es technisch trivial ist, aus dem Hash die IP zurückzugewinnen, haben EU-Datenschutzbehörden sehr klar gesagt, dass das Hashen personenbezogener Daten keine Anonymisierung ist.
      Selbst wenn man den vollständigen Namen einer Person hasht, kann man später die Frage beantworten: „Entspricht dieser Hash diesem bestimmten vollständigen Namen?“ Dass man diese Frage beantworten kann, bedeutet, dass der Anonymisierungsprozess reversibel ist.
    • Ich bin der Autor. Ich habe es unten schon geschrieben, aber es scheint besser in diesen Thread zu passen.
      Der Hash der IP-Adresse dient in diesem Kontext nur dazu, doppelte Aufrufe innerhalb eines Tages zu verhindern. Er soll im Grunde jeden Seitenaufruf eindeutig machen, und am Ende jedes Tages entfernt ein Worker-Job die IP-Hashes, die dann nicht mehr benötigt werden.
    • Zur Info: In der Diskussion darüber, dass Storybook bei Telemetrie etwas Ähnliches gemacht hat, kam dasselbe Problem auf[0], und selbst ohne jegliche Optimierung dauerte es auf meinem privaten Laptop nur etwa zwei Stunden, gesalzene Hashes für alle IPv4-Adressen zu berechnen.
      [0] https://news.ycombinator.com/item?id=37596757
    • Hashes müssen mit einem Salt versehen werden. Mit Salt ist es in Ordnung, ohne Salt nicht.
      Ob man den Salt dauerhaft speichert oder regelmäßig rotiert, sind nur Implementierungsdetails; der Kernpunkt beim Salten von Analytics-Hashes ist, dass der Salt den Client niemals verlässt.
      Nach der Beschreibung im Artikel sieht es so aus, als gäbe es keinen Salt. Oder es wird das aktuelle Datum wie ein Salt verwendet, aber das ist kein zufälliger Salt und lässt sich für jeden leicht erraten, der wissen will: „Hat IP x.y.z.w am Datum yy-mm-dd besucht?“
      Aus Angreifersicht ist es leicht, solche Probleme zu beurteilen. Wie würde man mit den gegebenen Daten etwas über eine bestimmte Person herausfinden? Wenn das nicht geht, ist es im Allgemeinen wahrscheinlich okay, diese Daten zu speichern.
    • Könnte man nicht einen geheimen oder rotierenden Salt verwenden? Im Beispielcode steht das nicht, daher ist der Einwand wohl berechtigt. Trotzdem könnte man es mit nur einer Ergänzung ziemlich vernünftig absichern.
      Ich mache mir allerdings Sorgen, dass schon solches Security Theater ausreichen könnte, um verschiedene Datenschutzgesetze und -vorschriften zu passieren.
  • Es wirkt zwar clever, aber body:hover wird reine Tastaturnutzer und User Agents ohne Zeigegerät, also Nutzer assistiver Technologien, wahrscheinlich fast vollständig verpassen.
    Auch wenn diese Gruppen randständig sein mögen, ist es immer ein sehr schlechtes Signal, wenn man sieht, dass sie in irgendeiner Weise ausgeschlossen werden.
    Ich weiß nicht, ob es mit reinem CSS eine Möglichkeit gibt, über alle User Agents hinweg zu 100 % zuverlässig zu erkennen, dass „ein echter Nutzer diesen Artikel liest“, und einen HTTP-Request zu senden; ich bezweifle es auch. Manche unterstützen CSS gar nicht oder haben das Laden dekorativer CSS-Bilder deaktiviert.
    Ein moderner Selektor, der helfen könnte, ist :root:focus-within, aber der Nutzer muss tatsächlich ein interaktives Element fokussieren, und nicht jeder User Agent garantiert das. Auch das hochmoderne scrollgebundene Animationsfeature @scroll-timeline wäre denkbar, aber Braille-Reader würden wahrscheinlich weiterhin durchs Raster fallen.

    • Randständig? Betrifft das nicht Smartphones und Tablets, die :hover nicht unterstützen, also mehr als 50 % der User Agents? Natürlich sofern keine Maus angeschlossen ist.
    • So wie es geschrieben ist, hängt es auf Geräten mit Pointer von der Position des Pointers ab. Befindet sich der Pointer innerhalb des mittleren 760px-Bereichs, wird es aktiviert, außerhalb nicht.
      Dieser Bereich ist die Breite der Content-Spalte plus 20px Padding auf beiden Seiten. Dadurch werden manche Tastaturnutzer erfasst und manche Mausnutzer, insbesondere mit großen Viewports, nicht erfasst.
  • Dass „nicht nur schlimme Dinge wie Google Analytics, sondern auch Fathom und Plausible in Browsern mit Adblockern Schwierigkeiten haben, Aktivitäten aufzuzeichnen“, liegt meiner Ansicht nach daran, dass sie im Grunde versuchen, in einer toxischen Ödnis zu überleben.
    Nutzer wie wir haben von diesem ganzen Konzept genug, und wenn CSS-Analytics populär werden, wird es meiner Meinung nach auch Versuche geben, sie zu umgehen.

    • Warum?
      Ich habe Piwik/Matomo, Plausible und Fathom in uBlock manuell entsperrt. Ich sehe keinen Schaden darin, was und wie sie tracken. Und sie geben Seitenbetreibern nützliche Informationen zur „Verbesserung des Dienstes“.
      Plausible sammelt zum Beispiel weniger Informationen über mich als normale nginx- oder Apache-Logs. Aus Sicht eines Bloggers ist es wichtig zu sehen, ob ein Beitrag auf HN gelandet ist, ob er irgendwo verlinkt wurde, welche Inhalte als wertvoll wahrgenommen und welche ignoriert werden. Nur so kann man Inhalte schreiben, die die Leute tatsächlich lesen wollen, und sie über Kanäle verbreiten, die man tatsächlich nachvollziehen kann.
    • Die access.log eines Webservers in einen Analytics-Dienst einzuspeisen, verhindert gar nichts.
      Im Gegenteil: Weil es praktisch unmöglich ist, allen Bot-Traffic nur anhand des User-Agents herauszufiltern, können die Zahlen aufgebläht werden.
    • Was sich für mich im Web wie eine toxische Ödnis anfühlt, sind die ganzen Anzeigen. Tracking ist ein sehr viel subtileres Problem; langfristig besteht der Schaden darin, dass ein experimentierbarer digitaler Zwilling entstehen kann, mit dem man die optimale Methode findet, mich zu manipulieren.
      Ich weiß nicht, wie viele Menschen davor tatsächlich Angst haben. Die Reaktionen dürften von „ja, gruselig“ bis „Unsinn, das ist nur Science-Fiction“ reichen.
    • Mir fällt uMatrix ein, das sogar das Laden von CSS blockieren konnte.
    • Diese Methode ist nicht schwerer zu blockieren als eine JavaScript-basierte. Am Ende geht es nur darum, Requests an ein bestimmtes URL-Muster zu blockieren.
  • Das ist seit Jahrzehnten als Tracking-Pixel bekannt.

    • Wird auch in E-Mails verwendet. Ein transparentes 1x1-Bild zu laden ist zuverlässiger, als ein Hover-Event auszulösen, aber Adblocker blockieren solche Bilder häufig.
    • Stimmt. Mit CSS gibt es allerdings ein paar interessante Aspekte. Mit :hover kann man Bots herausfiltern, die keinen vollständigen WebDriver verwenden, also die meisten Bots.
      In gewisser Weise könnte es sogar besser sein, eine nahezu leere .css-Datei zusammen mit supports per @import zu laden. Adblocker erkennen transparente 1px-Tracking-Pixel sehr gut, werden .css-Dateien aber wahrscheinlich seltener blockieren, um Layouts nicht zu zerstören. In diesem Fall ginge allerdings der clevere Vorteil von :hover verloren.
  • Eine ernst gemeinte Frage, auch wenn ich befürchte, dass sie abwertend klingt: Was ist das Ziel beim Sammeln von Analytics-Daten auf einem nichtkommerziellen persönlichen Blog, wie es Bearblog zu sein scheint?

    • Aus der Sicht eines Autors, der seit etwa 2000 kontinuierlich bloggt und sich die ganze Zeit sehr für „Statistiken“ interessiert hat, kann ich dazu etwas sagen.
      Der Hauptgrund für mein Interesse an Analytics ist zu sehen, ob meine Texte gelesen werden. Oberflächlich, und in gewisser Weise, geht es um Eitelkeit, aber tatsächlich geht es um die Verbindung zwischen Autor und Lesern. Ich bin wirklich neugierig, worauf Leser reagieren, und möchte ihnen mehr davon geben. Dieses „davon“ kann das Thema sein, der Tonfall oder die Länge. Es hilft mir, Stoffe auf meine Leserschaft zuzuschneiden. Letztlich kann ich über ein Dutzend Themen auf zwei Dutzend Arten schreiben. Natürlich schreibe ich über das, was mir gefällt, aber ich schleife es so, dass es bei den Lesern besser ankommt.
      In diesem Sinne sind Analytics auch eine Möglichkeit, die Leser kennenzulernen. Bei Blogs mit hoher Beteiligung gaben mir Analytics so etwas wie ein unscharfes Porträt der Leserschaft. Ich konnte nicht nur sehen, was sie mögen, sondern auch wann sie es mögen. Ob sie gleich morgens lesen, in der Mittagspause oder spätabends; das half mir zu entscheiden, ob ich zu bestimmten Zeiten veröffentliche, oder gab mir Sicherheit bei dieser Entscheidung. Natürlich waren das alles unscharfe Informationen, aber sie halfen wirklich dabei, aktiver mit den Lesern in Verbindung zu treten.
    • Wegen der Feedback-Schleife. Anders als viele denken, sind Analytics nicht nur für Werbung oder den Verkauf von Daten da, sondern für die Analyse der Performance von Website und Inhalten.
      Natürlich kann man sie für Werbung nutzen und missbrauchen, aber wenn ich Feedback zu dem haben möchte, was ich mache, sind sie unverzichtbar.
      Ob 12 oder 12.000 Menschen eine Website lesen, kann finanziell wertlos sein. Aus persönlicher Sicht ist es aber gut zu wissen, was Menschen von mir lesen wollen; ich kann das Gefühl haben, dass die Zeit fürs Schreiben gut investiert war, und, wenn ich möchte, stärker in Richtung der beliebteren Inhalte nachjustieren.
    • Vielleicht Neugier? Ich möchte wissen, ob jemand liest, was ich geschrieben habe. Es ist auch nützlich zu wissen, wofür sich die Leute interessieren.
      Auch als persönlicher Blogger kann man Inhalte an die Leser anpassen wollen. Es ist gut zu wissen, dass ein Beitrag zu einem Thema von 500 Leuten gelesen wurde und ein Beitrag zu einem anderen Thema nur von 3.
  • Anfang des Jahres wollte ich so etwas ausprobieren, habe aber beim Bau der Web-UI die Motivation verloren. Mein Ansatz ist nicht CSS, sondern lädt einfach per Tag ein Fake-Bild.
    https://github.com/nolytics

  • Warum holt man diese Informationen nicht einfach vom HTTP-Server?

    • Im Blogbeitrag wird es so erklärt:
      „Es gibt immer die Möglichkeit, Server-Logs zu parsen, und man kann grob verstehen, welche Arten von Traffic auf den Server zugreifen. In der Regel sieht jedoch der gesamte Server-Traffic gleich aus. Technisch gesehen sollten Bots einen User-Agent haben, der sie als Bots ausweist, aber weil sie versuchen, Informationen wie ein ‚Mensch‘ mit Browser abzugreifen, kennzeichnen sie sich fast nie so. Im Kern führt die reine Analyse von Server-Logs wegen Suchmaschinen-Crawlern, Scrapern und inzwischen auch GPT-basierten Parsern dazu, dass der Traffic verzerrt dargestellt wird.“
    • Bots sind alle mit drin.
    • Wenn man serverless betreibt, ist es schwierig.
  • Wie speichert man Analysedaten?
    Nehmen wir an, es gibt eine E-Commerce-Website und Produkte, die verkauft werden sollen. Neben Analytics entscheidet man sich, bestimmte Aktionen direkt zu loggen, etwa den Besuch einer Produktdetailseite im eingeloggten Zustand. Daher möchte man Dinge wie Nutzer-ID, Produkt-ID und Zeitstempel speichern.
    Wie sollte man das in der Praxis speichern? Naiv dachte ich, man könne es einfach in eine Tabelle schreiben. Der DBA fragte, wie lange die Daten benötigt würden, und ich antwortete: mindestens einen Monat. Darauf sagte er okay; vermutlich hat er einen Job eingerichtet, der ältere Daten in eine andere Tabelle verschiebt.
    Wie speichert man solche Logs tatsächlich, und wie lange bewahrt man sie auf?

    • Wenn es nicht um gewaltige Größenordnungen geht, ist es völlig in Ordnung, sie in eine Postgres-Tabelle zu schreiben. Selbst bei größerem Umfang kann man die Tabelle nach Datum oder einem anderen passenden Attribut partitionieren, sodass man nicht mit riesigen Indizes hantieren muss.
      Ich habe das früher so gemacht und musste bis etwa 1 Milliarde Zeilen nicht einmal über Partitionierung nachdenken. Trotzdem ist es besser, früher zu partitionieren. Diese Erfahrung war nicht angenehm.
    • Eine Analytics-Datenbank ist besser. Gemeint sind Dinge wie ClickHouse oder BigQuery.
      Aggregationen lassen sich damit viel schneller durchführen, und sie kommen gut mit vielen spärlich gefüllten Spalten zurecht. Zum Beispiel hat ein paid-Event ein Attribut amount, während ein page_view-Event ein Attribut url hat.
    • Wir speichern 13 Jahre Daten in MySQL. Die Größenordnung liegt bei 5 Millionen Besuchern pro Jahr. Abfragen darauf sind mühsam, daher halten wir zusätzlich eine Kopie in ClickHouse vor. ClickHouse ist wirklich angenehm für Abfragen.
    • Wir verwenden Postgres und TimescaleDB. Wenn die E-Commerce-Website nicht die Größenordnung von amazon.com hat, funktioniert das gut.
      Das Gute an TimescaleDB ist, dass es sich selbst um das Erstellen materialisierter Views für Aggregationen kümmert, die einen interessieren, etwa Produktaufrufe pro Stunde. Wenn man wegen vieler Events vermeiden möchte, dass die Datenbank zu groß wird, kann man auch entscheiden, die Events selbst „wegzuwerfen“ und nur die Aggregationen zu behalten.
    • ClickHouse