← ブログに戻る

アクセス2.8倍の正体、クローラー3種の見分け方

この記事を含む総合ガイド LLMO 総合ガイド

日ごとのセッションを100%として、実トラフィックと非人間の内訳を積み上げた棒グラフ。8/29から8/31は非人間が27%から43%で推移しているが、9/1だけ69%に膨らんでいる

9月1日、kenimoto.dev の GA4 のセッションが前日の 2.8 倍になりました。

実質は 1.2 倍でした。残りはマジックです。タネは自分のアクセスログの中にありました。

その日のセッションの7割は、人間ではないものが踏んだ跡でした。しかもそれは一種類ではなく、目的の違う3つが同じ日に重なっていました。ひとつは歓迎したい相手で、ひとつは名前を隠していて、もうひとつはこちらのサイトがWordPressだと思い込んでいます。

見分けるのに GA4 は使えません。理由と、代わりに何を見るかを書きます。

増分はほぼ全部シンガポールから来ていた

まず日次を並べます。GA4 の生の数字と、シンガポールと中国を除いた数字です。

日付生の数字に占める非人間の割合
8/2943%
8/3041%
8/3127%
9/169%

除外後で見ると前日から2割弱の増加で、この21日間ずっと同じレンジの中にいます。跳ねたのは片側だけでした。

9月1日のシンガポールは前日の9倍に跳ねていました。中身はこうでした。

  • エンゲージメント率 0.0%
  • 平均滞在 0.5秒
  • 1セッションあたり 1ページビューちょうど
  • 99% が Chrome / Windows / desktop
  • 画面解像度も 98%1280x1200 で同一

滞在0.5秒でページを1枚だけ見て帰る訪問者が、同じ解像度の同じOSでこれだけ並びます。人間の集団はこうなりません。

時刻を分単位まで割ると、もっとはっきりします。

時刻その日のシンガポール分に占める割合
17:114%
17:1212%
17:1323%
17:1421%
17:1515%
17:166%

この6分間だけで、その日のシンガポール分の8割。この日はEN版の記事をまとめて公開した直後で、サイトマップが更新されたタイミングとほぼ一致しています。

GA4 は User-Agent を持っていない

ここから先、GA4 では進めません。GA4 が持っているのは国・ブラウザ名・OS・解像度までで、User-Agent の文字列そのものがありません。「Chrome」としか分からないので、名乗っているクローラーなのか、Chromeを騙っている何かなのかが区別できません。

もう一つ大きい制約があります。GA4 に載るのは JavaScript を実行したアクセスだけです。gtag が動かないと計測されません。つまり、

  • JSを実行しないクローラー(大半の検索エンジンボット)は GA4 に一切出てこない
  • 逆に、GA4 に出ている非人間アクセスは JSを実行できるヘッドレスブラウザ

この2点だけで、見えている世界が半分に切れているのが分かります。

正体を見るには配信側のログが要ります。kenimoto.dev は Cloudflare Workers で配信しているので、Cloudflare の GraphQL Analytics API を叩きました。

TOKEN=<Cloudflare API token>
ZONE=<zone id>

read -r -d '' Q <<'EOF'
query($zone:String!,$start:Time!,$end:Time!){
  viewer{ zones(filter:{zoneTag:$zone}){
    httpRequestsAdaptiveGroups(
      limit:20,
      filter:{datetime_geq:$start, datetime_lt:$end},
      orderBy:[count_DESC]
    ){ count dimensions{ userAgent clientCountryName } }
  }}}
EOF

curl -s -X POST https://api.cloudflare.com/client/v4/graphql \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg q "$Q" --arg z "$ZONE" \
        '{query:$q, variables:{zone:$z,
          start:"2026-09-01T08:10:00Z", end:"2026-09-01T08:20:00Z"}}')"

時刻は UTC で渡します。日本時間の 17:11 は 08:11Z です。

Free プランで使えるフィールドには制限があります。試した結果です。

フィールドFree用途
userAgent正体判定の主軸
clientCountryName発信国
clientRequestPath何を取ったか
edgeResponseStatus404の多さ=探索型か
clientRequestHTTPProtocolHTTP/1.1 か HTTP/2 か
clientASNDescription権限エラー
clientRefererHost権限エラー

ASN が取れないのでホスティング事業者までは特定できませんが、判定に必要なものは Free で全部揃います

来ていたのは3種類だった

引いた結果を分類すると、性質の違う3つに分かれました。

同じ日に来ていた3種類の比較カード。Aの名乗るクローラーはrobots.txtを読みGA4に載らず引用リンクが返る、Bの名乗らないヘッドレスはrobots.txtを読まずGA4に載り引用リンクが返らない、Cの脆弱性スキャンは全部404で終わる

正体robots.txtGA4に載るか
A名乗るクローラー(検索・AI)読む載らない
B名乗らないヘッドレス読まない載る
C脆弱性スキャン読まない載らない

