市場をマッピングする
製品説明、カテゴリキーワード、または競合名を入力として受け取ります。Reddit、HN、GitHub Issue、G2、Google Trendsを検索し、実際のペインシグナルを収集します。それらをスコアリングし、クラスタリングします。完全なポジショニングフレームワークを構築します。ICP定義、実際の引用文を含むランキングペインテーマ、市場規模シグナル、そして実際のユーザー言語から導き出されたメッセージングアングルを提供します。
重要なルール: 出力内のすべてのペイン引用は、スクリプトによって収集された生データにそのまま存在しなければなりません。マーケットマップ内のすべてのベンダー名は、G2スクレイピング結果またはGitHub検索結果から取得したものでなければなりません。市場規模については「シグナルは示唆している」という表現を使用し、薄っぺらい代理指標から金額を推定してはいけません。ソースが0件を返した場合は、0と報告し、架空の例で補わないでください。
よくある間違い
| エージェントがやりたがること | なぜ間違っているか |
|---|---|
| ペインポイントや市場規模の数値をでっち上げる | すべてのペイン引用は生データからそのまま引用する必要があります。市場規模は見つかったシグナルを引用しなければなりません。「典型的な」市場規模を決して推定しないでください。 |
| 投稿数ではなくpain_scoreでスコアリングする | 価格に関する2000アップボートの投稿は、10アップボートの投稿50個よりも強いです。references/pain-scoring.mdのpain_score計算式を使用してください。 |
| すべてのカテゴリで同じサブレディットを使用する | r/politicsはdevops検索にノイズを追加します。カテゴリと競合名から関連するサブレディットを自動検出してから検索してください。 |
| スコアリングせずにすべての生シグナルをAIに送信する | まずローカルでスコアリングし、上位60件の高ペインスコアシグナルのみをAIクラスタリングに送信します。トークンを節約し、クラスタ品質を向上させます。 |
| 投稿メタデータからICP抽出をスキップする | サブレディット、フレア、著者バイオ(HN)、GitHub Organizationタイプは、投稿内容よりも豊かなICPシグナルです。常にそれらを取得して報告してください。 |
| ベンダー数と市場規模を混同する | 「G2に47社のベンダー」は競争が激しいことを意味しますが、市場が大きいとは限りません。すべてのシグナルを方向性を示す指標として提示し、厳密な数値としては提示しないでください。 |
ステップ1: セットアップ確認
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-未設定 — GitHub Issues検索は認証なしで60 req/hrで実行されます}"
echo "他のAPIキーは必要ありません。"
echo ""
echo "今回の実行で使用するデータソース:"
echo " Reddit公開JSON (認証不要、10 req/min)"
echo " HN Algolia API (認証不要、無料)"
echo " GitHub Issues API (${GITHUB_TOKEN:+認証済み、}60-5000 req/hr)"
echo " G2カテゴリスクレイピング (認証不要、HTML解析)"
echo " Google Trends (認証不要、非公式エンドポイント)"
GITHUB_TOKENが設定されていない場合:続行してください。認証なしのGitHub検索は60 req/hrで、標準的な実行には十分です。繰り返し使用する場合は、github.com/settings/tokensでトークンを追加してください(公開リポジトリにはスコープは不要です)。
ステップ2: 入力の解析
会話から以下の情報を収集します:
category— 市場スペースを説明するキーワード(例:「developer observability」、「B2B analytics」、「devops tooling」)competitors— オプション:競合製品名またはドメインのリスト(例:「Datadog, New Relic, Grafana」)product_context— オプション:ユーザーの製品が何をするか(メッセージングアングルの調整に役立ちます)
ユーザーがカテゴリキーワードなしで製品説明のみを提供した場合:自分で2〜3のカテゴリキーワードを抽出します。
ユーザーがカテゴリなしで競合名のみを提供した場合:競合名を調べてカテゴリを推測します。
解析した入力を書き込みます:
python3 << 'PYEOF'
import json, os
data = {
"category": "ここにカテゴリ",
"competitors": ["競合1", "競合2"],
"product_context": "ここに製品コンテキスト"
}
with open("/tmp/mym-input.json", "w") as f:
json.dump(data, f, indent=2)
print("入力は /tmp/mym-input.json に書き込まれました")
print(f"カテゴリ: {data['category']}")
print(f"競合: {', '.join(data['competitors']) if data['competitors'] else '指定なし'}")
PYEOF
ステップ3: スタンドアロンデータ収集スクリプトの実行
スクリプトがすべてのデータ収集を処理します。最初に存在するか確認します:
ls scripts/fetch.py 2>/dev/null && echo "スクリプトが見つかりました" || echo "見つかりません"
利用可能な場合、実行します:
GITHUB_TOKEN="${GITHUB_TOKEN:-}" python3 scripts/fetch.py \
"$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(d['category'])")" \
--competitors "$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(','.join(d['competitors']))")" \
--context "$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(d['product_context'])")" \
--output /tmp/mym-raw.json
完了するまで待機します(最大4分 — Reddit + HN検索で約90秒かかります)。
出力を確認します:
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
print(f'Redditシグナル: {d[\"market_signals\"][\"reddit_signals_found\"]}')
print(f'HNシグナル: {d[\"market_signals\"][\"hn_signals_found\"]}')
print(f'GitHubシグナル: {d[\"market_signals\"][\"github_issue_signals\"]}')
print(f'G2ベンダー数: {d[\"market_signals\"][\"vendor_count_g2\"]}')
print(f'トレンド: {d[\"market_signals\"][\"trends_direction\"]}')
print(f'合計シグナル数: {d[\"summary\"][\"total_pain_signals\"]}')
"
合計シグナル数が10未満の場合:停止します。ユーザーに伝えます:「このカテゴリについて10未満のペインシグナルしか見つかりませんでした。この市場はReddit/HNのカバレッジに対してニッチすぎるか、カテゴリキーワードを調整する必要があります。より広いキーワードを試すか、競合名を追加してください。」
ステップ4: AIペインクラスタリング
AI分析のための上位60のペインシグナルを表示します:
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
top60 = sorted(d['raw_pains'], key=lambda x: x['pain_score'], reverse=True)[:60]
print(json.dumps(top60, indent=2))
"
これで上位60のペインシグナルが得られました。それらを分析し、ペインクラスタを生成します。
AI分析の指示:
- すべてのソースにわたって5〜7の繰り返しペインテーマを特定する
- 各テーマに対して:市場自身の言語(自分の言葉ではなく)を使用した名前を選ぶ
- 各クラスタ内のすべてのシグナルのpain_scoreを集計する
- 各テーマに対して最高の生の引用文を3〜5つ選択する(最高スコア、最も具体的な言語)
- 各テーマがどのソースとサブレディットに集中しているかを記録する
- 1つのソースにしか現れないテーマにはフラグを立てる(信頼性低)
クラスタを /tmp/mym-clusters.json に書き込みます:
{
"clusters": [
{
"theme": "データからの正確な言語",
"total_score": 847,
"signal_count": 34,
"sources": {"reddit": 18, "hn": 12, "github_issue": 4},
"top_subreddits": ["devops", "sysadmin"],
"verbatim_quotes": [
{"text": "正確な引用", "source": "reddit", "score": 234, "url": "..."},
{"text": "正確な引用", "source": "hn", "score": 87, "url": "..."}
],
"who_has_this_pain": "これについて投稿している人の説明"
}
]
}
python3 -c "
import json, os
# クラスタファイルが書き込まれたことを確認
with open('/tmp/mym-clusters.json') as f:
d = json.load(f)
print(f'クラスタ書き込み完了: {len(d[\"clusters\"])}')
for c in d['clusters']:
print(f' {c[\"theme\"]} -- スコア: {c[\"total_score\"]}, シグナル数: {c[\"signal_count\"]}')
"
ステップ5: ICPプロファイリング
生データからICPシグナルを表示します:
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
print('ICPシグナル:')
print(json.dumps(d['icp_signals'], indent=2))
print()
print('サブレディット分布:')
sub_counts = {}
for p in d['raw_pains']:
s = p.get('subreddit', '')
if s:
sub_counts[s] = sub_counts.get(s, 0) + 1
for sub, count in sorted(sub_counts.items(), key=lambda x: -x[1])[:10]:
print(f' r/{sub}: {count} シグナル')
"
上記のICPシグナルとサブレディット分布を使用して、ICPプロファイルを合成します。/tmp/mym-clusters.json に icp キーを追加して書き込みます:
{
"icp": {
"who_they_are": "データの言語を使用した2〜3文のプロファイル",
"where_they_live": ["r/devops (89投稿)", "r/sysadmin (67投稿)", "HN ask-hn (34スレッド)"],
"what_they_say": ["逐語引用1", "逐語引用2", "逐語引用3"],
"what_they_have_tried": ["データで言及された代替ツールやアプローチ"],
"confidence": "high|medium|low — シグナル量とソースの多様性に基づく"
}
}
ステップ6: 市場規模の合成
市場シグナルを表示します:
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
ms = d['market_signals']
print('市場シグナル:')
print(f' G2ベンダー数: {ms[\"vendor_count_g2\"]}')
print(f' トレンド方向: {ms[\"trends_direction\"]}')
print(f' HNシグナル (12ヶ月): {ms[\"hn_signals_found\"]}')
print(f' Redditシグナル: {ms[\"reddit_signals_found\"]}')
print(f' G2トップベンダー: {json.dumps(ms.get(\"top_vendors\", []), indent=4)}')
"
これらのシグナルのみを使用して、方向性のある市場規模評価を合成します。金額を推定しないでください。以下のような表現を使用します:
- 「シグナルは競争が激しく成長している市場を示唆しています」(多くのベンダー + トレンド上昇)
- 「シグナルは初期の市場を示唆しています」(ベンダーが少ない + シグナル量が少ない)
- 「シグナルは飽和した市場を示唆しています」(多くのベンダー + トレンド横ばい/下降)
評価を /tmp/mym-clusters.json に market_size キーとして追加します。
ステップ7: ポジショニングフレームワーク
クラスタ(ステップ4)、ICP(ステップ5)、市場規模(ステップ6)を使用して、ポジショニングフレームワークを生成します。
指示:
- 上位3つのペインクラスタを主要なポジショニングアングルとして選択する
- 各アングルに対して:データからの逐語的な言語を使用して1つのポジショニングステートメントを書く(言い換えではない)
- ペインデータで人々が使用する正確なフレーズを使用して3つのランディングページ見出しを生成する
- ペイン言語に基づいて3つのコールドメール件名を生成する
- 禁止ワードを使用しない:powerful、robust、seamless、innovative、game-changing、streamline、leverage、transform、revolutionize
完全なポジショニングフレームワークを /tmp/mym-output.json に書き込みます:
{
"positioning_angles": [
{
"pain": "テーマ名",
"statement": "市場の言語を使用した一行ポジショニング",
"headline": "逐語的なペイン言語を使用したランディングページ見出し",
"cold_email_subject": "件名"
}
],
"icp_card": {
"one_liner": "一文:彼らが誰か + 彼らが気にしていること",
"where_to_find_them": [...],
"how_to_talk_to_them": "データからのトーン + 語彙に関するメモ"
},
"market_map": [...ポジショニングメモ付きのG2からのトップベンダー...]
}
ステップ8: 自己QAと出力の保存
自己QAチェックを実行します:
python3 -c "
import json
# すべての出力を読み込む
with open('/tmp/mym-raw.json') as f:
raw = json.load(f)
with open('/tmp/mym-clusters.json') as f:
clusters = json.load(f)
with open('/tmp/mym-output.json') as f:
output = json.load(f)
raw_texts = set()
for p in raw['raw_pains']:
raw_texts.add(p.get('title', ''))
raw_texts.add(p.get('body_excerpt', ''))
# チェック1: 全角ダッシュなし(英語のem dash)
import json as j
full_text = j.dumps(output)
if '—' in full_text:
print('FAIL: 全角ダッシュ(em dash)が出力に見つかりました')
else:
print('PASS: 全角ダッシュなし')
# チェック2: 禁止ワードなし
banned = ['powerful', 'robust', 'seamless', 'innovative', 'game-changing',
'streamline', 'leverage', 'transform', 'revolutionize']
found = [w for w in banned if w.lower() in full_text.lower()]
if found:
print(f'FAIL: 禁止ワードが見つかりました: {found}')
else:
print('PASS: 禁止ワードなし')
# チェック3: 市場規模の言語チェック
if 'billion' in full_text.lower() or 'trillion' in full_text.lower() or 'worth \$' in full_text.lower():
print('FAIL: ハードな市場規模推定が見つかりました — 方向性のみの言語を使用してください')
else:
print('PASS: ハードな市場規模推定なし')
# チェック4: シグナル数が一致する
.total = raw['summary']['total_pain_signals']
print(f'PASS: 生データに {total} のペインシグナル')
print('自己QA完了。')
"
失敗があれば、保存する前に修正します。
最終レポートを保存します:
python3 << 'PYEOF'
import json, re
from datetime import datetime
with open('/tmp/mym-input.json') as f:
inp = json.load(f)
with open('/tmp/mym-raw.json') as f:
raw = json.load(f)
with open('/tmp/mym-clusters.json') as f:
clusters = json.load(f)
with open('/tmp/mym-output.json') as f:
output = json.load(f)
slug = re.sub(r'[^a-z0-9]+', '-', inp['category'].lower()).strip('-')
date = datetime.now().strftime('%Y-%m-%d')
outpath_md = f"docs/market-maps/{slug}-{date}.md"
outpath_json = f"docs/market-maps/{slug}-{date}.json"
# マークダウンレポートを構築
ms = raw['market_signals']
icp = clusters.get('icp', {})
market_assessment = clusters.get('market_size', {})
angles = output.get('positioning_angles', [])
icp_card = output.get('icp_card', {})
market_map = output.get('market_map', [])
lines = [
f"# マーケットマップ: {inp['category'].title()}",
f"日付: {date} | 分析されたシグナル数: {raw['summary']['total_pain_signals']} | ソース: Reddit ({ms['reddit_signals_found']}) + HN ({ms['hn_signals_found']}) + GitHub Issues ({ms['github_issue_signals']})",
"",
"---",
"",
"## 市場規模シグナル",
f"G2のベンダー数: {ms['vendor_count_g2']} | Google Trends: {ms['trends_direction'].upper()} | 市場ステージ: {market_assessment.get('stage', '以下のシグナルを参照')}",
"",
market_assessment.get('summary', ''),
"",
"---",
"",
"## あなたのICP",
"",
f"**彼らは誰か:** {icp.get('who_they_are', '')}",
"",
f"**彼らがいる場所:** {', '.join(icp.get('where_they_live', []))}",
"",
"**彼らが言うこと:**",
]
for q in icp.get('what_they_say', []):
lines.append(f'> "{q}"')
lines += ["", "---", "", "## トップペイン(シグナル強度でランク付け)", ""]
for i, c in enumerate(clusters.get('clusters', []), 1):
lines.append(f"### ペイン {i}: {c['theme']} [スコア: {c['total_score']}]")
sources = c.get('sources', {})
source_str = " + ".join(f"{src} ({cnt})" for src, cnt in sources.items())
lines.append(f"{c['signal_count']} シグナル | ソース: {source_str}")
lines.append(f"このペインを持つ人: {c.get('who_has_this_pain', '')}")
lines.append("")
lines.append"逐語引用:")
for q in c.get('verbatim_quotes', [])[:4]:
lines.append(f"> 「{q['text']}」 — *{q['source']}*")
lines.append("")
lines += ["---", "", "## ポジショニングアングル", ""]
for i, angle in enumerate(angles, 1):
lines.append(f"### アングル {i}: {angle['pain']}")
lines.append(f"**ポジショニングステートメント:** {angle['statement']}")
lines.append(f"**ランディングページ見出し:** {angle['headline']}")
lines.append(f"**コールドメール件名:** {angle['cold_email_subject']}")
lines.append("")
lines += ["---", "", "## ICPカード", ""]
lines.append(f"**一言で:** {icp_card.get('one_liner', '')}")
lines.append(f"**見つけられる場所:** {', '.join(icp_card.get('where_to_find_them', []))}")
lines.append(f"**どう話すか:** {icp_card.get('how_to_talk_to_them', '')}")
lines += ["", "---", "", "## 競合マップ", ""]
for v in market_map:
lines.append(f"- **{v.get('name', '')}** — {v.get('posicioning_note', '')}")
report_md = '\n'.join(lines)
with open(outpath_md, 'w') as f:
f.write(report_md)
with open(outpath_json, 'w') as f:
json.dump(output, f, indent=2)
print(f"レポート書き込み完了: {outpath_md}")
print(f"JSON書き込み完了: {outpath_json}")
PYEOF