自分のサイト群の検索流入を、直近30日で数えた。
- Bing: 表示 107,652 回、クリック 3,579
- Google: 表示 78 回、クリック 2
Googleがドメインごと評価を落としている事情は別の記事に書いた。理由はどうあれ、いま検索から来ている人は事実上すべてBing経由だ。であれば順位や表示回数を見る場所もSearch ConsoleではなくBing Webmaster Toolsになる。
毎週「先週の改修で順位は動いたか」を確かめたい。画面で追うより、APIで取って手元に置くほうが早い。
叩いてみたら、いくつか癖があった。
最小の呼び方
キーはBing Webmaster Toolsの「設定 → APIアクセス → APIキー」で発行する。アカウント単位で、登録している全サイトに使える。
エンドポイントはこの形だ。
https://ssl.bing.com/webmaster/api.svc/json/<Method>?siteUrl=<サイトURL>&apikey=<キー>
GETで叩くとJSONが返る。認証ヘッダもOAuthも無い。キーをクエリに付けるだけなので、シェルの履歴やプロキシのログに残ることは意識しておく。
主に使うメソッドは4つ。
| メソッド | 返るもの |
|---|---|
GetUserSites | 登録サイトの一覧 |
GetRankAndTrafficStats | サイト全体の日別の表示・クリック |
GetPageStats | ページ別の週別の表示・クリック・平均順位 |
GetQueryStats | 検索語別の週別の同上 |
GetPageQueryStats | 特定ページに対する検索語別(page= を付ける) |
スクリプトはこれだけで済んだ。
#!/usr/bin/env bash
set -euo pipefail
out=${1:?出力ディレクトリ}
site=${2:-https://example.com/}
key=$(<~/.config/bing-wmt/api-key)
base=https://ssl.bing.com/webmaster/api.svc/json
enc() { python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "$1"; }
s=$(enc "$site")
mkdir -p "$out"
get() { curl -fsS -m 120 "$base/$1&apikey=$key" -o "$out/$2"; }
get "GetUserSites?" sites.json
get "GetRankAndTrafficStats?siteUrl=$s" site.json
get "GetPageStats?siteUrl=$s" page.json
get "GetQueryStats?siteUrl=$s" query.json
キーはファイルから読む。chmod 600 にして、リポジトリには入れない。
癖1: 期間を指定できない
一番の癖はここだ。日付範囲のパラメータが無い。
GetPageStats と GetQueryStats は、呼ぶと約32週分を週単位で全部返してくる。「8月18日から24日だけ」のような窓は切れない。欲しければ全部受け取って自分で絞る。
Date は週の区切り日で、金曜付けになっている。形式は /Date(1757030400000)/ という、.NETの古いJSONシリアライザの書式だ。ミリ秒のエポックが括弧の中に入っているので、正規表現で数字を抜く。
import re
def dt(r):
return int(re.search(r'(-?\d+)', r['Date']).group(1)) / 1000
もう一つ。GetPageStats の結果で、ページのURLは Query という列に入っている。検索語のAPIと同じ型を使い回しているらしく、読むときに戸惑う。
癖2: 順位は整数に丸められている
AvgImpressionPosition が平均順位だが、整数で返る。8.4位と8.9位の区別は付かない。
週次で「title を変えて順位が動いたか」を見るには粗い。動いたと言えるのは1位以上変わったときだけで、それ未満の変化は見えない。逆に言えば、1位単位の変化が見えれば十分な用途にしか使えない、と割り切る必要がある。
癖3: 6か月しか遡れない
APIが返すのは直近およそ6か月分だけだ。それより前のデータは、取っていなければ消える。
「去年の同じ週と比べる」がやりたければ、自分で貯めるしかない。毎朝取ってPostgreSQLに写すことにした。
CREATE TABLE bing_page_weekly (
site text NOT NULL,
url text NOT NULL,
week date NOT NULL,
impressions integer NOT NULL,
clicks integer NOT NULL,
avg_impression_position integer,
PRIMARY KEY (site, url, week)
);
取り込みは COPY で流し込んでから INSERT ... ON CONFLICT DO UPDATE で本テーブルに載せる。冪等にしてある。APIは毎回6か月分をまるごと返し、しかも過去の値が後から直ることがあるので、上書きのほうが都合がいい。
systemdのタイマーで毎朝1回。失敗したらSlackに⚠️を投げる。最後に成功した時刻をメトリクスに出して、それが古くなったら鳴らすアラートも付けた。取り込みが黙って止まっていたら、半年後に「貯めたつもりが穴だらけ」になるからだ。
癖4: 末尾の行が呼ぶたびに入れ替わる
貯め始めてから気づいた。同じ日に2回呼ぶと、行数が少し違う。
ページ別・検索語別は上位N件に切られていて、その末尾、表示1回しかないような行が、呼ぶたびに入れ替わる。実測では、連続2回呼んでこうだった。
- ページ別: 1,328行のうち 12行が片方にしか無い
- 検索語別: 1,783行のうち 84行が片方にしか無い
- 両方にある行で、値が違うもの: 0
つまり本体は安定していて、裾だけが揺れる。ON CONFLICT DO UPDATE で上書きしているので既存の値は変わらず、裾の行が和集合として少しずつ貯まるだけだ。壊れてはいない。
ただし合計を出すときは注意が要る。表示1回の行が回によって混ざるので、「サイト全体の表示回数」を週別テーブルの合計から出すと、日別APIの合計と微妙に合わない。順位やCTRの比較には影響しないが、合計を扱うときはこれを踏まえる。サイト全体の数字が欲しいなら GetRankAndTrafficStats の日別のほうを使う。
使い方の例: 改修の前後で順位を見る
これで、週次の定点観測がSQL一本になった。
SELECT week, impressions, clicks, avg_impression_position
FROM bing_page_weekly
WHERE url = 'https://tools.omikuji.dev/my-ip/'
ORDER BY week;
titleを直した週の前後で表示回数と順位を並べれば、効いたかどうかが見える。画面を何クリックもして表を読む作業が消えた。
まとめ
- 検索流入がBingに偏っているなら、順位を見る場所もBing Webmaster Toolsにする。Search Consoleだけ見ていると、流入の実態と違う数字を眺めることになる
- APIはGETにキーを付けるだけ。認証は無いぶん、キーが履歴やログに残らないようファイルから読む
- 期間指定ができない。 週単位で約32週分を全部返す。窓は自分で切る
- ページ別の結果ではURLが
Query列に入っている - 平均順位は整数。1位未満の変化は見えない
- 保持は6か月。 長期で比べたいなら自分で貯める。取り込みは冪等にして、鮮度を監視する
- 末尾の行は呼ぶたびに入れ替わる。 上書きで貯めれば壊れないが、合計を出すときは日別APIを使う
画面の都合で見えなかったものが、SQL一本で見えるようになった。