GA4 を汚していたのは B だけです。A と C は JS を実行しないので、GA4 の数字には最初から入っていません。

A: AI検索のクローラーは全部名前を書いている

9月1日の1日分を、名乗っているものだけ抜き出しました。

クローラーリクエスト何者か
Bytespider140ByteDance
bingbot140Microsoft
Googlebot133Google
PetalBot110Huawei(Petal Search)
Semrush102SEOツール
Applebot64Apple
ChatGPT-User57ChatGPTがユーザー指示で読みに来た分
ClaudeBot52Anthropic
Ahrefs48SEOツール
Amazonbot37Amazon
Claude-User15
GPTBot14OpenAI(学習用)
PerplexityBot9Perplexity
OAI-SearchBot7ChatGPT検索のインデックス用
DuckAssistBot2DuckDuckGo

AI系はUAに自分の名前とURLを書きます。Bytespider なら spider-feedback@bytedance.com まで入っています。

Mozilla/5.0 (Linux; Android 5.0) AppleWebKit/537.36 (KHTML, like Gecko)
Mobile Safari/537.36 (compatible; Bytespider; spider-feedback@bytedance.com)

名乗る理由は単純で、robots.txt で指定してもらう必要があるからです。名前がなければ許可も拒否もされません。「うちのAIに学習させないでほしい」と言われたときに応じる先として名前を出しています。

裏を返すと、名乗らない相手には robots.txt が効きません。こちらのサイトは今こうなっています。

User-agent: *
Allow: /

User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /
...

全部 Allow です。歓迎の意思表示はできています。

それでも、名乗らずに来るものがあります。

B: 名乗らないものには指紋が出る

問題の 9月1日 17時台です。UA を全部並べたところ、こうなっていました。

リクエスト名乗っているバージョン
47Chrome/131
45Chrome/109
40Chrome/110
36Chrome/111
28Chrome/107
24Chrome/133
23Chrome/116

以下、103・104・105・106・108・112・117・120・124 と続きます。16種類以上をローテーションしています

Chrome 103 から 111 は2022年から2023年のバージョンです。2026年にこの分布で実ユーザーが来ることはありません。バージョンを散らしているのは、同一のUAで連続アクセスすると弾かれるからです。

決め手はもう一つあります。プロトコルでした。

リクエスト
HTTP/1.1389
HTTP/25

Chrome を名乗りながら 99% が HTTP/1.1 です。実際の Chrome は HTTP/2 で接続します。UAだけ書き換えて、通信の中身が追いついていない状態です。

判定に使える指紋をまとめます。

指標名乗るクローラー名乗らないもの
UAの一貫性固定毎回変わる
プロトコルHTTP/2が主HTTP/1.1に偏る
robots.txt最初に読む読まない
404の割合低い低い
アクセス間隔ならされているバーストする

このうち robots.txt を読んだかどうかが一番はっきりします。Bの394リクエストの中に /robots.txt は1件もありませんでした。

取ったものを見ると目的が分かる

「何のために集めているのか」は、取ったファイルの種類にかなり出ます。Bが6分で取ったものの内訳です。

種別リクエスト
HTML290
.json98
.js3
.png3

画像をほぼ取っていません。転載サイトを作るなら画像も取ります。見た目を再現しないと成立しないからです。テキストだけを抜いて画像を捨てているなら、用途は本文そのものにあります。

他に分かったことです。

  • 404がわずか3件。存在するURLだけを正確に叩いている=サイトマップかURLリストを持っている。手探りの探索ではない
  • /ja/ /pt/ /es/ を横断。特定の記事狙いではなくサイト全体
  • 更新した直後に来ている。差分を監視している

ここまで揃うと、用途はテキストの網羅収集に絞られます。学習データか、リライトの元ネタです。

もう一点。このサイトだけが標的でした。同じサービスアカウントで見ている他サイトと、直近14日で比べます。

サイト(直近14日)セッションに占めるシンガポールの比率
kenimoto.dev34.5%
mypcrig.com2.3%
kaoriq.com4.4%
legacydram.com3.5%

無差別に回っているなら全部同じ比率になります。選ばれています。

転載されているかを調べる4手

テキストを持っていかれているなら、次に確認するのは転載です。4つやりました。

1. 完全一致でフレーズ検索する

記事から「自分しか書かない一文」を抜いて、引用符で囲んで検索エンジンに投げます。引用符付きは完全一致検索で、その並びの文字列を含むページだけが返ります。

"毎日ビュー数といいね数をスナップショット"

コピペで転載されていれば、自分のドメインと並んで見知らぬドメインが出ます。日本語・英語・ポルトガル語で4本試して、出てきたのは自サイトだけでした。

ここで一つ注意があります。検索エンジンは複数当ててください。 同じフレーズを Bing と DuckDuckGo の両方に投げたところ、DuckDuckGo は日本語の完全一致で0件を返しました。転載どころか、元記事である自分のサイトすらヒットしません。日本語のインデックスが薄いためで、この0件は「転載されていない」ではなく「測れていない」です。Bing は自サイトを正しく1件返しました。

エンジンを1つしか当てないと、後者を前者と読み違えます。

2. リファラを全部見る

自動転載ツールは出典リンクを残すことがあります。GA4 で sessionMedium = referral の全ドメインを90日分洗って、知らないものを踏みました。

  • sunblog.asia → 踏むと xtraffic.plus に302リダイレクト。リファラースパムです。解析画面に自分のドメインを載せて踏ませる手口で、転載ではありません
  • aleemuh.com → 個人のポートフォリオ。無関係

3. 被リンクを引く

Bing Webmaster Tools の GetLinkCounts で0件。Bingが把握する範囲に転載元からのリンクはありません。

4. 画像を取っているかを見る(前述)

4つとも空振りで、コピペ転載の形跡は出ませんでした。

ただし、これで「されていない」とは言えません。この4手で見えるのは検索エンジンにインデックスされたコピペ転載だけです。見えないものが3つ残ります。

  • リライト・翻訳された転載(完全一致では原理的に引っかからない)
  • インデックスされていない転載
  • LLMの学習データとして取り込まれた場合(外から観測する手段がない)

証拠が出ないことを安全の証明として扱わないほうがいいと思っています。今回の相手の挙動を見るかぎり、本命は3番目です。

確定させたいなら、記事ごとに固有の文字列を人間に見えない形で埋めておいて、定期的に完全一致検索する方法があります。転載されれば一意に引っかかります。

C: 攻撃スキャンは毎日来ていて、全部空振りしている

3つ目は毛色が違います。同じ日の別の国からです。

発信国叩いたパス回数
オランダ/wp-json/batch/v1 を20階層以上で総当たり約175
ロシア/wp-admin/install.php?step=117
ドイツ/admin1 /ur-admin /backup /fileadmin(ffuf)各4-5
ドイツ/.env /.git/HEAD各4

/wp-json/batch/v1 は WordPress の REST API バッチ機能で、認証まわりの既知の穴を探しています。/wp/ /blog/ /wordpress/ と階層を変えながら20通り以上試しているのは、どこにWordPressが置かれていても当てるためです。

/.git/HEAD はリポジトリの露出確認、/.env は環境変数ファイルの直読み、ffuf はディレクトリ総当たりのツールです。

このサイトは Astro の静的サイトなので、PHP も WordPress も .env もありません。全部404で終わります。この手のスキャンはインターネットの常時背景ノイズで、ドメインを公開している以上どこにでも来ます。対応は不要です。

404の割合を見ると分かれます。Bは404が3件でしたが、Cは叩いたパスのほぼ全部が404です。存在するURLを正確に取りに来ているのか、当てずっぽうで撃っているのかが、404率に出ます。

困るのは帯域ではなく計測

実害がどこに出るかという話です。

帯域は問題になりません。数百リクエストで落ちるサイトではないです。

効くのは計測の側でした。8月に Dev.to の canonical A/B をやっていたのですが、水増しが片側だけに乗っていました。A/Bは差で判断するので、片方だけ太ると結論が反転します。

9月1日の数字も、生のまま読めば「前日比2.8倍」です。この数字から施策を評価したら、効いていないものを効いたと判定します。実質は1.2倍です。

もう一つは、AI検索との関係です。名乗るクローラーが引用してくれるとき、出典としてリンクが返ってきます。名乗らない収集はリンクを返しません。取られるだけで、戻りがない。

対処は「シンガポールを除外する」で暫定的に足りますが、除外量を見ずに減った数字を実力低下と読まないことのほうが大事です。除外前後を必ず並べて出すようにしています。

手順のまとめ

同じことを調べるときの順番です。

  1. GA4 の日次を見て、跳ねた日を特定する
  2. その日を国別 × エンゲージメント率 × 平均滞在で割る。0.0% / 1秒未満 / 1セッション1PVが揃ったら非人間を疑う
  3. 分単位まで割る。バーストしていれば自動化
  4. Cloudflare(または配信側のログ)で User-Agent を引く。GA4 では取れない
  5. UAが名乗っているかを見る。名乗るものは robots.txt で制御できる
  6. 名乗らないものは HTTP/1.1への偏りrobots.txtを読んだかで確認する
  7. 取ったファイルの種別を見る。画像を取らずHTMLだけなら、用途はテキスト
  8. 404の割合で収集探索かを分ける

GA4 だけを見ていたら、この日は「アクセスが2.8倍になった日」として記録されていました。

なぜあなたのサイトはChatGPTに無視されるのか 関連書籍 なぜあなたのサイトはChatGPTに無視されるのか LLMO 実践ガイド | AI検索最適化・llms.txt・JSON-LD・引用率改善の体系書 書籍ページを見る →