Google Chrome プライバシーサンドボックス Topics APIの概要と試し方

[Supership](https://supership.jp/)でVPoEをしている名畑です。[銀河鉄道999](https://www.toei-anim.co.jp/movie/999/)はまったくもって明るい内容ではないのですが、幼少期にたくさん夢を見せてもらった作品でした。

:::note warn
こちらの記事の内容は古くなっておりますので「[ユーザーの興味関心を取得するGoogle Chrome Topics APIの概要](https://qiita.com/nabata/items/42610d5e795287b184c0)」をご覧ください。
:::

## はじめに

[プライバシーサンドボックス(The Privacy Sandbox)](https://privacysandbox.com/intl/ja_jp/)という取り組みがあります。

> ウェブ向けのプライバシーサンドボックスは、サードパーティ Cookie を段階的に廃止し、隠されたトラッキングも制限します。新しいウェブ基準（英語）を策定することで、データのプライバシー保護を強化しながらデジタル事業の構築を可能にします。 
 
 > Android向けのプライバシーサンドボックスは、ユーザーのプライバシー保護を優先しながら、アプリの開発者が事業のサポートや成長に必要なツールを提供します。広告IDなどの複数アプリ共通の識別子を使用しないことでサードパーティとのデータ共有を制限します。

プライバシーサンドボックスでは様々な開発が並行して進んでいますが、その中にWeb向けの一つとして[Topics API](https://japan.googleblog.com/2022/01/topics-api.html)というものがあります。Topics APIの詳細は[Githubのpatcg-individual-draftsのtopicsリポジトリ](https://github.com/patcg-individual-drafts/topics)にまとめられています。

今回の記事ではTopicsについてデモを動かしてみたり、JavaScript向けAPI経由で実際に値を取得してみたりした結果を記しています。

## Topics APIとは？

[Google Japan Blog: プライバシー サンドボックスの新しい Topics API について ](https://japan.googleblog.com/2022/01/topics-api.html)には下記のように書かれています。

> Topics は、ブラウザが、閲覧履歴に基づいて、たとえば「フィットネス」や「旅行」などといったその週に関心の高い項目（トピック）を選びます。

 [Topics API: 開発者ガイドの「この API が必要な理由」](https://developer.chrome.com/ja/docs/privacy-sandbox/topics/#%E6%A9%9F%E8%83%BD%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%A4%9C%E5%87%BA)には下記のように書かれています。

> Topics APIは、ユーザーがアクセスしたサイトを追跡せずに、インタレストベース広告を可能にするメカニズムに関するプライバシーサンドボックス提案です。

> インタレストベース広告（IBA） は、ユーザーが最近アクセスしたサイトから推測された、ユーザーの興味に基づいて広告が選択されるパーソナライズされた広告の形態です。これは、ユーザーがアクセスしているページのコンテンツと照合することを目的としたコンテンツターゲット広告とは異なります。
> IBA は、広告主が潜在的な顧客にリーチするのを支援し、コンテンツターゲット広告だけでサイトへのアクセスを簡単に収益化できない Web サイトに資金を提供するのに役立ちます。 IBA は、現在のページのコンテキスト情報を補足して、ユーザーにとって適切な広告を表示できるようにします。

つまりは今ページを閲覧しているユーザーの関心が高い項目を**トピック**としてブラウザから取得できるというものですが、トピックが実際にどのような値をとるか、どのようなカテゴライズがされるかは[こちらのmardownファイル](https://github.com/patcg-individual-drafts/topics/blob/main/taxonomy_v1.md)で確認することができます。
ただ、2023年2月18日時点でのトピック数は349ですが「**現在の提案は約 350 ですが、最終的なトピックの数は数百から数千になると想定されています**」と[Topics API: 開発者ガイドの「トピックの分類と選択の方法」](https://developer.chrome.com/ja/docs/privacy-sandbox/topics/#%E3%83%88%E3%83%94%E3%83%83%E3%82%AF%E3%81%AE%E5%88%86%E9%A1%9E%E3%81%A8%E9%81%B8%E6%8A%9E%E3%81%AE%E6%96%B9%E6%B3%95)に書かれています。

トピックがいかにして求められるかについては[Topics API: 開発者ガイドの「Topics API でどの呼び出し元がどのトピックを表示できるのかを決定する方法」](https://developer.chrome.com/ja/docs/privacy-sandbox/topics/#topics-api-%E3%81%A7%E3%81%A9%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E5%85%83%E3%81%8C%E3%81%A9%E3%81%AE%E3%83%88%E3%83%94%E3%83%83%E3%82%AF%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%AE%E3%81%8B%E3%82%92%E6%B1%BA%E5%AE%9A%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)」あたりをご覧ください。一定期間毎の上位のトピックを返すだけではなく「**5% の確率で分類上のすべてのトピックからランダムに選択されます**」とあるのでノイズも差し込まれるようです。

どのようなトピックが自分に紐づけられているかを確認する方法は後述いたします。

## 公式デモの実行

前提として私の環境は**Mac版Chrome バージョン110**です。

### Chromeの起動

まず、Chromeで**Privacy SandBox Ads APIs**を有効にします。

アドレスバーに**chrome://flags/#privacy-sandbox-ads-apis**と入力します。

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/1f598906-e292-5320-890e-d8bb2b1a597c.png)

このDisabledをEnabledに切り替えてChromeを再起動します。

Chromeの画面上ではなくコマンドラインからオプション指定して起動することも可能です。下記はコマンド例です。

```console
$ open "/Applications/Google Chrome.app" --args --enable-features=BrowsingTopics:time_period_per_epoch/15s,PrivacySandboxAdsAPIsOverride,PrivacySandboxSettings3,OverridePrivacySandboxSettingsLocalTesting --flag-switches-begin --flag-switches-end
```

ちなみに私はいつもコマンドラインからの起動をしています。値を細かく指定できるためです。たとえば**time_period_per_epoch**はデフォルトだと1週間のため、このコマンドラインでは15sにしています。**time_period_per_epoch**は**エポック**と呼ばれる期間を決めるためのものです。この期間の行動履歴を元にトピックが生成されます。今回の記事で用いる**document.browsingTopics**というメソッドは直近3つのエポックを参照します。
オプションについて詳しくは[Topics API: 開発者ガイドの「Topics API の表示を表示する」](https://developer.chrome.com/ja/docs/privacy-sandbox/topics/#view-api-information)をご参照ください。

Chromeのアドレスバーに**chrome://version**と入力すれば**コマンドライン**で指定されたオプションが確認できます。

![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/6403fafc-9207-786d-f7ad-17854ce77799.png)

### 公式デモページを開く

[Topics API demo](https://topics-demo.glitch.me/)をChromeで開いてください。

読み進めながら下の方にスクロールしていくと薄黄色い長方形の領域があります。

![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/93121d13-4c2c-5847-5d5f-8007dfc7a709.png)

> No topics observed yet: try visiting one of the sites above.
 
と書かれています。topicsがないので上記のサイトの一つを訪問しましょうとあります。

スクロールして上に戻ると下記4つのリンクが用意されています。

- [pets-animals-pets-cats.glitch.me](https://pets-animals-pets-cats.glitch.me/)
- [cats-cats-cats-cats.glitch.me](https://cats-cats-cats-cats.glitch.me/)
- [cats-pets-animals-pets.glitch.me](https://cats-pets-animals-pets.glitch.me/)
- [cats-feline-meow-purr-whiskers-pet.glitch.me](https://cats-feline-meow-purr-whiskers-pet.glitch.me/)

これらのうちの一つ[pets-animals-pets-cats.glitch.me](https://pets-animals-pets-cats.glitch.me/)を別タブで開いてみます。

![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/8cca90a3-5d14-9e5f-51a3-b9fc8349ddea.png)

元のタブに戻って少し待ってみると先ほどの薄黄色の領域に取得できたトピックが表示されます(setIntervalされているのでリロードは不要です)。

![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/6d37ab7a-bcf0-d8fa-244a-83d7063cde08.png)

**Pets & Animals/Pets**と書かれた箇所です（必ずしもこれが表示されるとは限りません）。

しばらく待っても表示されない時もあるかと思います。そのときは4つのリンクの他のページも開いてみましょう。

> The epoch length is set to 15 seconds using the flags above (the default length is one week).

と書かれていますし、**time_period_per_epoch**を短くセットしておいた方がトピックス生成の更新頻度が上がるので、今回のような**値が取得できるか**というテストはやりやすそうですかね。

## トピックを取得するコードを書いてみる

トピックを取得するコードを実際に書いてみました。

```html
<!doctype html>
<html lang="ja">
<head>
</head>
<body>
<script>
async function getTopics() {
  if('browsingTopics' in document && document.featurePolicy.allowsFeature('browsing-topics')) {
    try {
      const topics = await document.browsingTopics();
      if (topics.length > 0) {
        for (const topic of topics) {
          console.log(topic.topic)
        }
      } else {
        console.log('there are no topics')
      }
    } catch (error) {
      console.log('exception with browsing topics')
    }
  } else {
    console.log('document.browsingTopics() is not supported on this page')
  }
}

const intervalId = setInterval(getTopics, 1000);
</script>
</body>
</html>
```

htmlでかつログを差し込んでいるので長く見えますが、要点はかなり少ないです。

以降、要点です。

### 環境判定

```JavaScript
'browsingTopics' in document && document.featurePolicy.allowsFeature('browsing-topics')
```

まず、この条件式でトピックの取得が可能かをチェックしています。ただ、ユーザー側がオプトアウトをしていてもTrueとなるのでそこはご注意下さい。
[Topics API: 開発者ガイドの「機能サポートの検出」](https://developer.chrome.com/ja/docs/privacy-sandbox/topics/#%E6%A9%9F%E8%83%BD%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%A4%9C%E5%87%BA)には下記の通り書かれています。

> 現在のページで機能がサポートされていても、API が使用可能であることは保証されません。ユーザーがブラウザの設定で API を無効にしたり、API の使用を妨げる他の設定が使用されていたりする可能性があるためです。ユーザーのプライバシーを保護する理由から、このことをプログラムでチェックする方法は存在しません。

### トピックの取得

```JavaScript
const topics = await document.browsingTopics();
```

この1行です。

ちなみに

```JavaScript
document.browsingTopics({observe: false})
```

というように **{observe: false}** を引数として渡すと、状態を変更せずに(つまりは現在のページ閲覧がエポック計算に用いられずに)トピックの取得を行えます。

これをfor文で回しています。

```JavaScript
if (topics.length > 0) {
  for (const topic of topics) {
    console.log(topic.topic)
  }
}
```

topic.topicにIDが入ります。

![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/a414e384-8309-cbdf-4968-73c5ce5f7ea8.png)

setIntervalで1秒毎に呼び出しを行なって、トピックの取得ができなかったときは**there are no topics**と、取得できたときはその**ID**をログに表示しています。

先述した公式デモでは[こちらのjs](https://topics-demo.glitch.me/js/taxonomy.js)のマッピングに従ってIDを文字列に変換しています。
1ならArts & Entertainment、2ならArts & Entertainment/Acting & Theater……といった感じです。

## 自身に紐づいたトピックの確認

アドレスバーに**chrome://topics-internals**と入力すると自身に紐づいたトピックの確認ができます。

![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/600379b1-09f1-6fb4-5495-4220fbb0a955.png)

データ量が5個のトピック生成に満たないためランダムな値が入っています。
分析に用いられたサイト、Chromeに指定されているオプション(time_period_per_epochなど)もここで確認できます。


## オリジントライアルへの参加

> トライアルに正しく登録できたら、有効なトライアルトークンを提供するページで Topics JavaScript API を試すことができます。
 
[Topics API: 開発者ガイドの「Topics オリジントライアルに参加する」](https://developer.chrome.com/ja/docs/privacy-sandbox/topics/#origin-trial)にはこう書かれているのですが、トークンを取得しなくてもTopics APIの呼び出し自体は可能なようです。 

参考までにトークンの取得方法を記載しておきます。

### Origin Trialsを開く

> オリジントライアルは、ウェブプラットフォームの新しい機能や実験的な機能が一般公開されるより先に、使いやすさ、実用性、そして有効性についてテストし、ウェブ標準コミュニティにフィードバックできる方法です。

**オリジン**という単語が初耳という方は[「same-site」と「same-origin」を理解する](https://web.dev/same-site-same-origin/#%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%B3)をご参照ください

[Chrome Origin Trials](https://developer.chrome.com/origintrials/#/trials/active)を開くと、登録可能なオリジントライアルの一覧が確認できます(完了したトライアルと自分が登録済みのトライアルも確認できます)。

![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/d96eabea-9956-3721-c9eb-c9fc6d172e4d.png)

### Privacy Sandbox Relevance and Measurementへの登録

**Privacy Sandbox Relevance and Measurement**という項目があるので、REGISTERをクリックします。

![009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/72045207-1f71-ba47-8c85-b3e05e87fec0.png)

遷移先のページでサイトのURLの入力等をしてREGISTERを押します。

![010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/71792a0d-aa75-babc-4972-edda630ee2f3.png)

するとトークンが発行されます。

発行したトークンの設定方法は[Topics API: 開発者ガイドの「Topics オリジントライアルに参加する」](https://developer.chrome.com/ja/docs/privacy-sandbox/topics/#origin-trial)をご覧ください。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。