Claude Media
ClaudeでUTMパラメータの命名規則を決め、URL台帳の表記ゆれを洗う

ClaudeでUTMパラメータの命名規則を決め、URL台帳の表記ゆれを洗う

案件ごとに散らばったUTM付きURLを台帳にまとめ、大小文字と区切りの揺れをスクリプトで検出する手順。命名規則の決め方と、Claudeに任せる範囲を分けて示します。

UTMパラメータの表記ゆれは、台帳に並べて機械的に突き合わせると数分で洗い出せます。直し方の判断だけを人が持ち、検出はスクリプトとClaudeに任せる分担が手堅い進め方です。

ここでは、広告代理店のように案件をまたいでURLを管理している場面を想定します。命名規則を6行で決め、CSVの台帳を検査するPythonスクリプトを用意し、Claudeに出力を読ませて正規形の案を作らせる流れです。GA4のイベント名やパラメータの設計はGA4の計測設計書をClaudeで作る手順が扱っており、ここはURL側の規則に絞ります。

表記ゆれは、レポートで別の行になる

GA4のUTM解説には、値の大小文字が区別されると明記されています。utm_source=google と utm_source=Google は別の値です。同じ解説は、Meta と meta が別の値になること、SpringSale と Spring_Sale も別物として扱われることを例に挙げています。

つまり、担当者が違うだけで「同じ春のセール」が複数の行に割れます。

割れたときの影響は、集計の見づらさだけではありません。同じ解説は、UTMがデフォルトチャネルグループとカスタムチャネルグループの分類にも効くと書いています。paid-social と paid_social が混在すれば、片方だけが想定のチャネルに入らない事態が起こります。

もうひとつ、値が欠けた場合の挙動があります。UTMを1つ設定するなら関連するものを全部設定するよう推奨されており、欠けた項目はレポート上で (not set) になります。ここも台帳で検出できる対象です。

検出対象

台帳で見つけたい3種類のずれ

  • 大小文字のずれ

    Facebook と facebook のように、綴りは同じで大小文字だけが違う値です。

  • 区切りのずれ

    paid_social / paid-social / paidsocial のように、区切り文字だけが違う値です。

  • 欠落

    utm_source・utm_medium・utm_campaign のどれかが付いていないURLです。

命名規則は6行で足りる

規則は短いほど守られます。GA4の解説にあるベストプラクティスを土台にして、台帳検査に落とし込める形へ整えると、次の6行になります。値の形式(小文字のスネークケース)と、媒体の語彙を閉じた一覧にする点は、解説の「小文字を標準にする」「チャネルごとに一意の値を使う」を運用ルールに言い換えた私案です。

項目規則例
文字種規則小文字の英数字とアンダースコアだけ例spring_sale
utm_source規則プラットフォーム1つにつき1値例facebook / instagram / newsletter
utm_medium規則チャネルごとに決めた一覧から選ぶ例cpc / paid_social / email
utm_campaign規則広告媒体側の名称と同じ綴り例spring_sale
utm_content規則クリエイティブ単位で一意例top_link
必須規則source・medium・campaignの3つ例欠けたら台帳に入れない

GA4の解説は utm_medium の例として paid_social と organic_search を、utm_source の例としてFacebookとInstagramを別々の値にする運用を挙げています。区切りをアンダースコアに揃えるのは、解説の例が paid_social や spring_sale と書かれているのに合わせた選択です。ハイフンに揃えても機能の面では差がありません。大事なのは案件をまたいで1つに決めることです。

utm_medium を閉じた一覧にすると、台帳検査が一段強くなります。たとえば cpc paid_social email referral organic_social の5つだけ許す、と決めれば、それ以外の値は自動で警告になります。一覧の中身は自社の運用に合わせて決めてください。

旧版のUniversal Analytics向け解説にも、同じ趣旨の記述が残っています。値の大小文字は区別され、パラメータは任意の順序で並べてよく、utm_ は必須の接頭辞だという内容です。ただしこれはUA時代の説明です。GA4向けの解説では、これに加えて utm_id や utm_source_platform などのパラメータが増えています。

台帳はCSVで足りる

台帳の列は3つあれば検査できます。案件の識別子、URLの識別番号、URLそのものです。

id,client,url
1,acme,https://www.example.com/lp?utm_source=Facebook&utm_medium=paid_social&utm_campaign=spring_sale
2,acme,https://www.example.com/lp?utm_source=facebook&utm_medium=paid-social&utm_campaign=spring_sale
3,acme,https://www.example.com/lp?utm_source=facebook&utm_medium=paidsocial&utm_campaign=SpringSale

実運用ではスプレッドシートから書き出したCSVが入口になります。URLが列に収まっていればよく、担当者や出稿日の列が増えても検査には影響しません。

検査スクリプトは判定を3種類に分ける

ここから先を人が手で目視すると、見落としが出ます。検出は決定論的に書けるので、スクリプトに任せます。判定は次の3種類です。

  • FORMAT: 値が小文字のスネークケースになっていない
  • MISSING: 必須の3パラメータが欠けている
  • VARIANT: 区切りと大小文字を無視すると同じ値になるのに、綴りが複数ある

VARIANT が肝心です。FORMAT だけでは paidsocial を捕まえられません。小文字で区切りもなく、形式としては合格するからです。値から区切りと大小文字を落として束ね、その束に綴りが2つ以上あれば揺れと判定します。

import csv, re, sys
from collections import defaultdict
from urllib.parse import urlparse, parse_qs
 
REQUIRED = ["utm_source", "utm_medium", "utm_campaign"]
SNAKE = re.compile(r"^[a-z0-9]+(_[a-z0-9]+)*$")
 
def fold(v):
    return re.sub(r"[-_\s]|%20", "", v.lower())
 
rows = list(csv.DictReader(open(sys.argv[1], encoding="utf-8")))
seen = defaultdict(lambda: defaultdict(set))
problems = []
for r in rows:
    q = parse_qs(urlparse(r["url"]).query, keep_blank_values=True)
    for p in REQUIRED:
        if p not in q:
            problems.append((r["id"], "MISSING", p))
    for p, vals in q.items():
        if not p.startswith("utm_"):
            continue
        v = vals[0]
        seen[p][fold(v)].add(v)
        if not SNAKE.match(v):
            problems.append((r["id"], "FORMAT", f"{p}={v}"))
 
for p, groups in seen.items():
    for key, raws in groups.items():
        if len(raws) > 1:
            problems.append(("-", "VARIANT", f"{p}: {sorted(raws)}"))
 
for pid, kind, detail in problems:
    print(f"{kind:8} id={pid:<2} {detail}")
print(f"-- {len(rows)} urls, {len(problems)} findings")

parse_qs はパーセントエンコードを戻すので、summer%20fair は空白入りの summer fair として FORMAT に引っかかります。UTMの値に空白を残すと、URLの見た目が崩れる原因にもなります。

8本のサンプルで走らせた出力

上のCSVに8本のURLを入れ、Python 3.12.2で実行した出力が次のとおりです。8本には、大小文字違いの Facebook、区切り違いの paid-social と paidsocial、綴り違いの SpringSale、campaign 欠落、%20 入りの値、toplink と top_link の食い違いをわざと混ぜてあります。

python3 -I utm_audit.py url_ledger.csv
FORMAT   id=1  utm_source=Facebook
FORMAT   id=2  utm_medium=paid-social
FORMAT   id=3  utm_campaign=SpringSale
FORMAT   id=4  utm_campaign=spring-sale
FORMAT   id=5  utm_medium=Email
MISSING  id=6  utm_campaign
FORMAT   id=7  utm_campaign=summer fair
VARIANT  id=-  utm_source: ['Facebook', 'facebook']
VARIANT  id=-  utm_medium: ['paid-social', 'paid_social', 'paidsocial']
VARIANT  id=-  utm_medium: ['Email', 'email']
VARIANT  id=-  utm_campaign: ['SpringSale', 'spring-sale', 'spring_sale']
VARIANT  id=-  utm_campaign: ['summer fair', 'summer_fair']
VARIANT  id=-  utm_content: ['top_link', 'toplink']
-- 8 urls, 13 findings

読み方のコツは、FORMAT を個別の修正リスト、VARIANT を「どれを正にするか」の決定リストとして分けることです。FORMAT は規則に照らせば直し方が一意に決まります。VARIANT は、utm_medium の paid-social / paid_social / paidsocial のどれを残すかを人が選ぶ問題です。

ここで paidsocial が FORMAT を素通りして VARIANT でだけ見つかる点に注目してください。形式の検査だけで台帳を締めると、この型が残ります。

Claudeに任せるのは、出力の読み取りと正規形の案

検出が終わったら、出力をClaudeに読ませて案を作らせます。Claude Codeはパイプ入力を受けて非対話で動かせるので、検査結果をそのまま渡せます。概要ページにも、ログをパイプで渡して claude -p に指示する例が載っています。

python3 -I utm_audit.py url_ledger.csv \
  | claude -p "VARIANTの各行について、残す綴りを1つ提案して。
理由は、規則表にある文字種と媒体の一覧に合うかで書く。
FORMATとMISSINGは別表にして、修正後の値を併記する"

非対話モードの権限や --bare の扱いは、claude -pオプションの解説にまとめています。

依頼文のポイントは2つあります。ひとつは、残す綴りの根拠を規則表に求めさせることです。「なんとなく多いほう」と答えさせると、台帳に偏りがあるだけで規則が曲がります。もうひとつは、MISSING の値をClaudeに埋めさせないことです。欠けた utm_campaign に入るべき名称は、広告媒体側の管理画面を見ないと分かりません。推測で埋めた値は、一見それらしいので台帳に紛れ込みます。

規則表そのものは、プロジェクトの CLAUDE.md に置いておくと毎回の依頼文を短くできます。

## UTM命名規則
- 値は小文字の英数字とアンダースコアだけ(例: spring_sale)
- utm_medium は cpc / paid_social / email / referral / organic_social のみ
- utm_source・utm_medium・utm_campaign は必須。欠けたら補完せず報告する
- 検査は python3 -I utm_audit.py <台帳.csv> で行い、出力の
  FORMAT / MISSING / VARIANT を別表で報告する

最後の1行が効きます。報告の形を決めておくと、担当者が変わっても出力の読み方がそろいます。

utm_idとutm_source_platformは、台帳の列に足すと照合が楽になる

GA4向けの解説には、旧版の5パラメータに加えて utm_id(キャンペーンID)と utm_source_platform が載っています。utm_id は、キャンペーンデータをアップロードするときと同じIDを使うよう案内されています。広告管理画面のキャンペーンIDを入れておけば、名称の綴りが揺れても、IDで広告側の行と突き合わせられます。

台帳にこの2項目の列を足す場合は、検査の必須リストにも加えます。解説は、utm_source・utm_medium・utm_campaign・utm_id・utm_source_platform を揃えて設定することを強く推奨しています。必須にする範囲は運用の重さと相談し、まずは3つで始めて、IDの運用が回り出してから広げる進め方でも構いません。

台帳を直すと、レポートの系列も分かれる

揺れを直す作業には、1つ注意点があります。値を変えれば、GA4の側では別の値として集計されます。大小文字を別物と数える仕様から導かれる当然の帰結です。

すでに配信中のURLを書き換えると、書き換えた日を境にレポートの行が切り替わります。過去分と現在分を同じ行で比較したい案件では、配信中のURLは触らず、次の出稿から新しい規則で作るのが無難です。台帳には「旧表記」の列を足し、集計時に対応表で束ねる運用も考えられます。

規則の適用範囲を決めるときは、既存URLと新規URLを分けて書きます。

対象台帳での扱い
未配信のURL台帳での扱い規則に合わせて直す
配信中のURL台帳での扱い触らず、旧表記の列に記録する
次回以降の新規URL台帳での扱い規則表から作る

新規のURLは、台帳に入れる前に検査する

事後に洗うより、入口で止めるほうが安く済みます。台帳へ行を足す変更をレビューに通す前に、同じスクリプトを走らせる形です。Gitで台帳を管理しているなら、プルリクエストの検査として組み込めます。

検査が落ちたときの出力は、そのままClaudeに渡して修正案を作らせられます。ただし、直した値を採用するのは担当者です。広告媒体側の名称と食い違う値は、検査を通っても現場で困ります。

URLを手で組む代わりに、GA4が案内しているURLビルダーを使う方法もあります。解説は、ウェブサイトのリンクにはCampaign URL Builder、Androidアプリの広告にはPlay Campaign URL Builderを使うよう案内しています。ビルダーを使っても、入力する値の綴りは人が決めるので、台帳検査は引き続き効きます。

検査しても見えないもの

GA4の解説には、Landing page + query string と Page path + query string のディメンションからUTMが外される、という注記があります。UTMの情報は Page location ディメンションに入ります。「ランディングページ別」のレポートでUTMが見当たらなくても、規則の不備とは限りません。

また、utm_creative_format と utm_marketing_tactic は、GA4のプロパティではまだレポートに出ない項目だと解説にあります。規則表に載せても、集計で使えるのは別の項目です。

URLの外にも揺れはあります。広告管理画面のキャンペーン名とUTMの utm_campaign が食い違う、といった問題は台帳のCSVからは見えません。GA4のレポートでキャンペーン別の成果を見るときは、台帳と管理画面の名称を突き合わせる作業を別に用意します。成果の集計と予算の見直しは、キャンペーンの効果測定と予算再配分の手順に流れを置いてあります。

台帳検査でClaudeに頼まないこと

重複の判定を、Claudeの文章読解だけに頼らないことです。台帳が数百行を超えると、「似た綴りを見つける」作業は見落としが出ます。ここまでの検査で示したとおり、束ねる処理は決定論的に書けます。Claudeには、その結果の解釈と、規則表への当てはめを担当させます。

もうひとつは、規則表を書き換えさせないことです。検査に引っかかる値が多いと、規則のほうを緩めて辻褄を合わせたくなります。規則の変更は運用の合意事項なので、人が決めます。依頼文にも「規則表は変更しない」と書き添えておくと、提案の範囲が台帳の修正に収まります。

この記事を共有:XはてブLinkedIn