pixelstomotors.com は人より先に AI に読まれていた ― 次は AI から人を連れてくる

この記事の素材(コード・ログ・データ 4ファイル・まとめてダウンロードあり)

pixelstomotors.com は、人がページを見た回数が5日で130回なのに対して、ChatGPT の利用者が頼んだときに記事を読みに来た回数が48回あり、人より先に AI に読まれるサイトになり始めていました。 ただ、AI が読んだあとに人がサイトへ来ているかは、今の計測では見えていませんでした。AI の回答のリンクから来た人が「直接・不明」に混ざってしまうためです。

pixelstomotors.com は、ロボットとフィジカルAI(現実の物を動かす AI)の研究を、ひとりで記録して公開しているサイトです。記事は、人より AI が読む前提で書いています。人が調べものをするとき、検索より AI に聞くことが増えたからです。

その前提が数字のうえでどうなっているかを、サイトの計測の記録から読みました。人が見た回数・どこから来たか・どのページが読まれたか・AI と検索エンジンの巡回プログラム(クローラー)がどのページを読みに来たか、です。読んだ結果から、すぐできる直しを3つ入れました。

やったことなぜ結果
トップのシリーズのカード3枚を作り直した
線画のイラストが続き、何の研究か伝わりにくかった
研究室は「お手本の動きのデータ」、画像認識は「写真が処理されていく4列」、導入シミュレーションは「Unity の店」にした
サイトの計測の記録を読んだ
AI に読ませる準備をしてきたことが、数字に出ているかを見たかった
人の表示は5日で130回。ChatGPT の利用者の依頼での読み取りは48回
AI の回答のリンクから来た人を数えるようにした
アプリから来ると「どこから来たか」が送られず、「直接・不明」(59回で最多)に混ざる
リンクに付く印(utm_source)を記録し、「chatgpt.com(リンクの印)」と分けて出す
クローラーが開けなかったページを特定した
開けなかった訪問が計62回あった
記事のファイルの枠5つが、人が押しても開けない状態だったのを見つけて直した
古い住所を今のページへ転送した
昔のシリーズのページの住所などに、クローラーが読みに来て 404 になっていた
/prototype1〜6 → /series1〜6 など。外部の AI(codex)のレビューを通してから公開した

計測の数字は、人と機械を見分けられるようになった 10/26 から 10/30 夕方までの、5日分です。数が少ないので、傾向として読んでいます。

作り直したあとのトップのシリーズのカード。左上はハコブくんの写真、右上はお手本の動きの線を上は白・下は黒の面に重ねた図、左下は赤い箱の写真が画素・白黒・しきい値・輪郭と4列で処理されていく図、右下は Unity で作ったファミレスの店を斜め上から見た画面

作業ログ ― トップのシリーズのカードを作り直した

この記事のコード・ログ・データは、1ファイルずつ下に置いています。全文はリンク先にあります。

ファイルの一覧(4本)

01_sec01.py.txt ― カードの絵を作るコード / tools/series_cards/series7.py(画像認識と物体検出のカード)・3,389字

02_sec03.txt ― 印の値だけを記録し、「どこから来たか」の表で分けて出す / worker/index.js・SiteMeasure.astro・privacypolicy.astro の変更(utm_source とクローラーの表)・11,596字

03_codex.txt ― 住所を変える変更なので、codex のレビューを通した / codex のレビュー依頼・3,687字

04_codex.txt ― 住所を変える変更なので、codex のレビューを通した / codex のレビュー結果(Claude Code の報告から、手を加えずに)・3,411字

最初は写真に替えて、「写真を置いただけは NG」になった

フィジカルAI研究室とサービスロボット導入シミュレーションのカードは、どちらも線画のイラストでした。これを画像に替えるよう頼まれ、まず実機の写真と Unity の画面に替えました。

  • フィジカルAI研究室:LOG401 の成功した回を撮った iPhone の4K動画から、アームが赤い箱を白い箱の縁まで運んだ1コマ
  • サービスロボット導入シミュレーション:Unity で作ったファミレスの店を斜め上から見た画面(背景の紺色を、導入前ガイドのカードと同じ薄いグレーに替えた)

導入シミュレーションはこれで決まりました。研究室のほうは、写真をそのまま置いただけで、デザインとして組んでいないと言われました。

なんでもいいわけではなくデザイン性も考慮して作ってね

写真おいただけはNG

フィジカルAI研究室:主役の違う3案から、お手本のデータの案を選んだ

案は、並べ方の違いではなく、何を主役にするかの違いで3つ出しました。色は3案とも白・黒・赤だけにし、赤は「箱」の意味にそろえています。

案主役作り方
A 動きの軌跡アームがどう動いたか
実機の動画を白黒で明るく仕上げ、箱が通った道を点線と赤い枠で描く
B お手本のデータアームが何から動きを覚えたか
手で教えたお手本(デモ)の関節の動きを線で重ねる。赤い線は箱をつかんでいる間
C 形と面ロボットの形
アームの切り抜きを3段階の色で塗り分け、床を黒い面にしたポスター風
フィジカルAI研究室のカードの3案を、トップのカードの並びに入れて比べた画像。A は白黒の実機の写真に箱の通り道、B はお手本の関節の動きの線、C はアームをシルク印刷のように塗り分けた絵

B のデータは、KIOXIA に置いてあるお手本のデータ(datasets/mecharm_redblock_box_teach_v1_trim2)の19回分です。1回ごとに、7つの値(関節6つとグリッパー)が10分の1秒ごとに記録されています。カードには、いちばん大きく動く2つの関節(J0 は台の回転、J1 は肩)を上下2段に描きました。

B を元に、アームの写真を小さく入れる形も試しました。位置を6通りに変えましたが、最後はアームを入れない形になりました。あわせて、見出しの「×19」も外しています。お手本の回数はこれから増えるので、絵の中に数を焼き込まないためです。

お手本の線の図に、アームの切り抜き写真を置く位置を6通りに変えて並べた画像(右上・右下・左上・2段のあいだ・右に大きく・赤い線の上)

アームはなし。x19もなし。これをベースに白黒のツートンにしてみようか

白黒のツートンは、分け方を3通り出しました。左が黒(縦に分ける)、上の段が黒(横に分ける)、右が黒(箱を運ぶあたりから)です。黒い面の上では線を白に、白い面の上では線を黒にし、赤はどちらの面でも同じにしました。

