クリックした箇所をヒートマップで表示するサービスの技術的検証

概要

クリックした箇所をヒートマップで表示するにはどういうサービス構成になるのか?という技術的興味から heroku で以下のように表示をするサービスを作成しました。いろんな理由で実際の使用には耐えられないので、技術的検証というタイトルにしています。

f:id:bamch0h:20200407000342p:plain
ヒートマップサイト

経緯

ひょんなことから、こういうサイトがあることを知り、いったいどういう仕組みで動くのだろう? という技術的興味から、こうやったら作れそうだな。という見立てができたので実装してみました。

最終的なサービスの構造

最終的には以下のような構造になりました。

f:id:bamch0h:20200407001525j:plain
最終的なサービスの構造

heroku の dyno を3つ立ち上げて、それぞれに以下のサイトを設定しました。

  • サイト1: クリックポイントを取得する用のサイト (これは別で用意してもらっても構いません)
  • サイト2: クリックポイントを収集し表示する用のサイト
  • サイト3: クリックポイント対象のサイトのスクリーンショットを取得するためのサイト

ユーザーはサイト1にクリックポイントをサイト2に送る用のjavascriptを埋め込みます。これにより、クリックポイントの収集が可能になります。クリックポイントを収集した後、ユーザーがサイト2にアクセスすると、サイト2の内部でサイト1のスクリーンショットを取得します。スクリーションショットの取得はサイト3の puppeteer 経由で chromium を使用して行われます。スクリーンショットを取得すると、その画像の上にクリックポイントを重ねて表示します。クリック回数が多い場所が赤く、少ない場所は青く表示されます。

ソースコード

  • サイト1

GitHub - bamchoh/heatmap-sample-site1 at for_blog

  • サイト2

GitHub - bamchoh/heatmap-sample-site2 at for_blog

  • サイト3

GitHub - bamchoh/heatmap-sample-site3 at for_blog

ハマリポイント

heatmap.js の使い方

heatmap.js : Dynamic Heatmaps for the Web

上記サイトにheatmap.js の使い方は記載されているのですが、はじめ setData を使っていて、なかなかうまくグラデーションが掛かりませんでした。 setData だと指定する Value を固定で設定する必要があるため、自前でドットの強弱を計算しておかなければならないようです。この問題は addData を使うことで解決できましたが、クリックポイントが多いと処理が重くなって表示までの時間にかかってしまうという問題があり、今のところ解決に至っていません。

fetch の クロスオリジン

この問題は Fetch: クロスオリジン(Cross-Origin) リクエスト に記載されている通りなのですが、別ドメインへの fetch リクエストは設定をちゃんとしておかないとリクエスト・レスポンスともに取得できない場合がありました。ブラウザからのリクエストは同じドメインに送信するようにし、サーバーサイドで別ドメインへのリクエストを送信することで、この問題を回避しています。ちゃんと設定すればブラウザレベルでも対応できたのでしょうけど、今回のヒートマップを表示する。という目標から考えると、本質的な問題ではないと考えて、今回はサーバー側での対応を行いました。

サイトのスクリーンショット

ハマリポイントというか、これ以外の方法が今のところ浮かんでないのですが、クリックポイントをサイトに重ねる方法として、Chromeスクリーンショット機能を使用しています。ローカルで行う分には問題なかったのですが、それを heroku で動かそうとすると Chrome をインストールする必要があり、heroku コンテナではそれはできません。 puppeteer という ChromeAPIを使用して色々できるライブラリが node.js にあり、それを使うことで解決しました。

スクリーンショットが文字化けする

heroku で puppeteer を使う記事はいくつかありますが、参考にするサイトによっては puppeteer が日本語に対応していないものをインストールすることになっているものがあります。そういった場合は日本語フォントを対応している puppeteer をインストールしなおすことで治ります。

HerokuにPuppeteerの実行環境を構築する - Qiita

現状の問題点

スクリーンショットが重い

スクリーンショットを撮る時にいちいち Chrome が起動してしまうので処理が重くなっています。キャッシュの検討が必要ですね。

クリックポイントが多くなってきた場合の処理速度の低下

heatmap.js の addData メソッドを使うと、自動でクリックポイントの値の強弱を計算してくれますが、その計算が重くクリックポイントが多くなると描画までの時間が長くなってしまいます。70個の点で数十秒待たないと描画されないので、自前で計算して setData で描画するほうがいいかもしれません。まだ、自前で計算する部分は考えられていません。

クリックポイントAPIの制限

現状、POSTメソッドは誰でもアクセスできるため、第三者がURLを偽ってクリックポイントを送ることができるので正確なクリックポイントが収集できません。トークンのようなものでアクセスを制限する方法を考えてみたのですが、タグを外部サイトに埋め込むという性質上、そのタグさえ知っていればトークンも取得できてしまうのではないか?と思っています。(もしかすると、うまくやる方法があるのかもですが、まだ調査しきれていません) もう一つ考えているのは、リクエスト元のURLとリクエスト内のURLを比較して、一致しているかどうかで判断する。ということもできるかと思っています。こちらは比較的容易に実装ができそうですが、これですべて賄えるかは検証できていません。

まとめ

クリックした箇所をヒートマップで表示するサービスを技術的検証レベルで実装してみました。 様々なハマリどころがあり、様々な問題点があることに気づけました。 今後も改善していけるところは改善していけたらと思っています。