- Bear Blogは、速度・効率・安定性の制約から、クライアントJavaScriptなしで動作する独自の分析システムを実装した
- 一般的な分析スクリプトは広告ブロッカーに遮断されることがあり、サーバーログだけではクローラー・スクレイパー・GPTベースのパーサーまで混ざって訪問統計が歪む可能性がある
- 各ページのCSSで
body:hoverが発生すると、border-imageで/hit/{{ post.id }}/エンドポイントを呼び出し、hoverやモバイルスクロールを閲覧シグナルとして使う
- サーバーはuser-agentでボットかどうか・ブラウザー・プラットフォームを確認し、IPアドレスは国判定にのみ使ったうえで、IP+日付ハッシュで重複閲覧を除去する
- 同じIPから同じ日に複数デバイスで読んでも1回として数えられるが、識別情報を保存せずにページごとのユニーク閲覧数をシンプルに集計できる
CSS hoverで閲覧イベントを作る
- Bear Blogの分析システムは、クライアント側のJavaScriptを使わない制約に従っている
- 一般的な分析ツールはクライアントJavaScriptでトラフィックの真正性やボットかどうかをある程度判断できるが、多くの広告ブロッカーはGoogle Analyticsだけでなく、FathomやPlausibleのような分析スクリプトも遮断する
- サーバーログの解析だけでは、検索エンジンクローラー、スクレイパー、GPTベースのパーサーが通常トラフィックに混ざり、歪んだ見方が生じうる
- Bearは各ページに次のCSSを入れ、ユーザーがページ上にカーソルを置いたり、モバイルでスクロールしたりしたときに
body:hoverが発生するようにしている
body:hover {
border-image: url("/hit/{{ post.id }}/?ref={{ request.META.HTTP_REFERER }}");
}
body:hoverがトリガーされると、その記事のhit URLが呼び出される
- このリクエストで明示的に追加している情報はreferrerであり、
HTTP_REFERERという綴りは誤綴りのまま標準のように残ったものである
- ボットはhoverしないという点を利用し、
body:hoverベースの呼び出しを人間の読者のシグナルとしている
- その後サーバー側でuser-agentがボットかどうかを確認し、user-agent文字列からブラウザーとプラットフォームを抽出する
識別情報なしで重複閲覧を除去する
- 2つ目の制約は、読者を識別できる情報をブラウザークッキーやサーバーに保存しないことだった
- IPアドレスは国を判断するためにのみ使用し、保存前にはIPアドレスと日付を一緒にハッシュ化する
- 同じページへの後続リクエストは
IPアドレス + 日付ハッシュと比較し、重複なら破棄する
- この方式では、1日の間に1つのIPアドレスは1ページにつき1回のreadとして計算される
- IPアドレスの生データは保存せず、日付を含むハッシュには日単位で期限切れになる効果がある
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)
- IPハッシュは1日の重複hitを防ぐ用途にのみ使われ、すべてのページ閲覧は基本的にユニークとして処理される
- 毎日の終了時にバックグラウンドジョブがhitログからハッシュを削除し、過度に厳格なGDPR解釈と衝突しないようにしている
- 同じIPアドレスから同じ日に複数デバイスで読んでも1回のreadとしてしか扱われない点は、この方式の欠点である
- Bearは、このようなケースはトラフィックのごく一部だと見ており、CSSベースの方式は複数の分析収集手法よりも簡潔でシンプルでありながら、より正確な閲覧数を提供すると考えている
1件のコメント
Hacker News の意見
作者です。IPアドレスのハッシュは、この文脈では1日の中で重複閲覧を防ぐためだけのものです
各ページビューを基本的に一意にするためのもので、毎日の終わりにワーカーのジョブが閲覧情報は残しつつハッシュを空にします。記事にも明確にするため修正を追加しました
CSSで発生するリクエストを分析に使うというアイデアを初めて見たとき、本当にすごいと思いました
Twitterのある人が、ページ上に見えない四角形のグリッドを敷き、各マスでhoverすると固有の背景画像が読み込まれるようにしてマウス追跡に使っていました。各背景画像がサーバーに特定のリクエストを送り、サーバーがそれを解釈する仕組みです
ある夏に遊びでこのアイデアを拡張し、JavaScriptなしの「CSS専用非同期Webチャット」を作りました: https://github.com/kkuchta/css-only-chat
日付とIPだけをハッシュしてIPアドレスを匿名化するというのは、単なるセキュリティ劇場です
暗号学的ハッシュは高速に計算できるよう設計されています。hashcatを使うとMacBook M1 Proで毎秒60億個のMD5ハッシュを計算でき、IPv4アドレスは40億個しかありません。全範囲を総当たりしてIPアドレスを見つけられるので、実質的にはハッシュを元に戻すのと同じです
壊れたMD5の代わりにSHA-256のような安全なハッシュを使っても同じです
誰かのフルネームをハッシュしても、あとで「このハッシュはこの特定のフルネームと一致するか」という質問に答えられます。この質問に答えられるということは、匿名化プロセスが可逆であるという意味です
IPアドレスのハッシュは、この文脈では1日の中で重複閲覧を防ぐためだけのものです。各ページビューを基本的に一意にするためのもので、毎日の終わりにワーカーのジョブが、もう不要になったIPハッシュを削除します
[0] https://news.ycombinator.com/item?id=37596757
ソルトを永続保存するか、定期的にローテーションするかといったことは実装の詳細にすぎず、分析用ハッシュにソルトを付けるときの核心は、ソルトが絶対にクライアントの外に出ないという点です
記事の説明ではソルトがないように見えます。あるいは現在の日付をソルトのように使っているようですが、これはランダムなソルトではないので、「IP x.y.z.w が yy-mm-dd の日に訪問したか?」を知りたい人なら誰でも簡単に推測できます
攻撃者の視点で見ると、こうした問題は判断しやすいです。与えられたデータで特定の人物について何かを知るにはどうするか? それができないなら、そのデータは保存しても概ね問題ない可能性が高いです
ただ、こうしたセキュリティ劇場だけでも、プライバシー関連のさまざまな法律や規制を通過するには十分なのではないかと心配です
賢く見えはしますが、
body:hoverはキーボード専用ユーザーやポインティングデバイスを使わないユーザーエージェント、つまり支援技術のユーザーをほぼ完全に取りこぼす可能性が高いですこうした集団が周辺的かもしれないとしても、何らかの形で排除される様子を見るのは常に非常に悪い兆候です
基本的なCSSだけで、すべてのユーザーエージェントにおいて「実際のユーザーがこの記事を読んでいる」ことを100%信頼できる形で検出し、HTTPリクエストを送る方法があるのかは分かりませんし、疑わしくもあります。一部はCSSをまったくサポートしていないか、CSSの装飾用画像の読み込みをオフにしている可能性もあります
役立つ可能性のある現代的なセレクターとしては
:root:focus-withinがありますが、ユーザーが実際にインタラクティブ要素にフォーカスする必要があり、すべてのユーザーエージェントがそれを保証するわけでもありません。最先端のスクロール連動アニメーションである@scroll-timelineも可能でしょうが、点字リーダーは依然として漏れる可能性があります:hoverをサポートしないスマートフォンやタブレット、つまりユーザーエージェントの50%以上に影響するのでは? もちろんマウスを接続していない場合ですこの領域はコンテンツカラムと左右20pxのパディングを含む幅です。そのため、一部のキーボードユーザーは捕捉され、一部のマウスユーザー、特に大きなビューポートを使うユーザーは捕捉されないことになります
「Google Analyticsのような悪いものだけでなく、FathomやPlausibleも広告ブロックブラウザではアクティビティの記録に苦労する」というのは、彼らが事実上有毒な荒野で生きようとしているからだと思う
私たちのようなユーザーはその考え方全体にうんざりしていて、だからCSS分析が人気になれば、それも回避しようとする試みが出てくると思う
私はuBlockでPiwik/Matomo、Plausible、Fathomを手動でブロック解除している。これらが何をどう追跡するのかに害があるとは思わない。そしてサイト運営者に「サービス改善」のための有用な情報を与える
たとえばPlausibleは、一般的なnginxやApacheのログよりも私について収集する情報が少ない。ブロガーにとっては、記事がHNに載ったのか、どこかからリンクされたのか、どんなコンテンツが価値あるものとして受け止められ、どんなものが無視されているのかを見ることが重要だ。そうしてこそ、実際に読みたいと思われる内容を書き、実際に届くチャネルで広められる
access.logを分析サービスに入れても、何も防げないむしろユーザーエージェントだけを見てすべてのボットトラフィックを除外するのは事実上不可能なので、数字が膨らむ可能性がある
実際にどれほど多くの人がこれを恐れているのかは分からない。「そうだ、気味が悪い」から「あり得ない、ただのSFだ」まで反応は分かれそうだ
これは何十年も前からピクセルトラッカーとして知られている
:hoverを使うと、完全なWebDriverを使っていないボット、つまり大半のボットを除外できるある意味では、空に近い
.cssファイルをsupportsとともに@importで読み込む方式のほうがよいかもしれない。広告ブロッカーは1pxの透明トラッキングピクセルは非常によく見つけるが、レイアウトを壊さないように.cssファイルはあまりブロックしない可能性がある。ただしこの場合、:hoverの賢い利点は失われる純粋に気になる質問なのだが、突き放した意見のように読まれないか心配している。Bearblogのように見える非商用の個人ブログで分析データを収集する目的は何なのか?
分析に関心を持つ主な理由は、文章が読まれているかを見るためだ。表面的には、またある面では虚栄心のためだが、実際には書き手と読者のつながりに関わることだ。読者が何に反応するのかを本気で知りたいし、そういうものをもっと提供したい。その「そういうもの」はテーマかもしれないし、口調かもしれないし、長さかもしれない。読者に合わせて題材を磨く助けになる。結局、私は12のテーマを24通りの方法で書ける。もちろん自分の好きなものを書くが、読者により響くように磨く
この意味で、分析は読者を知る方法でもある。エンゲージメントの高かったブログでは、分析によって読者についてのぼんやりした人物像のようなものが得られた。何を好むかだけでなく、いつ好むかも見られた。朝一番に読むのか、昼休みに読むのか、深夜に読むのかが分かり、特定の時間に投稿するかを決めたり、その判断に自信を持ったりする助けになった。もちろんすべて曖昧な情報だったが、読者とより積極的につながるのに本当に役立った
もちろん広告に使われることもあり、悪用されることもあるが、自分のやっていることへのフィードバックが欲しいなら不可欠だ
サイトを12人が読もうが12,000人が読もうが、金銭的価値はないかもしれない。しかし個人的な観点では、人々が自分から何を読みたいと思っているのかを知るのはよいことだし、執筆に費やした時間が有意義だったと感じられる。望むなら、より人気のある方向に調整することもできる
個人ブロガーでも、読者に合わせてコンテンツを調整したいことはある。あるテーマの記事は500人が読み、別のテーマの記事は3人しか読まなかったと知るのはよいことだ
今年の初めにこういうものを作ろうとしたが、Web UIを作っている途中でやる気を失った。私の方式はCSSではなく、単純にタグで偽の画像を読み込むものだ
https://github.com/nolytics
なぜ単にHTTPサーバーからこの情報を得ないのか?
「サーバーログをパースする選択肢は常にあり、サーバーにアクセスするトラフィックの種類を大まかに把握できる。しかし一般的に、すべてのサーバートラフィックは同じように見える。技術的にはボットは自分をボットとして識別するユーザーエージェントを持つべきだが、ブラウザを使う『人間』のように情報をかき集めようとするため、そう識別するケースはほとんどない。本質的にサーバーログだけを分析に使うと、検索エンジンのクローラー、スクレイパー、今ではGPTベースのパーサーまで多く、トラフィックを歪めて見ることになる」
分析データはどのように保存するのか?
ECサイトがあり、販売したい商品があるとしよう。分析以外にも、ログイン状態で商品詳細ページを訪問したといった一部の行動を直接ログとして残すことにした。そのため、ユーザーID、商品ID、タイムスタンプのようなものを保存したい
実際にはどのように保存すべきなのか? 素朴にはテーブルに入れればよいと思っていた。DBAにデータがどれくらい長く必要かと聞かれ、最低1か月と答えた。すると了解と言われ、おそらくより古いデータを別のテーブルへ移すジョブを設定したようだった
実際にはこのようなログをどう保存し、どれくらい長く保管するのか?
以前この方法を試したことがあり、10億行程度になるまではパーティショニングを考える必要すらなかった。それでも、それより早めにパーティショニングするほうがよい。その経験は楽しいものではなかった
集計をはるかに高速に行え、まばらに存在する多数のカラムもうまく扱える。たとえば、
paidイベントにはamount属性があり、page_viewイベントにはurl属性がある、という具合だTimescaleDBのよい点は、1時間あたりの商品閲覧数のような関心のある集計について、マテリアライズドビューの作成を自動で処理してくれることだ。イベントが多く、データベースが大きくなりすぎるのを避けたい場合は、イベント自体は「捨てて」集計だけを残すように選ぶこともできる