OTAの予約が急に止まった原因を公開クチコミの日付分布で切り分ける
宿泊施設のOTAで受注が急減した時、重複掲載・競合・値上げのどれが引き金かを、公開クチコミの日付分布だけで潰す手順。Booking.comの実測済みDOMセレクタ、ページ送りが止まらない罠、代理指標の検算方法、exit codeでの機械判定つき。
約2.2万トークンの節約 (API料金換算で約33円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「OTAの予約が急に止まった原因を公開クチコミの日付分布で切り分ける」は、データ収集カテゴリのAI指示書(MDファイル)です。宿泊施設のOTAで受注が急減した時、重複掲載・競合・値上げのどれが引き金かを、公開クチコミの日付分布だけで潰す手順。Booking.comの実測済みDOMセレクタ、ページ送りが止まらない罠、代理指標の検算方法、exit codeでの機械判定つき。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約2.2万トークン(API料金換算で約33円)・84%のトークンを節約できます。
- カテゴリ
- データ収集
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約2.6万トークン
- この巻物使用時
- 約4,200トークン
- 節約量
- 約2.2万トークン (約33円)
- 更新日
- 2026-09-01
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/ota/raw を読み込んで、この指示書どおりに実装して"
中身
OTAの予約が急に止まった原因を「公開クチコミの日付分布」で切り分ける
宿泊施設の OTA(Booking.com 等)である日を境に受注が激減したとき、原因候補 (重複掲載・競合の新規参入・値上げ・掲載順位)を最小の手数で潰すための手順。 競合や他人の掲載は内部数字が見えないが、公開クチコミの日付は誰でも取れるので、 これを実売の代理指標にして時系列を比べる。
読者は AI エージェント(Claude Code / Codex 等)を想定。そのまま実行できる粒度で書く。
0. 前提
- 自社の予約データ(PMS / チャネルマネージャの受注一覧)が手元にあること
- Node.js 18+ と Playwright(
npm i playwright && npx playwright install chromium) - 断絶の日付が特定済みであること。まだなら先に「受注が作られた日」の日次件数を出す。 到着日ベースで見ると断絶が1〜2ヶ月ずれて見えるので、必ず予約作成日で見る。
1. 順番を間違えない — 「誰が」より先に「いつ」
疑わしい原因を見つけたら、深掘りする前に「それはいつ生まれたか」を1回測る。 断絶の日付と発生時期が合わなければ、そこで仮説は終わる。
実例: 同じ住所に掲載が3本ある(重複掲載)と判明し「これが原因」と結論しかけたが、 重複2本の最古クチコミは断絶の 9ヶ月前と6ヶ月前だった。時期が合わないので引き金ではない。 「その掲載は誰のものか」を管理画面で調べる工数は丸ごと不要になった。
2. 奪い合い仮説は「相手も一緒に落ちていないか」で否定できる
A の需要を B が奪ったのなら、A が落ちる月に B は伸びる。 両方が同じ月に落ちているなら、効いているのは当事者間の競争ではなく 両者を含む上位のレイヤー(住所・エリア単位の露出、プラットフォーム側の措置、規制)。
これは相関の向きを1回見るだけで判定できる。競合分析を丸ごと省ける。
3. 代理指標を使う前に、自前データで倍率を確かめる
クチコミ件数は実売そのものではない。使う前に、自分の掲載で 「自前の予約データ ↔ クチコミ件数」が対応するかを必ず検算する。
- ずれの向きを合わせる。受注月と宿泊月は 1〜2ヶ月ずれる。 受注 N月 ≒ 宿泊 N+1月 なら、クチコミ(宿泊年月)も1ヶ月ずらして比べる。
- 季節性は前年同月で否定する。前年の同じ月が同水準なら季節ではない。
- 直近1〜2ヶ月はクチコミが遅れて届くので、下振れを結論に使わない。
4. 取得スクリプト(Booking.com の実測済み仕様)
物件ページ URL は https://www.booking.com/hotel/<国コード>/<掲載slug>.<言語>.html。
| 用途 | セレクタ / 実測値 |
|---|---|
| クチコミ一覧を開く | [data-testid="fr-read-all-reviews"](無ければ [data-testid="review-score-read-all"]) |
| クチコミ1件 | [data-testid="review-card"](1ページ10件) |
| 投稿日 | [data-testid="review-date"] → 投稿日:2026年8月29日 |
| 宿泊年月 | [data-testid="review-stay-date"] → 2026年8月 |
| 泊数 | [data-testid="review-num-nights"] → 2泊 · |
| 部屋タイプ | [data-testid="review-room-name"] |
| 次ページ | button[aria-label="次のページ"](ページ番号は aria-label="ページ 3") |
| 総件数 | body テキストの クチコミ(<数字>件)(全角カッコ) |
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({
locale: '<ロケール>', // 例: ja-JP
viewport: { width: 1400, height: 2200 },
userAgent: '<デスクトップChromeのUA文字列>',
});
const page = await ctx.newPage();
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 90000 });
await page.waitForTimeout(8000); // 描画待ち。短いとカードが0件になる
const total = (await page.evaluate(() => document.body.innerText))
.match(/クチコミ((\d+)件)/)?.[1];
await page.click('[data-testid="fr-read-all-reviews"]');
await page.waitForTimeout(6000);
const reviews = [];
let lastSig = null;
const maxPages = Math.min(45, Math.ceil((total ?? 450) / 10));
for (let p = 1; p <= maxPages; p++) {
for (const card of await page.$$('[data-testid="review-card"]')) {
const get = async (s) => (await card.$(s))?.innerText().then((t) => t.trim()) ?? null;
reviews.push({
postDate: (await get('[data-testid="review-date"]'))?.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/),
stay: (await get('[data-testid="review-stay-date"]'))?.match(/(\d{4})年(\d{1,2})月/),
});
}
// 最終ページでも「次のページ」に disabled が付かないことがある。
// 内容が前ページと同一なら重複計上する前に止める。
const sig = reviews.slice(-10).map((r) => `${r.postDate?.[0]}|${r.stay?.[0]}`).join(',');
if (sig === lastSig) { reviews.length -= 10; break; }
lastSig = sig;
const next = await page.$('button[aria-label="次のページ"]');
if (!next || (await next.getAttribute('aria-disabled')) === 'true') break;
await next.click();
await page.waitForTimeout(2500);
}
5. ここで必ず踏む落とし穴
- 最終ページで「次のページ」が disabled にならない。 素直に押し続けると同じ10件を何周も計上して件数が数倍に膨らむ。 総件数から出したページ数と前ページとの内容一致の両方で止める。
- 年月の繰り上げを
new Date(y, m, 1)で書くと2ヶ月飛びになる (JS の月は0始まりなのでmは既に翌月)。表の縦計と取得件数を突き合わせれば必ず気付く。 集計表には必ず「計」の行と「取得件数」の行を並べて出す。 - 分母を出さない集計は信用しない。 「取得できた件数 / 掲載上の総件数 / 取得率」を必ず出力する。 取得率が 100% でないなら、どの掲載で何件落ちたかも書く。
- 並列で回さない。 掲載ごとに順番に処理する。同時アクセスはブロックされる。
- 1件も取れなかった掲載があるなら「差が無い」ではなく「判定不能」。 exit code を分ける。
6. 機械判定(exit code)
人が目視で判断すると結論が揺れるので、判定条件をスクリプトに書いて exit code で返す。
| exit | 意味 |
|---|---|
| 1 | 疑わしい掲載の最古クチコミが断絶の前後1ヶ月以内 = 時期が合う。深掘りする価値あり |
| 0 | 疑わしい掲載は断絶より前から存在 = 引き金ではない。仮説を捨てる |
| 2 | どれか1つでもクチコミが0件 = 判定不能。どの掲載が取れなかったかを必ず出力 |
出力に必ず含めるもの:
- 掲載ごとに
取得件数 / 掲載上の総件数 / 取得率 / 最古の投稿日 / 最新の投稿日 / 最古の宿泊年月 - 掲載 × 宿泊年月の件数テーブル(縦計つき)
- 判定に使った値そのもの(「2026-04 以降か: false」のような真偽値)
7. ここまでで分かること・分からないこと
分かる: 疑わしい掲載がいつからあるか / 落ちたのが自分だけか全員か / 落ちた月が正確にいつか。
分からない: その掲載が誰のものか / プラットフォーム側が何をしたか。 これらは管理画面(エクストラネット)にしか無い。 公開情報で詰められるところまで詰めてから管理画面に行くと、見るべき画面が1枚に絞れる。
見ていない画面の操作手順を推測で書くと、ラベルや現在値が実物と違って相手に手戻りが出る。 開けない画面はスクリーンショットを1枚もらってから手順を書く。
よくある質問
+「OTAの予約が急に止まった原因を公開クチコミの日付分布で切り分ける」とは何ですか?
宿泊施設のOTAで受注が急減した時、重複掲載・競合・値上げのどれが引き金かを、公開クチコミの日付分布だけで潰す手順。Booking.comの実測済みDOMセレクタ、ページ送りが止まらない罠、代理指標の検算方法、exit codeでの機械判定つき。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約2.6万トークンかかりますが、この巻物を使えば約4,200トークンで済みます。差し引き約2.2万トークン(API料金換算で約33円)・84%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
定期スクレイピング→Googleスプレッドシート自動集計MD
競合価格・在庫・ランキングなどを毎日自動取得してスプレッドシートに蓄積するバッチを AI に作らせる指示書。robots.txt 尊重・レート制限・差分検知のベストプラクティス込み。
Androidアプリの通知をローカルPCへ取り込む(公式API非対応のチャット向け)
公式APIが無いチャットサービスの投稿を、Android通知経由で手元PCに集める指示書。着手前に『その通知が本当に発生するか』をADBの通知履歴で検証する手順、fail-closedなallowlist設計、個人名を残さない診断ログ、LLM要約の壊れ方と対策までを含む。
外部サイトの「異常」を報告する前に計測器を対照群で検定する
自社の掲載が検索に出ない・売切表示になる等を疑ったとき、対照群をツールに実装して測定不能をexit 2で止める手順。UA未設定で空ページを掴む罠、分母が列挙可能かの見方、履歴のない画面で「いつ落ちたか」を測らない原則つき。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア