競合するB2B企業が今四半期にどの国で広告を出しているのか。いつから掲載され、どの程度の配信量があり、どんなターゲティングを設定しているのか。こうした情報は、広告透明性の要件に応じて公開されている各プラットフォームの広告ライブラリに載っています。ところがDevToolsを開いて探しても、見つかるのはサーバーサイドレンダリングされたHTMLだけ。使いやすいJSONエンドポイントはありません。パーサーを書けば最初は取れます。しかし3週間後にUIがリニューアルされると、例外も出さないまま全フィールドが空になることがある。空データを根拠に判断しかけて、初めて気づく――これが一番危険です。
ここでは、リニューアル後も運用し続けられるパーサーの設計と、その保守でモデルが実際に役立つ場面を扱います。先にデータの境界を明確にしておくと、対象は各プラットフォームの公開広告ライブラリおよびクリエイティブセンターです。自分のアカウントで通常どおりログインして閲覧できる情報だけを取得し、署名の利用、制限の迂回、非公開エンドポイントへのアクセスは行いません。この線引きは、後述するパーサー設計にも明示的に組み込みます。
B2B広告インテリジェンスでHTML解析が避けられない理由
広告ライブラリの公開方法には、大きく3種類あります。Metaの広告ライブラリのように構造化検索やJSONを提供する消費者向けサービス。一方で、検索するだけでもセッションが必要なサービス。そしてB2Bプラットフォームの多くは、JSONエンドポイントを持たず、サーバーサイドレンダリングのHTMLだけを返します。違いの根本にあるのは、広告ライブラリが開発者向けAPIではなく、コンプライアンス対応の成果物だという点です。
存在目的は広告透明性規制への対応であり、開発者から呼び出されることではありません。バージョン番号も、変更履歴も、後方互換性の約束もない。人間がページを読むためにサーバーがHTMLを生成して返す。それが利用者に与えられた唯一の「API」です。
したがって壊れやすいのは本質的に避けられません。他社UIの実装詳細に依存し、相手は好きなタイミングで変更でき、事前に知らせる義務もないからです。JSON APIでフィールドが変われば、少なくとも明確な「変更」です。しかしHTMLのリニューアルは、相手にとっては通常のフロントエンド改善にすぎません。回避策はありません。目指すべきなのは、壊れないパーサーではなく、壊れても静かに空値を返さず、リニューアル後に直しやすいパーサーです。
ストリーミングパーサーが減らすのはコード量ではなく認知負荷
最初はlxmlやBeautifulSoupでページ全体のDOMツリーを作り、.find()を重ねて必要な要素をたどりたくなります。動作はしますが、この用途には向きません。DOMとは、ブラウザが文書を構造に沿ってスクリプトから扱えるノードのツリーへ解析したものです。MDNのDOMの定義にもあるとおりです。けれど必要なのは数項目の抽出であり、ツリー全体ではありません。その構造に自分を縛る必要もありません。
最終的に採用したのは、Python標準ライブラリのHTMLParserを継承した、881行のストリーミングパーサーです。starttag、data、endtagのコールバックで状態機械を動かし、HTMLを順に走査しながら値を蓄積します。広告カードの境界に到達したらレコードを出力し、状態をリセットして次へ進む。ページ全体のDOMツリーは一度も構築しません。
ストリーミング方式の利点は明確です。まずメモリです。詳細ページのHTMLは数十KBから数百KBになることがあり、DOM方式ではページ全体の構造をメモリに保持します。ストリーミングなら必要なのは「今どこを読んでいるか」「このカードをどこまで処理したか」という状態だけです。ただし、より重要なのは認知負荷の削減です。.find('div').find('div')[2]と書いた瞬間、解析はDOMの階層位置に結びつきます。そしてリニューアルで最も簡単に動かされるのが、その階層です。コンテナを1つ挟む、ラッパーを分ける、それだけですべての位置がずれます。状態機械なら問うのは「今読んでいるものは意味として広告カードの開始か、インプレッション数か、ターゲティングタグか」だけです。位置は変わっても、意味はそう簡単には変わりません。
リニューアル後も持ちこたえるための3つの設計
要点は3つです。いずれも実際のリニューアルから学んだものです。
第一に、位置ではなく意味にアンカーを置くこと。状態機械を進める条件は、フィールドのラベル文言、「total impressions」や「run dates」のように人が読めるテキスト、roleを持つマーカー、ブロックの意味的な境界にします。「上から3番目のノード」のような指定は使いません。判断はシンプルです。この要素が移動したり、さらに1階層ラップされたりしても、解析は成立するか。成立するなら有効なアンカーです。位置アンカーは最初のリニューアルで壊れますが、意味アンカーは見た目だけの変更なら大半を生き残れます。
第二に、フィールドが欠けても例外にしないこと。各カードを蓄積する前に、全フィールドを空の初期値で持つテンプレートを用意します。テキストは空文字列、数値はNone、リストは空配列です。取得できた値だけを埋め、取れないものは空のまま残します。1フィールドの抽出失敗でカード全体、ましてページ全体が失敗してはいけません。CTA文言がない広告でも、インプレッション数や対象国には価値があります。重要度の低い欠損ひとつで、そのページから得られたはずのインテリジェンスを失う設計は最悪です。
第三に、結果に完全性を付け、「空」と「故障」を同じに見せないことです。見落とされやすい一方で、最もコストの大きいポイントでもあります。「広告を0件解析した」には、まったく異なる2つの意味があります。広告主が今四半期は実際に広告を出していないのか、それともページ構造が変わり、アンカーが何も見つからずパーサーが壊れたのか。この2つは返り値の時点で区別できなければなりません。そこで、取得したカード数、ページ自身が宣言する総数、ページネーションの状態といった裏付け情報を一緒に返します。たとえば「カード数は0だが、ページメタデータにはバッチがあることが示され、次ページのマーカーもない」という組み合わせなら、正当な空結果ではなく構造変更と判断できます。空リストを平然と返すのではなく、明確なエラーにすべきです。
先ほどのデータ境界も、この層で守ります。パーサーはログインページへリダイレクトされたかを確認し、タイトルがログインまたはサインアップページであれば、その時点でエラーにします。自分のアカウントで通常閲覧できる公開ページだけを扱い、ログインウォールに到達したら停止し、突破を試みることはありません。
インテリジェンスの価値は広告フィルターの切り口にある
ここまで読むと、各広告のフィールドを漏れなく抽出することが目的に見えるかもしれません。しかし本質はそこではありません。広告1件のフィールド単体では価値が薄く、真に重要なのは広告群をどの軸で切り分けられるかです。広告ライブラリの検索フィルター自体が、そのままインテリジェンスの次元になります。これらをプログラムから扱えるクエリパラメータとして整理すれば、得られるのは「1件の広告」ではなく、「競合のローンチ施策を切り出した断面」です。
国:どの市場で広告を出していて、どの市場では出していないか。あるB2B企業が特定の国で突然広告を始めたなら、自社サイトより早く海外展開の動きを示していることがあります。
掲載期間(開始日・終了日):そのクリエイティブがどれだけ長く使われたか。長期間掲載される広告は強いシグナルです。成果につながらないクリエイティブに、誰も広告費を払い続けないからです。掲載期間そのものが、相手が実費で回し、実市場で検証したA/Bテストの結果になります。
インプレッション範囲(最小・最大):大まかな広告出稿量の代理指標です。絶対値は正確ではなくても、どれが優先度の高い出稿かを順位付けするには十分です。
ターゲティングの属性:含まれるターゲティングと除外されるターゲティング。相手が自社製品を誰に買ってほしいと考えているかを最も直接的に示す、オーディエンスインテリジェンスです。
フィールド抽出は手段であり、これらの次元が目的です。パーサーは逆算して設計します。この切り口で検索・並べ替えを支えるには、どのフィールドを最低限かつ安定して取るべきか。残りの見栄えのよいフィールドは、取れなくてもインテリジェンスの価値を損ないません。
リニューアル後の修正案は旧新HTMLの差分をモデルに読ませる
この種のパーサーは、リニューアルがあれば必ず壊れます。そこでモデルが本当に役立つのは、解析処理そのものではなく修正作業です。解析は決定論的な仕事であり、ハードコードした状態機械で行うべきです。モデル呼び出しを組み込む場所ではありません。決定論的な作業をモデルに任せないという、リバースエンジニアリングでも同じ原則です。モデルの担当は保守です。
手順はこうです。自分のアカウントで広告ライブラリを開き、リニューアル前に保存したHTMLと、リニューアル後のHTMLを用意します。それらに現在のパーサーが抽出するフィールド一覧を添え、旧新差分を基に、どのフィールドの意味アンカーが変わったか、新しいアンカーはどこか、最小限で済む修正は何行かをモデルに示させます。これは典型的な推論モデル向けの仕事です。新しい構造に対応する意味が残っているかを見つけ、実際に修正できる計画を返さなければならず、「構造が調整されました」と言い換えるだけでは足りません。工程ごとにモデルへ求める能力は異なります。全工程を1つのモデル階層に任せると、コストか精度のどちらかを失います。
工程 | 必要な能力 | 選択 | model id |
|---|---|---|---|
SSR HTMLのページ全体を読み、旧新の構造を対応付ける | 長いコンテキスト。数十KBから数百KBの詳細ページを一度に扱えること | Kimi K3 |
|
リニューアル後、旧新差分からアンカーのずれを判断し、最小修正を提案する | 強い推論力。現象の言い換えではなく構造に基づいて説明できること | Claude Opus 5 |
|
数百の広告主のカードを一括で正規化・タグ付けし、インテリジェンス化する | 低コストで、高並行に数百から数千回呼び出せること | Claude Sonnet 5 |
|
fixture比較が失敗した際の差分原因の特定 | 中程度の推論力。「期待したフィールドと実際の抽出結果」を比較して説明できること | GPT-5.6 Sol |
|
中核は2番目の階層であり、モデルを変えた効果が目に見えて出る唯一の工程です。推論モデルに価値があるかどうかは、私の言葉を信じる必要はありません。短い手順で試せます。
リニューアル前のページHTMLと後のHTMLを保存します。自分のアカウントで広告ライブラリを開き、ページを保存してください。
2つのHTMLと現在のパーサーのフィールド一覧を、
claude-opus-5とgpt-5.6-solに渡します。見るべき点は1つです。修正案が具体的な意味アンカーの変化を示しているか――たとえば「以前は'total impressions'ラベルをアンカーにしていたが、新バージョンではそのラベルのコンテナroleが変わったため、Xをアンカーに変更する」と言えるか。それとも「構造が調整されたため再適応を推奨する」と曖昧に言うだけか。
前者ならそのまま適用できますが、後者からは何も分かりません。この違いが選定基準であり、リニューアル当日に何回手探りの試行を繰り返すかを直接左右します。
1回比較すれば、ベンチマークの数字より直接的に差が分かります。
本当の障壁はモデル切り替えの手間
4つの階層は3つのベンダー、3種類のSDK、3種類の認証方式、3種類のエラー形式にまたがります。工程ごとに3つのクライアントをつなぐ必要があるため、多くの人は「たまにあるパーサー修正と大量データのクレンジングのために、そこまでやる価値はない」と判断します。結局は全工程を1モデルで回し、リニューアル修正にも「再適応を推奨する」としか言わない階層を使い続け、なぜ時間がかかるのか分からないまま時間を消費します。
AIReiterはこの層を統一します。キーは1つ、インターフェースはOpenAI互換で、4つすべての階層を利用できます。リクエストボディのmodelフィールドを変えるだけで切り替えられます。
# リニューアル修正:推論モデル階層
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<旧新HTMLの差分 + 現在のフィールド一覧を渡し、アンカーがどこでずれたかを聞く>"}]
}'
# インテリジェンスの一括正規化:modelフィールドだけを変え、他はそのまま
# "model": "claude-sonnet-5"
# ページ全体を読む長文コンテキスト:"model": "kimi-k3"
# 差分原因の特定: "model": "gpt-5.6-sol"
すでにOpenAI SDKを使っているなら、base_urlをhttps://aireiter.com/api/v1に向けるだけで、ほかは変更不要です。Anthropic SDKでは、同じキーでPOST /api/v1/messagesを呼び出します。
価格面では、Claudeモデルは定価から30%オフ、GPTモデルは半額で利用でき、Kimi K3も同じキーで呼び出せます。このフローでは、割引が最も大きいコスト部分にそのまま効きます。主なコストはリニューアル修正ではありません。これは低頻度で価値の高い呼び出しです。コストの中心はインテリジェンスの一括正規化です。競合する広告主を20社監視し、それぞれ数十から数百のカードをモデルに渡して、訴求、オーディエンス、掲載期間を読み取る。最も呼び出し密度の高い工程を、Claude Sonnetの30%オフで回せます。ページ全体を読んで構造を対応付ける長文コンテキスト入力が、その次に密度の高い部分です。最も高くつく2つの工程が、ちょうど割引対象になります。
登録なしで試す:リニューアル前後のHTMLペアを1組ずつ手作業で両モデルに渡し、実際にアンカーのずれを指摘できるのはどちらかを比較してから、組み込むか判断してください。
修正後に生のカードを一括正規化したら、このインテリジェンスは次に、判断とアセット生成のためクリエイティブパイプラインへ渡されます。それを担うのがキーワードから完成した広告までをつなぐ全体ループです。本稿が扱うのはその最初の区間、公開データを確実に取り込む部分です。
フィールドの正しさを最終判断するのはfixture
モデルが出す修正案は、検証するまでは単なる提案です。「アンカーをXに変えるべきだ」という説明はもっともらしく聞こえても、Xがすべてのカードで成立する保証はありません。B2B広告には画像とテキストの組み合わせ、テキストのみ、カルーセル、ランディングページあり・なしなど、複数の形式があります。モデルが見た2つのサンプルだけでは、すべてをカバーできない可能性があります。
ここで防波堤になるのが、リバースエンジニアリングにおけるモデルのハルシネーションを防ぐものと同じ、固定ベクトル比較です。既知の入力群、つまり保存しておいた実ページHTMLを数件と、1回人手で正しさを確認した既知の出力、つまりフィールド結果をfixtureとしてリポジトリにコミットします。自分でパーサーを変えた場合でも、モデルの提案に従った場合でも、変更のたびにfixture群を再実行し、フィールド単位で比較します。これがあれば、上流のリニューアル後に「提案の適用を誤ったのか、それともページがさらに変わったのか」をすぐに判断できます。すべて通れば変更は正しい。いくつか失敗したなら、そのケースのフィールドが、問題がどの層にあるかを直接示します。モデルは修正案を生成し、fixtureがその正しさを判定する。この役割は混ぜてはいけません。差分比較の方法全体は差分テストの記事で解説しており、広告ライブラリのパーサーにも同じゲートを適用します。この層がなければ、モデルの自信を正しさと取り違えることになります。「提案を適用し、エラーも出ないのでリリースしたら、3日後に特定の国のデータがずっと空だった」となるのは簡単です。
まとめ
B2B広告インテリジェンスはHTMLから解析するしかありません。広告ライブラリは製品APIではなくコンプライアンス対応の成果物であり、API契約もなく、いつでもリニューアルされ得るからです。壊れにくくする鍵は3つあります。位置ではなく意味にアンカーを置くこと、フィールド欠損で例外を投げずに劣化させること、「空」と「故障」を区別できるよう結果に完全性を付けることです。真に価値があるのは広告1件のフィールドではなく、競合のローンチを切り分ける国、掲載期間、インプレッション範囲、ターゲティング属性という次元です。モデルの役割も明確です。決定論的な状態機械である解析処理ではなく、保守に使います。リニューアル後に旧新HTMLを差分比較して修正案を出す場面では推論モデルが効き、カードを大量にインテリジェンス化する処理は、低コストで高並行な階層の仕事です。ただしフィールドの正しさは、常にfixtureで判定します。これらの階層を統一インターフェースにつなげれば、残る摩擦はmodelフィールドの変更だけです。それもモデルを選ぶことで解消できます。