白黒ツートンの3案。左が黒、上の段が黒、右が黒

上の段が黒の案を、上下逆(上が白・下が黒)にした形を足して比べ、下が黒の案に決めました。上が白だと、隣のハコブくんの明るい写真と、カードの上の端の明るさがそろいます。

上が黒の案(左)と、上下を逆にした下が黒の案(右)を、トップのカードの並びに入れて比べた画像

画像認識と物体検出:赤い箱の写真が処理されていく4列にした

画像認識と物体検出のカードも、別の案を頼まれました。このシリーズで実際に撮った写真から、3案を作りました。

案元の写真作り方
A 処理の段階
Log03 の赤い箱の写真(4K)
同じ写真を4列に並べ、画素 → 白黒 → しきい値 → 輪郭 と処理が進む順に見せる
B 機械の目
Log06 の、アームと図形の紙を上から見た写真
黒地に輪郭の線だけを残し、見つけた赤い形を点線の枠で囲む
C 画素Log03 の赤い箱の写真
写真を大きなマス目に分け、赤い箱のマスを点線の枠で囲む
画像認識と物体検出のカードの3案。A は赤い箱が4列で処理されていく図、B は黒地に輪郭の線と赤い形の枠、C は大きなマス目に分けた赤い箱

A に決まりました。ただ、最初の版には、写真の端に写っていた青い箱の角と、紙の継ぎ目が残っていました。

このへんの消せればA案良さそう

A 案の最初の版の一部。白黒の列の右下に、青い箱の角が灰色の三角として写っている

消し方は、赤い箱とその影だけを元の写真のまま残し、まわりを、写真から作ったなめらかな面に置き換えるやり方です。青い箱の部分を周りの色で埋め(OpenCV の inpaint)、全体を強くぼかして面にしました。そのうえで、赤い箱から少し広げた範囲だけを、境目をぼかして元の写真に戻しています。

  • ねらい:シリーズの中身(物を見つける手法を、下の層から1つずつ確かめる)を、カードの絵だけで伝える
  • 変わったこと:線画のイラストから、実際に撮った写真が4段階で処理されていく図にした。研究室のカードも、実際のお手本のデータで描いた
  • 見込める効果(見込み):トップからシリーズのページへ進む人が増える。トップの「押したもの」の表で、/series6・/series7 が押された回数を、来週の数字と比べて確かめる

カードの絵を作るコード

どちらも、元のデータや写真から作り直せるように、サイトの tools/series_cards/ に置きました。フィジカルAI研究室のカードは、お手本のデータを JSON にまとめたものを読み、SVG に描いてから、Playwright(ブラウザを動かす道具)で画像にします。

tools/series_cards/series6.py(フィジカルAI研究室のカード)

"""フィジカルAI研究室(/series6)のトップのカード(3:2・1800×1200)を作る。
お手本19回分の関節の動き(J0・J1)を重ねた線。赤はグリッパーが箱をつかんでいる間。上の段は白、下の段は黒のツートン(2026-10-30)。
元データ: KIOXIA の datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes/*/episode.npz の state を JSON にまとめたもの(DEMOS に置く)。
出力: tools/series_cards/series6.svg → Playwright で PNG にして public/site/series6-physical-ai-lab-demos.webp
回数は増えるので、絵の中に「×19」のような件数は入れない。
"""
import sys
DEMOS = sys.argv[1] if len(sys.argv) > 1 else 'demos_trim2.json'
import json,numpy as np
d=json.load(open(DEMOS))
W,H=1800,1200
L,R=140,1660
lanes=[(300,620),(760,1080)]
BK='#111111'; WH='#f1f0ec'; RED='#e0262c'
def lines(color,opa):
    out=[]
    for (j,(t,b)) in zip([0,1],lanes):
        allv=np.concatenate([np.array(v)[:,j] for v in d.values()]); lo,hi=allv.min(),allv.max()
        out.append(f'<line x1="{L}" y1="{b+14}" x2="{R}" y2="{b+14}" stroke="{color}" stroke-width="1.5"/>')
        for v in d.values():
            a=np.array(v); n=len(a); g=a[:,6]
            pts=[(L+i/(n-1)*(R-L), b-(a[i,j]-lo)/(hi-lo)*(b-t)) for i in range(n)]
            segs=[];seg=[pts[0]];cur=g[0]
            for p,gg in zip(pts[1:],g[1:]):
                seg.append(p)
                if gg!=cur: segs.append((cur,seg)); seg=[p]; cur=gg
            segs.append((cur,seg))
            for gg,sp in segs:
                path=' '.join(f'{x:.1f},{y:.1f}' for x,y in sp)
                col,op=(RED,'.9') if gg>0.5 else (color,opa)
                out.append(f'<polyline points="{path}" fill="none" stroke="{col}" stroke-opacity="{op}" stroke-width="3.2" stroke-linejoin="round"/>')
    return '\n'.join(out)
def text(color):
    return (f'<text x="{L}" y="150" fill="{color}">DEMONSTRATIONS</text>'
            f'<text x="{R}" y="150" text-anchor="end" fill="{color}">PICK → PLACE</text>')
def make(fn,dark_shape):
    s=[f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}">',
       '<style>text{font-family:"JetBrains Mono","SFMono-Regular",Menlo,monospace;font-size:40px;letter-spacing:.08em}</style>',
       f'<defs><clipPath id="dk">{dark_shape}</clipPath><mask id="lt"><rect width="{W}" height="{H}" fill="#fff"/><g fill="#000">{dark_shape}</g></mask></defs>',
       f'<rect width="{W}" height="{H}" fill="{WH}"/>',
       f'<g fill="{BK}">{dark_shape}</g>',
       f'<g mask="url(#lt)">{lines(BK,".4")}{text(BK)}</g>',
       f'<g clip-path="url(#dk)">{lines(WH,".5")}{text(WH)}</g>',
       '</svg>']
    open(fn,'w').write('\n'.join(s))
make('tools/series_cards/series6.svg',f'<rect x="0" y="690" width="{W}" height="{H-690}"/>')  # 下の段が黒

tools/series_cards/series7.py(画像認識と物体検出のカード)

01_sec01.py.txt3,389字・63行・python
"""画像認識と物体検出(/series7)のトップのカード(3:2・1800×1200)を作る(2026-10-30)。
Log03 で撮った赤い箱の写真を4列に並べ、画素 → 白黒 → しきい値 → 輪郭 と処理が進む順に見せる。
背景の青い箱の角と紙の継ぎ目は、赤い箱と影だけを残して背景をなめらかな面に置き換えて消している。
元の写真: KIOXIA の vision/captured/Log03/IMG_7911.JPG(第1引数で渡す)
出力: 第2引数の PNG → public/site/series7-image-recognition-steps.webp
"""
import sys
import cv2, numpy as np
別のタブで開く保存

src/pages/index.astro の変更(カードの絵の差し替え)

前の絵のファイルは、どれも消さずに残しています。どの絵だったかを、コードのコメントに書きました。iCloud の site_archive/top_card_thumbnails_20261030/old/ にもコピーを置いています。

diff --git a/src/pages/index.astro b/src/pages/index.astro
index 1996ff3..0ed2af9 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -18,14 +18,16 @@ import { allManga } from '../lib/records';
 // Studio のトップと同じく、4コマは新しい4話
 const mangaCards = allManga.slice(0, 4);
 
+// 2026-10-30:研究室(series6)はお手本の関節の動きを重ねた白黒ツートンの図(tools/series_cards/series6.py)、導入シミュレーション(series8)は Unity の画面、画像認識(series7)は赤い箱の写真が処理されていく4列の図(tools/series_cards/series7.py)に変えた。
+// 前の絵:研究室 = media の s-1672x941_76620d86-fa7b-491f-b554-99c2f8981d8d.webp、画像認識 = media の s-1800x1200_v-frms_82af05c4-20df-4ea1-b88d-355ed8fabd63.webp、導入シミュレーション = /site/series8-service-robot-simulation.png?v=3(どれも消さずに残してある)
 const series = [
   { href: '/series1', img: 's-1920x1318_v-frms_4cd7792b-68a1-4239-ad08-0785c4e538c5.webp', en: 'Drug Delivery Robot', ja: '薬剤搬送ロボットハコブくん',
     desc: '病院で薬を運ぶロボット「ハコブくん」を題材に、人とロボットの関わり(HRI)を設計する記録。画面の設計、Unity での自律移動、日々の開発の記録を、4コママンガと一緒に残しています。' },
-  { href: '/series6', img: 's-1672x941_76620d86-fa7b-491f-b554-99c2f8981d8d.webp', en: 'Physical AI Lab', ja: 'フィジカルAI研究室',
+  { href: '/series6', img: '/site/series6-physical-ai-lab-demos.webp', en: 'Physical AI Lab', ja: 'フィジカルAI研究室',
     desc: 'ロボットアームが動きを覚える環境を、自宅にひとりで作る記録。お手本の動きを集めてロボット用のAIに学習させ、実機で確かめます。誰でも同じ手順で始められる形で残しています。' },
-  { href: '/series7', img: 's-1800x1200_v-frms_82af05c4-20df-4ea1-b88d-355ed8fabd63.webp', en: 'Image recognition and Object detection', ja: '画像認識と物体検出',
+  { href: '/series7', img: '/site/series7-image-recognition-steps.webp', en: 'Image recognition and Object detection', ja: '画像認識と物体検出',
     desc: 'ロボットのカメラが物を見つける仕組みを、下の層から学び直す記録。画素・色・しきい値・輪郭といった手法を、机の上のアームを撮った写真で1つずつ測って確かめます。' },
-  { href: '/series8', img: '/site/series8-service-robot-simulation.png?v=3', en: 'Service Robot Simulation', ja: 'サービスロボット導入シミュレーション',
+  { href: '/series8', img: '/site/series8-service-robot-unity.webp', en: 'Service Robot Simulation', ja: 'サービスロボット導入シミュレーション',
     desc: '配膳・清掃・搬送などのロボットを店や施設に入れる前に、待ち時間・人数・止まる場所・費用を試算する記録。時間と台数は SimPy、空間と動きは Unity で扱い、前提・結果・コードをすべて公開しています。' },
   { href: '/series5', img: '/site/series5-deep-dive.jpg', // 考察記事のサムネイルと同じ形(3-B)。元の HTML は tools/series5_top_card.html(2026-10-26)
     en: 'Deep Dive', ja: '考察記事',

作業ログ ― サイトの計測の記録を読んだ

サイトの計測は3つある

このサイトには、自分で作った計測が3つあります。どれも、見ている人を見分ける情報(IP アドレス・Cookie など)は記録しません。

計測何を数えるか始めた日
サイトの使われ方(/api/ev-stats)
ページを表示した回数、どこまで読んだか、押したもの、どこから来たか。人か機械かの印も付ける
10/18(人と機械を見分けるのは 10/26 から)
クローラーの訪問(/api/cr-stats)
検索エンジンと AI の巡回プログラムが、どのページを読みに来たか
10/25
ハコブくんのお知らせ(/api/hk-stats)
新しい記事のお知らせが出た・押された回数
10/17

クローラー(crawler)は、検索エンジンや AI の会社が、Web のページを自動で読んで回るプログラムです。名乗り(GPTBot・ClaudeBot など)で、どこの会社の何のためのものかが分かります。ページの中の JavaScript を動かさないものが多いので、サイトの使われ方の計測には出てきません。そのため、ページを返すときに、名乗りを見て別に記録しています。

人の閲覧は5日で130回、計測に入った表示の9割は機械だった

週(月曜はじまり)表示人機械操作なし印なし(10/25 より前)
10/26〜11/01(10/30 夕方まで)
1,6211301,434570
10/19〜10/251,29212011,270
10/12〜10/182200022

人の閲覧(10/26〜10/30)の中身です。

項目値
表示130回
表示していた時間の中央値15秒
ページの4分の3まで読んだ割合
28%
スマホの割合40%
戻るボタンで戻った3回
外のサイトへのリンクを押した8回
リンクでない所を押した13回
メッセージのページの表示
2回(入力を始めた・送信は0)

来た元です。

来たところ回数
(直接・不明)59
(サイト内)27
www.google.com26
8
4
teams.public.onecdn.static.microsoft
3
t.co(X)3
com.google.android.googlequicksearchbox
1

Microsoft Teams から3回来ています。だれかが社内のチャットにリンクを貼った跡だと思われます。

人がよく見たページです(表示2回以上)。

ページ表示時間の中央値(秒)4分の3まで読んだ割合(%)
/gpu-buy-or-rent
171112
/122250
/issues6120
/record/service-robot-last-mile
6180
/series641825
/series44950
/record/building-a-physical-ai-lab-at-home
4140
/record/beyond-imitation-learning
41650
/series84150
/record/day145311133
/record/from-sim-series-to-lp
314033
/record/serving-robot-hidden-waits
3150
/record/pi05-first-block-in-box
2110
/record/rewrite-research-report
260
/record/day17230100
/message211100
/record/serving-robot-cost-subsidy
2810
/record/pi05-mecharm-demo-to-inference
2170
/one-year27750
/manga-archive290
/serving-robot-one-less-staff
22250
/record/rewind-and-correct-pi05
210650
/record/road-marking-job-post-and-physical-ai
2201100
/record/vertical-4koma-for-shorts
211350
/record/serving-robot-video-part2
2150

1回だけ見られたページのうち、長く読まれたものです。

ページ時間(秒)
/record/microfactory-fairino-fr3
2,104
/record/day18424
/record/first-search-hit-to-ai-citation
416
/record/dev-mac-free-space
351
/record/japan-robot-leader-physical-ai-gap
206
/record/day12180
/record/ai-readable-openings
174
/record/youtube-views-to-right-viewers
159

読み取れたことは3つです。

  • 来る人がいちばん多いのは導入比較ガイド(GPU を買うか借りるか)で、11秒で離れる人が多い。10/28 に最初の画面へ結論を置いたので、すぐ離れること自体は失敗ではない。次は計算機まで押されたかを見る
  • 課題地図(/issues)を開いた6人は、だれもページの4分の1より先にスクロールしていない
  • 少数だが、考察寄りの記事(マイクロファクトリー・路面標示の求人とフィジカルAI など)は3〜35分読まれている

クローラーは今週約7,900回、ChatGPT の利用者の依頼での読み取りは48回

表は、クローラーの記録を始めた 10/25 から 10/30 夕方までの合計です(8,062回。そのうち 10/26 からの週が7,901回)。

クローラーだれの・何のため読んだ回数ページの数記録(LOG)の数見つからない等(400以上)
Amazonbot
Amazon(Alexa・AI)
2,0351,31180312
meta-externalagent
Meta(AI)1,86798241924
GooglebotGoogle(検索)1,76959427511
Applebot
Apple(Siri・検索・AI)
645398646
ClaudeBotAnthropic(学習)4993406419
GoogleOtherGoogle(その他)337191550
GPTBotOpenAI(学習)3173114819
bingbot
Bing(検索。ChatGPT の検索・Copilot も使う)
3041411212
OAI-SearchBot
OpenAI(ChatGPT の検索)
16771571
ChatGPT-User
ChatGPT(利用者の依頼で読む)
4828240
YandexBotYandex(検索)24940
PerplexityBotPerplexity(検索)2116110
BytespiderByteDance(AI)18520
BaiduspiderBaidu(検索)6310
Claude-User
Claude(利用者の依頼で読む)
2220
DuckDuckBotDuckDuckGo(検索)2100
YetiNaver(検索)1100

この中で、狙いに直結するのは ChatGPT-User です。ChatGPT の利用者が質問したときに、答えを作るためにページを読みに来た記録だからです。Amazon・Meta・Google の大半は、どのサイトにも来る広い巡回で、このサイトが特に評価されているという意味ではありません。クローラーの記録は 10/25 に始めたので、前の週(161回)と比べて増えたとも言えません。

ChatGPT-User と OAI-SearchBot が読んだページです。

ChatGPT-User が読んだページ
回数
/16
/record/from-sim-series-to-lp
3
/record/microfactory-fairino-fr3
2
/record/serving-robot-cost-subsidy
2
/serving-robot-one-less-staff
2
/record/explainer-video-simulation-background
1
/record/pixelstomotors-com-domain
1
/record/first-video-end-to-end
1
/record/lock-down-ai-weekly-writes
1
/record/youtube-script-curation
1
/record/dev-mac-free-space
1
/record/shorts-81-percent-swiped
1
OAI-SearchBot が読んだページ
回数
/46
/robots.txt19
/updates.json17
/record/akita-data-center-robotics-perspective
6
/record/first-video-end-to-end
5
/record/pi05-libero-benchmark-first-run
5
/record/issues-map-weekly-update
4
/record/day242
/record/video-progress-board
2
/record/lock-down-ai-weekly-writes
1
/record/from-sim-series-to-lp
1
/record/dev-mac-free-space
1

なぜ人より先に AI に読まれているのか

「人に見られるサイト」より「AI に読まれるサイト」として先に育って これは何が要因だと思われますか?こちらの意図と合っていますか?

と聞かれ、考えられる要因を3つ挙げました。

  1. AI に読ませる準備を先にしてきた。検索に出さない設定の解除とサイトマップ(10/17)、AI 向けの案内 /llms.txt、公開をすぐ検索エンジンに知らせる IndexNow(10/28)、記事を AI が読む前提で書く(長いコードを別ファイルに分ける・最初の1文を問いへの答えにする)
  2. 毎日記事を出している。更新の多いサイトには、クローラーが何度も戻ってくる。トップ・robots.txt・updates.json・サイトマップへの訪問が上位に来ている
  3. 人の入口がまだ細い。新しいドメインに移って2週間ほどで、検索の評価がまだ付いていない。SNS から来たのは LinkedIn 4回・X 3回

意図とは合っています。記事は人より AI が読む前提で書き、AI 経由で読まれる数を、主指標の「再現可能な環境数」と同じくらい大事にしているからです。ただ、AI が読んだあとに人が来ているかは見えていません。 ChatGPT の画面のリンクから来た人は、来た元に chatgpt.com が出るはずですが、今週は0回でした。

作業ログ ― AI から来た人を数えるようにした

utm_source は、リンクに付く「来た元の印」

utm_source は、リンクの住所の後ろに付ける ?utm_source=名前 の印です。もともとは広告やメールのリンクがどこから押されたかを数えるために使われてきたもので、ChatGPT は回答の中のリンクに ?utm_source=chatgpt.com を付けます(OpenAI の説明)。

言葉意味
referrer(リファラー)
ブラウザが「どのページから来たか」をサイトに伝える情報。アプリから開いたときなどは送られない
utm_source
リンクの住所に付ける来た元の印。リンクを作った側が付けるので、referrer が送られなくても残る

これまでのサイトの計測は、referrer だけで来た元を決めていました。スマホの ChatGPT のアプリからリンクを開くと、referrer が送られないことが多く、そのまま「直接・不明」(今週59回で最多)に入ります。

印の値だけを記録し、「どこから来たか」の表で分けて出す

外から入った最初の表示のときだけ、utm_source の値を記録するようにしました。値は英小文字・数字・.・_・- の40字までで、それ以外は捨てます。「どこから来たか」の表では、referrer がなく印があるときに「chatgpt.com(リンクの印)」として数え、印だけをまとめた表も足しました。記録する項目が増えたので、プライバシーポリシーにも書き足しました。

確かめは2段でしました。受ける側は、Node で worker を読み込み、R2 をまねた入れ物に記録を入れて、表の数え方を見ました。送る側は、Playwright でページを開き、送られる中身を取り出しました。

(受ける側:chatgpt.com の印・Google から・おかしな印・何もなし の4件を送った)
204
204
204
204
[{"from":"(直接・不明)","count":2},{"from":"chatgpt.com(リンクの印)","count":1},{"from":"www.google.com","count":1}] [{"source":"chatgpt.com","count":1}]

(送る側:ページを開いて、送られた中身の p と us を出した)
/series6.html?utm_source=chatgpt.com → p= /series6.html us= "chatgpt.com" r= ""
/series7.html → p= /series7.html us= "" r= ""
/?utm_source=%3Cx%3E → p= / us= "" r= ""

<x> のようなおかしな印は記録されず、「直接・不明」に入りました。

  • ねらい:AI が読んだあと、人がサイトに来ているかを見えるようにする
  • 変わったこと:「直接・不明」に混ざっていた、AI の回答のリンクから来た人が「chatgpt.com(リンクの印)」として分かれて出る
  • 見込める効果(見込み):来週の「どこから来たか」の表で、印の付いた来訪が出るかを見る。出なければ、AI は読んでいるがリンクを押されていない、と分かる

worker/index.js・SiteMeasure.astro・privacypolicy.astro の変更(utm_source とクローラーの表)

02_sec03.txt11,596字・136行・diff
commit 34f2ba054d5bdd06d5d61891318a3a197c68f738
Author: Claude <noreply@anthropic.com>
Date:   Fri Oct 30 17:22:18 2026 +0900

    計測:リンクの来た元の印(utm_source)を記録、クローラーの表に返せなかったページを追加
    
    AI の回答のリンク(?utm_source=chatgpt.com)から来た人が「直接・不明」に混ざらないようにする。
    プライバシーポリシーに記録する項目を追記。
別のタブで開く保存

作業ログ ― クローラーが開けなかったページを特定して直した

返せなかったページを、クローラーの表に足した

クローラーの表には、返せなかった訪問の「回数」しか出ていませんでした。上の変更で、ページと結果(404 など)ごとの一覧も出るようにしました。公開後に開いた一覧です。

回数結果ページクローラー
6404/manga
ClaudeBot、GPTBot、Googlebot、OAI-SearchBot、Amazonbot
5404/ai-agent
meta-externalagent、Googlebot
3404
/record/%7BR_%5Bn%5D%7D
ClaudeBot、GPTBot、Amazonbot
3404
/record/%7BR_%5Blg%5D%7D
ClaudeBot、GPTBot、Amazonbot
3404
/record/precision-is-not-learning
meta-externalagent
3404
/english-4-koma/ep9
meta-externalagent
3404/prototype6
meta-externalagent、Googlebot
3404
/files/LOG388/%7Bs.img.startsWith(’/’)
GPTBot、ClaudeBot、Amazonbot
3404
/record/side-camera-pipeline-split
meta-externalagent
3404/prototype3
meta-externalagent、Googlebot
3404
/files/LOG361/18_AI_%E2%80%94state-from-target.txt
Googlebot、Applebot
3404
/files/LOG361/01_camera_align.py_%E2%80%94episode.txt
Applebot、Googlebot
3404
/files/LOG395/%7Bm%5Bl%5D%7D
ClaudeBot、GPTBot、Amazonbot
3404
/files/LOG395/%7Bc%7D
ClaudeBot、GPTBot、Amazonbot
3404
/files/LOG395/%7Bo%7D
ClaudeBot、GPTBot、Amazonbot
2404
/files/LOG378/‘%20%2B%20LOGO%20%2B%20’
ClaudeBot、GPTBot
2404
/files/LOG382/‘%20%2B%20LOGO%20%2B%20’
ClaudeBot、GPTBot
2404
/files/LOG385/%7B%60/%24%7BseriesPage%5B0%5D%7D%60%7D
ClaudeBot、GPTBot
2404/prototype5
meta-externalagent
2404
/files/LOG388/%7B%60/%24%7BseriesPage%5B0%5D%7D%60%7D
GPTBot、ClaudeBot
2404/prototype1
meta-externalagent
2404/prototype2
meta-externalagent
2404/manga/ep9
meta-externalagent
2404
/files/LOG361/12_1_%E2%80%94via-stride.txt
Googlebot、Applebot
2404
/files/LOG361/13_1_%E2%80%94via-stride.txt
Applebot、Googlebot
2404
/files/LOG373/%7Bs.img.startsWith(’/’)
Amazonbot
2404
/files/LOG395/%7BEI%5Bp%5D%5B0%5D%7D
ClaudeBot、GPTBot
2404
/files/LOG395/‘%20%2B%20LOGO%20%2B%20’
ClaudeBot、GPTBot
2404
/files/LOG395/%7Be%5B0%5D%7D
ClaudeBot、GPTBot
2404
/files/LOG395/%7Be%5B1%5D%7D
ClaudeBot、GPTBot
2404
/files/LOG395/%7B_c%7D
ClaudeBot、GPTBot
2404
/files/LOG395/%7B_o%7D
ClaudeBot、GPTBot
2404
/files/LOG399/%7BCTA%7D
ClaudeBot、GPTBot
2404
/files/LOG399/%7B%60/%24%7BseriesPage%5B0%5D%7D%60%7D
ClaudeBot、GPTBot
1404
/BingSiteAuth.xml
bingbot
1404/prototype4
meta-externalagent
1404
/files/LOG361/11_1_%E2%80%94via-stride.txt
Googlebot
1404
/files/LOG371/%7B%60
Amazonbot
1404
/files/LOG373/%7Bhref(l)%7D
Amazonbot
1404/record/bingbot

3種類に分かれました。

種類例扱い
サイトの不具合
/files/LOG361/11_1_—via-stride.txt
直した(下の節)
古い住所
/manga・/prototype1〜6・/english-4-koma/ep9
行き先がはっきりしたものだけ転送した(次の作業ログ)
コードの文字列を住所と勘違いしたもの
/files/LOG395/{c}・/record/{R_[n]}
存在しない住所なので、404 を返すのが正しい。直さない

記事のファイルの枠で、「—」が「—」に置き換わっていた

LOG361 の記事には、11_1_--via-stride.txt のように、名前に -- を含むファイルが5本あります。記事の Markdown を HTML に変える途中で、-- が全角のダッシュ「—」に置き換わっていました。読みやすい記号に直す仕組み(smartypants)が、ファイル名にまで効いていたためです。そのため、ファイルの枠のリンクは存在しない住所を指し、押しても開けず、枠の中の先頭の数行も空でした。クローラーだけでなく、人にとっても壊れていたリンクです。

直し方は、枠を作るときに、ファイルが見つからなければ「—」を「—」に、「–」を「-」に戻して探し直す形にしました。

src/lib/remark-markers.mjs の変更

commit 4fa96239b1ce36f67e749394b6159c29f637a1f9
Author: Claude <noreply@anthropic.com>
Date:   Fri Oct 30 17:29:42 2026 +0900

    記事のファイルの枠:ファイル名の「--」が「—」に置き換わってリンクが開けなかったのを直す(LOG361 の5本)
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXSDAgg3MAhAQWAaaXy

diff --git a/src/lib/remark-markers.mjs b/src/lib/remark-markers.mjs
index 56978c8..ce69d3b 100644
--- a/src/lib/remark-markers.mjs
+++ b/src/lib/remark-markers.mjs
@@ -82,6 +82,12 @@ export default function remarkMarkers() {
       }
       m = text.match(/^【ファイル】\s*(LOG\d+\/\S+)\s+(\S+)\s+(\S+)$/);
       if (m) {
+        // 読み込みの途中で「--」が「—」(「-」が「–」)に置き換わることがある。そのままでは
+        // 「11_1_--via-stride.txt」のようなファイル名が変わってリンクが開けないので、元のファイル名に戻す(2026-10-30)
+        if (!existsSync(`public/files/${m[1]}`)) {
+          const back = m[1].replace(/—/g, '--').replace(/–/g, '-');
+          if (existsSync(`public/files/${back}`)) m[1] = back;
+        }
         const href = `/files/${m[1]}`;
         const name = m[1].split('/').pop();
         const disk = `public${href}`;

直しが本番に出なかった:Astro の記事の作り置き

直してビルドしても、手元のページは変わりませんでした。Astro(このサイトを作っている仕組み)は、記事の中身が変わらないと、前に変換した結果(node_modules/.astro/data-store.json)を使い回すためです。変換の仕組みのほうを直しても、記事の中身は同じなので、作り置きがそのまま使われていました。

手元では作り置きを消して直りましたが、本番に出しても直りませんでした。Cloudflare のビルドも、この作り置きを次のビルドへ持ち越していたためです。

そこで、ビルドの前に動く小さなスクリプトを足しました。src/lib の remark・rehype のプラグインの中身からハッシュ(中身の指紋)を作り、前のビルドのときと違えば作り置きを捨てます。同じなら何もしないので、ふだんのビルドは速いままです。

tools/clear_content_cache.mjs

// 記事の変換の仕組み(src/lib の remark・rehype のプラグイン)を変えたときだけ、Astro の記事の作り置き(data-store.json)を捨てる(2026-10-30)。
// Astro は記事の中身が変わらないと前の変換結果を使い回すため、プラグインを直しても公開中のページに反映されなかった(Cloudflare のビルドも作り置きを持ち越す)。
// package.json の build で astro build の前に動く。プラグインが変わっていなければ何もしない。
import { createHash } from 'node:crypto';
import { readFileSync, readdirSync, existsSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
const files = readdirSync('src/lib').filter((f) => /^(remark|rehype)-.*\.m?js$/.test(f)).sort();
const h = createHash('sha256');
for (const f of files) h.update(f).update(readFileSync(`src/lib/${f}`));
const now = h.digest('hex');
const mark = 'node_modules/.astro/markdown-plugins.sha256';
const before = existsSync(mark) ? readFileSync(mark, 'utf-8').trim() : '';
if (before !== now) {
  for (const p of ['node_modules/.astro/data-store.json', '.astro/data-store.json']) if (existsSync(p)) rmSync(p);
  mkdirSync('node_modules/.astro', { recursive: true });
  writeFileSync(mark, now);
  console.log(`記事の変換の仕組みが変わったので、記事の作り置きを捨てた(${files.join('、')})`);
}

package.json の変更

commit 27b263670cb261ba19210eff1faa3d29d2919427
Author: Claude <noreply@anthropic.com>
Date:   Fri Oct 30 17:33:41 2026 +0900

    ビルド:記事の変換の仕組みを変えたときだけ Astro の記事の作り置きを捨てる(直しが本番に反映されなかったため)
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXSDAgg3MAhAQWAaaXy

diff --git a/package.json b/package.json
index cc6f497..c695f91 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
   "type": "module",
   "private": true,
   "scripts": {
-    "build": "astro build",
+    "build": "node tools/clear_content_cache.mjs && astro build",
     "dev": "astro dev",
     "preview": "astro preview"
   },

公開後に本番のページを開いて、5本とも正しい住所になり、ファイルが開けることを確かめました。

data-src="/files/LOG361/01_camera_align.py_--episode.txt"
data-src="/files/LOG361/11_1_--via-stride.txt"
data-src="/files/LOG361/12_1_--via-stride.txt"
data-src="/files/LOG361/13_1_--via-stride.txt"
data-src="/files/LOG361/18_AI_--state-from-target.txt"
200

作業ログ ― 古い住所を今のページへ転送した

301 転送は「引っ越し先を知らせる」返事

301 転送は、ページに来た人やクローラーに「このページは引っ越した。新しい住所はこちら」と伝える返事です。ブラウザは自動で新しい住所を開き、検索エンジンは古い住所の評価を新しい住所へ移します(Google の説明)。一度出すと、ブラウザや検索エンジンが覚えるので、あとで戻しにくい変更です。

行き先がはっきりしたものだけを転送した

古い住所転送先理由
/manga・/manga//manga-archive
4コマの一覧は /manga-archive。/manga という一覧ページはない
/manga/ep9 のような1桁の話数
/manga/ep09
話数の住所は2桁。ページがある話だけ転送する
/english-4-koma/ep9 のような1桁
/english-4-koma/ep09
同上(英語版)
/record/precision-is-not-learning(末尾の / もあり)
/record/precision-is-not-learning-so101
記事の住所の末尾が変わっていた
/prototype1〜6(末尾の / もあり)
/series1〜6
前はシリーズのページの住所が /prototype だった

最初の案では、/ai-agent と /prototype1〜6 は、出どころが分からないので 404 のままにしていました。確かめた範囲で手がかりがなかったためです(サイトのアーカイブ、iCloud の作業ファイル、Web 検索)。

/ai-agentは今は使っていません。Difyを使ってAIエージェントを動かしてました。 /prototype1〜6は前は違ったslugだった可能性があります

前はシリーズページがprototypeでした

/ai-agent は使っていないページなので、404 のままにしました。/prototype1〜6 は、同じ番号の /series1〜6 へ転送することにしました。

住所を変える変更なので、codex のレビューを通した

このプロジェクトでは、公開したあとに戻しにくい変更(住所・転送・フォーム・データの消去)は、公開の前に外部の AI のレビューを通す決まりです。同じ書き手で書いて確かめると、同じ見落としをするためです。

codex(OpenAI のコードを書く AI を、Mac のターミナルで動かす道具)に、読み取りだけの設定でレビューしてもらいました。渡し方は、Mac の「依頼箱」(Claude が置いた依頼を Claude Code に自動で渡す仕組み)を使い、Claude Code が codex に依頼文を渡して、答えをそのまま返す形です。

レビューを待つ間は、転送の変更を main とは別のブランチ(redirect-old-paths)に置き、公開していません。

codex のレビュー依頼

03_codex.txt3,687字・84行・text
# コードレビュー依頼:pixelstomotors.com の古い住所の 301 転送

対象:Cloudflare Workers の worker/index.js(差分を下に貼る)。サイトは Astro で作った静的ファイルを Workers の静的アセット(env.ASSETS)から返している。wrangler.jsonc の run_worker_first は ["/*", "!/_astro/*", "!/site/*", "!/og/*", "!/thumbs/*", "!/embeds/*", "!/sim/*", "!/favicon*"] なので、ページへのリクエストは必ず worker の fetch() を通る。

## やろうとしていること

検索エンジン・AI のクローラーが古い住所で読みに来て 404 になっているもののうち、行き先がはっきりしているものだけを 301 で今のページへ転送する。
別のタブで開く保存

codex のレビュー結果(Claude Code の報告から、手を加えずに)

04_codex.txt3,411字・97行・text
**公開前に、存在しない話への 301 だけ絞り込むことを勧めます。** 条件の範囲と処理順は概ね適切で、提示された差分自体に転送ループはありません。

以下は提示された差分・設定・検証結果に基づくレビューです。実際のビルド成果物や既存の転送設定までは確認していません。

### A1. 転送条件:✅ OK

完全一致と正規表現で、対象を十分限定できています。
別のタブで開く保存

codex の指摘を反映して公開した

指摘はどれも反映しました。

指摘直したこと
A3 ページのない話まで 301 にしている
1桁の話数は、実際にページがある話(4コマは1〜7・9、英語版は1〜4・7・9)だけ転送する
B1 転送した訪問がクローラーの記録に残らない
転送した訪問も、結果 301 として記録する
B1 301 に共通ヘッダーが付かない
ほかの返事と同じ secured() を通す
C1 末尾に / が付いた形
/record/precision-is-not-learning/ も転送する
A2 本番で最後に 200 が返るか
公開後に確かめた(下)

worker/index.js の変更(転送)

diff --git a/worker/index.js b/worker/index.js
index 2c44687..b4375c2 100644
--- a/worker/index.js
+++ b/worker/index.js
@@ -26,6 +26,18 @@ export default {
     if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/media/') || url.pathname === '/pixelstomotors' || url.pathname.startsWith('/pixelstomotors/')) {
       return secured(await route(request, env, url));
     }
+    // 古い住所・まちがった住所への訪問を、今のページへ恒久転送する(2026-10-30。クローラーの「返せなかったページ」から)
+    const moved = movedTo(url.pathname);
+    if (moved) {
+      const to = new URL(moved, url);
+      to.search = url.search;
+      // クローラーの記録にも残す(返した結果 301。どの古い住所に来たかが分かるように)
+      try {
+        const bot = crawlerOf(request, url);
+        if (bot) ctx.waitUntil(crRecord(request, env, url, bot, 301).catch(() => {}));
+      } catch { /* 数え損ねても転送はする */ }
+      return secured(Response.redirect(to.toString(), 301));
+    }
     const res = await env.ASSETS.fetch(request);
     // 検索エンジン・AI のクローラーが読みに来たか(記録の失敗でページを止めない)
     try {
@@ -40,6 +52,26 @@ export default {
   },
 };
 
+// 転送先の決まった古い住所。今は使っていないページ(/ai-agent。Dify で AI エージェントを動かしていた頃のもの)は転送しない(404 のまま)
+const MOVED = {
+  '/manga': '/manga-archive',
+  '/manga/': '/manga-archive',
+  '/record/precision-is-not-learning': '/record/precision-is-not-learning-so101',
+  '/record/precision-is-not-learning/': '/record/precision-is-not-learning-so101',
+};
+// 1桁の話数で来たとき、0を付けた住所へ転送するのは、ページがある話だけ(ない話を転送しても 404 になるだけなので。2026-10-30 の codex のレビュー)
+const EPISODES_1_9 = { manga: new Set(['1', '2', '3', '4', '5', '6', '7', '9']), 'english-4-koma': new Set(['1', '2', '3', '4', '7', '9']) };
+function movedTo(path) {
+  if (MOVED[path]) return MOVED[path];
+  // シリーズのページは、前は /prototype1〜6 だった(エイイチ 2026-10-30)。同じ番号の /series へ
+  const sp = path.match(/^\/prototype([1-6])\/?$/);
+  if (sp) return `/series${sp[1]}`;
+  // 4コマの話数は2桁(ep09)。1桁(ep9)で来たら0を付けた住所へ
+  const m = path.match(/^\/(manga|english-4-koma)\/ep([1-9])$/);
+  if (m && EPISODES_1_9[m[1]].has(m[2])) return `/${m[1]}/ep0${m[2]}`;
+  return '';
+}
+
 async function route(request, env, url) {
     // 旧 URL の形(/pixelstomotors/…)で来たら、/pixelstomotors を外した URL へ恒久転送する
     if (url.pathname === '/pixelstomotors' || url.pathname.startsWith('/pixelstomotors/')) {

公開後に、本番で古い住所を開いた結果です。かっこの中は転送の回数です。

/manga → 200 https://pixelstomotors.com/manga-archive (1)
/manga/ep9 → 200 https://pixelstomotors.com/manga/ep09 (1)
/english-4-koma/ep7 → 200 https://pixelstomotors.com/english-4-koma/ep07 (1)
/record/precision-is-not-learning → 200 https://pixelstomotors.com/record/precision-is-not-learning-so101 (1)
/record/precision-is-not-learning/ → 200 https://pixelstomotors.com/record/precision-is-not-learning-so101 (1)
/prototype1 → 200 https://pixelstomotors.com/series1 (1)
/prototype6/ → 200 https://pixelstomotors.com/series6 (1)
/manga/ep8 → 404 https://pixelstomotors.com/manga/ep8 (0)
/ai-agent → 404 https://pixelstomotors.com/ai-agent (0)

どれも転送1回で目的のページに着き、ページのない話と /ai-agent は 404 のままです。

作業ログ ― SNS の予約の枠を広げ、課題地図の42ページを毎日出すようにした

記事の X の予約に、毎日7:30の枠を足した

この記事の X の予約は、決まりの空いている枠(平日12:30・平日19:30・土日12:30)がすでに埋まっていて、11/25 19:30 になりました。記事を出す速さが上がり、予約がどんどん先になっていたためです。

LOGは前よりスピードが合って増えてるので、投稿時間を朝と昼にも振り分けていいよ(そうじゃないとどんどん先になるため)

昼の12:30 は平日・土日とも枠にあったので、朝の7:30 を毎日の枠に足しました。この記事の X は 10/31 7:30 に入れ直しています。

前後
平日12:30・19:30
7:30・12:30・19:30
土日12:307:30・12:30

決まりは、記事のルール(article_rules.md)と、予約投稿の手順書(r2r2r-content の docs/sns_autopost.md)に書きました。マンガの英語の投稿(平日22:30)と LinkedIn の研究の記録(平日17:00)の時刻は変えていません。

課題地図の42ページを、X は毎日21:00、LinkedIn は毎日8:00 に出す

課題地図(pixelstomotors.com/issues )は、フィジカルAIが社会で使われる前に解くべき課題を、1課題1ページでまとめたページです。今は42ページあります。

それと追加で。フィジカルAIが社会実装される前に解くべき課題。サイトのページごとに投稿するようにしてほしい(かなり数があるけど大事な情報なので発信する必要がある)

linkdeinにも投稿して

記事の枠に入れると、42本で記事の予約が1か月以上先へ押し出されます。そのため、課題地図だけの枠を別に作りました。

時刻期間文の形
X毎日21:0010/31〜12/11
1行目で手が止まる言い方・何が問題か・最初の一歩・URL・話題のタグ2つ
LinkedIn毎日8:00(X と同じ日)10/31〜12/11
1行目・この課題の要点3つ・地図にまとめていることの1文・読み手への問い・タグ。リンクはカードで付く

出す順番は、課題ごとの「急ぎ度」と「深刻度」を掛けた点数の高い順です(同じ点数なら番号順)。最初は「介護現場へのロボット導入の壁」、次が「実機データの圧倒的不足」「動作のポータビリティ不在」です。文は r2r2r-content の sns/ISSUE_{ページ名}_sns_posts.json に1ページ1つずつ置き、42本とも予約の点検(python3 tools/sns_autopost.py check)を通し、リンク先の42ページがすべて開けることを確かめました。課題の数はこれから増えるので、文の中に数は書いていません。

  • ねらい:課題地図を、検索や AI 経由だけでなく、SNS からも毎日見てもらう
  • 変わったこと:課題地図のページが、X と LinkedIn に毎日1本ずつ出る。記事の予約は押し出されない
  • 見込める効果(見込み):課題地図(/issues)への人の表示が増える。今週は6回で、開いた人はだれもページの4分の1より先まで読んでいなかった。2週間後に、/issues/ の各ページの表示と、来た元の t.co(X)・linkedin.com の数を比べて確かめる

LinkedIn は、平日だと研究の記録(17:00)と課題地図(8:00)の2本が同じ日に出ます。前に LinkedIn の分析で、同じ日の投稿どうしが見られる数を取り合っていたので、2週間ほどで1本あたりの数字が下がっていないかも見ます。

次は、AI が読んだあとに人が来ているかを見る

今週の数字で分かったのは、AI は読みに来ているが、そこから人が来ているかはまだ見えていない、ということです。印の記録を入れたので、来週は2つの数字を見ます。

  • 「どこから来たか」の表に、chatgpt.com・perplexity.ai などの印や来た元が出てくるか
  • ChatGPT-User が読んだページが増えていくか。どのページが読まれているか

クローラー全体の回数は、どのサイトにも来る巡回が大半なので、方針の判断には使いません。