競爭對手公關搜尋工具
提供您的產品網址。它能找出競爭對手,研究他們使用的每個公關管道(新聞、Podcast、社群),找出多個競爭對手共同出現的管道(也就是您已驗證的目標),為每個管道找到記者或主持人,並為所有 Tier 1 頻道撰寫個人化的冷信件草稿。
零虛構政策: 輸出中的每個管道、記者姓名、故事角度和信件細節,都必須能追溯到特定的 Tavily 搜尋結果或所擷取的產品頁面。這適用於:
- 競爭對手名稱:必須出現在 Tavily 搜尋結果中,而非 AI 訓練知識
- 管道名稱:必須在搜尋結果中擁有網址
- 記者/主持人姓名:必須一字不差地出現在 Tavily 片段中
- 故事角度:僅從搜尋結果的文章/集數標題中提取
- 信件草稿:必須引用搜尋數據 + 產品分析中的具體證據
常見錯誤
| 代理可能會想這麼做… | 為什麼那是錯的 |
|---|---|
| 從訓練知識中說出記者姓名 | 每個記者姓名都必須能追溯到搜尋結果片段。憑記憶寫出「Sarah Perez 在 TechCrunch 報導新創」就是虛構。 |
| 列出沒有證據網址的管道 | 輸出中的每個管道都必須至少有一個來自公關搜尋結果的網址,證明競爭對手曾在那裡被報導。 |
| 跳過競爭對手確認步驟 | 始終顯示發現的競爭對手,並等待使用者確認。錯誤的競爭對手 = 浪費搜尋 + 無用的輸出。 |
| 產生通用信件(「我們很樂意被報導」) | 每封信件都必須引用一個來自證據的特定角度,以及一個來自產品分析的特定差異化優勢。 |
| 將僅出現 1 次的競爭對手管道標記為 Tier 1 | Tier 1 = 3 個以上競爭對手。Tier 2 = 恰好 2 個。Tier 3 = 1 個。不要提升未經證實的管道層級。 |
| 在輸出中使用長破折號(em dash) | 將所有長破折號(---)替換為連字號(-)。 |
每次執行前先閱讀參考檔案
cat references/pr-channel-types.md
cat references/pitch-guide.md
cat references/tier-scoring.md
步驟 1:設定檢查
echo "TAVILY_API_KEY: ${TAVILY_API_KEY:+set}${TAVILY_API_KEY:-NOT SET -- required}"
echo "FIRECRAWL_API_KEY: ${FIRECRAWL_API_KEY:+set}${FIRECRAWL_API_KEY:-not set, Tavily extract will be used as fallback}"
如果缺少 TAVILY_API_KEY: 立即停止。告訴使用者:「TAVILY_API_KEY 是研究競爭對手和尋找公關報導的必要條件。沒有替代方案。請前往 app.tavily.com 取得——免費方案:每月 1000 個積分(約 43 次完整執行,每次約 23 次搜尋)。將其加入您的 .env 檔案。」
如果僅缺少 FIRECRAWL_API_KEY: 繼續執行。將使用 Tavily extract 進行網址擷取。
步驟 2:解析輸入
從對話中收集:
product_url:要擷取的網址(必要,除非使用者直接貼上描述)product_name:選填,若未提供則從頁面推導geography:選填——美國 / 歐洲 / 全球。預設值:美國
如果使用者只提供貼上的描述(沒有網址): 跳過步驟 3 和 4。直接使用貼上的文字作為 product_content 前往步驟 4(產品分析)。將 page_source 設為 user_description,並在 data_quality_flags 中註記。
如果沒有網址也沒有描述: 詢問:「您的產品或新創公司的網址為何?或貼上簡短描述:它的功能、目標對象,以及與競爭對手的差異。」
推導產品代稱:
PRODUCT_SLUG=$(python3 -c "
from urllib.parse import urlparse
import sys
url = 'URL_HERE'
if url.startswith('http'):
host = urlparse(url).netloc.replace('www.', '')
print(host.split('.')[0])
else:
import re
print(re.sub(r'[^a-z0-9]', '-', url[:30].lower()).strip('-'))
")
echo "Product slug: $PRODUCT_SLUG"
步驟 3:擷取產品頁面
主要方式:Firecrawl(如果 FIRECRAWL_API_KEY 已設定)
curl -s -X POST https://api.firecrawl.dev/v1/scrape \
-H "Authorization: Bearer $FIRECRAWL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "URL_HERE", "formats": ["markdown"], "onlyMainContent": true}' \
| python3 -c "
import sys, json
d = json.load(sys.stdin)
content = d.get('data', {}).get('markdown', '') or d.get('markdown', '')
print(f'Fetched via Firecrawl: {len(content)} characters')
open('/tmp/cprf-product-raw.md', 'w').write(content)
"
備援方式:Tavily extract(如果 FIRECRAWL_API_KEY 未設定)
curl -s -X POST https://api.tavily.com/extract \
-H "Content-Type: application/json" \
-d "{\"api_key\": \"$TAVILY_API_KEY\", \"urls\": [\"URL_HERE\"]}" \
| python3 -c "
import sys, json
d = json.load(sys.stdin)
content = d.get('results', [{}])[0].get('raw_content', '')
print(f'Fetched via Tavily extract: {len(content)} characters')
open('/tmp/cprf-product-raw.md', 'w').write(content)
"
檢查點:
python3 -c "
content = open('/tmp/cprf-product-raw.md').read()
if len(content) < 200:
print('ERROR: fewer than 200 characters fetched')
else:
print(f'Content OK: {len(content)} characters')
"
如果內容少於 200 個字元: 停止擷取。告訴使用者:「產品頁面未回傳可讀內容——該網站可能使用 JavaScript 渲染並被封鎖。請直接貼上簡短描述:它的功能、目標對象,以及與競爭對手的差異。」
步驟 4:產品分析(AI)
列印頁面內容:
python3 -c "
content = open('/tmp/cprf-product-raw.md').read()[:5000]
print('=== PRODUCT PAGE (first 5000 chars) ===')
print(content)
"
AI 指令: 分析上述產品頁面並提取:
product_name:產品或公司名稱one_line_description:它的功能、目標對象、核心價值主張。少於 20 字。不要使用行銷用語。範例:「適用於自行託管管線之開發團隊的 CI/CD 自動化工具。」industry_taxonomy:l1(頂層:例如 developer tools / fintech / healthtech / consumer)、l2(領域:例如 devops / payments / telemedicine)、l3(具體利基:例如 CI/CD automation / embedded payments / async video consultation)。僅使用「technology」這類模糊標籤是不可接受的。differentiators:正好 2-3 個具體事項,使本產品與一般競爭對手有所區別。這些將直接導入信件草稿——請具體說明。範例:["Self-hosted pipeline runner -- no data leaves your infra", "Native support for monorepos with dynamic step generation"]icp:buyer_persona(職稱)、company_type、company_sizegeography_bias:美國 / 歐洲 / 全球 / 不清楚page_source:"live_page" 或 "user_description"
寫入 /tmp/cprf-product-analysis.json:
python3 << 'PYEOF'
import json
analysis = {
# 從上方分析填寫
"product_name": "",
"one_line_description": "",
"industry_taxonomy": {"l1": "", "l2": "", "l3": ""},
"differentiators": [],
"icp": {"buyer_persona": "", "company_type": "", "company_size": ""},
"geography_bias": "US",
"page_source": "live_page"
}
json.dump(analysis, open('/tmp/cprf-product-analysis.json', 'w'), indent=2)
print('Product analysis written.')
PYEOF
驗證:
python3 -c "
import json
a = json.load(open('/tmp/cprf-product-analysis.json'))
print('Product:', a['product_name'])
print('Industry:', a['industry_taxonomy']['l1'], '>', a['industry_taxonomy']['l2'], '>', a['industry_taxonomy']['l3'])
print('Differentiators:')
for d in a['differentiators']:
print(f' - {d}')
"
步驟 4b:第一階段 -- 競爭對手發現
ls scripts/research.py 2>/dev/null && echo "script found" || echo "ERROR: scripts/research.py not found -- cannot continue"
python3 scripts/research.py \
--phase discover \
--product-analysis /tmp/cprf-product-analysis.json \
--tavily-key "$TAVILY_API_KEY" \
--output /tmp/cprf-competitors-raw.json
列印結果供 AI 審閱:
python3 -c "
import json
data = json.load(open('/tmp/cprf-competitors-raw.json'))
print(f'Searches run: {len(data[\"competitor_searches\"])}')
for s in data['competitor_searches']:
print(f'\nQuery: {s[\"query\"]}')
print(f'Answer: {s.get(\"answer\",\"\")[:400]}')
for r in s.get('results', [])[:5]:
print(f' - {r[\"title\"]} | {r[\"url\"]}')
print(f' {r.get(\"content\",\"\")[:200]}')
"
AI 指令: 閱讀上述搜尋結果。挑選恰好 5 家競爭對手公司,需符合:
- 在搜尋結果的標題、答案或片段中出現
- 與被分析產品屬於相同的 L3 利基
- 是實際的競爭產品(而非代理商、顧問公司或列表文章)
- 彼此不同(不是同一家公司使用不同名稱)
為每個競爭對手撰寫:name、url(來自其出現的搜尋結果)、description(來自片段的一句話)、source_url(找到它們的搜尋結果網址)
步驟 5:競爭對手確認
向使用者顯示發現的競爭對手:
python3 << 'PYEOF'
import json
analysis = json.load(open('/tmp/cprf-product-analysis.json'))
# 填寫:從上方搜尋結果中找出 5 個競爭對手
candidates = [
# {"name": str, "url": str, "description": str, "source_url": str}
]
print(f"\nFound 5 competitors for {analysis['product_name']} in {analysis['industry_taxonomy']['l3']}:\n")
for i, c in enumerate(candidates, 1):
print(f" {i}. {c['name']} -- {c['description']}")
print(f" {c['url']}")
data = json.load(open('/tmp/cprf-competitors-raw.json'))
data['competitor_candidates'] = candidates
json.dump(data, open('/tmp/cprf-competitors-raw.json', 'w'), indent=2)
PYEOF
告訴使用者:「這是我將進行公關報導研究的 5 個競爭對手。您可以新增、移除或替換任何一個——或者說『看起來不錯』來繼續。」
等待確認。 如果使用者編輯清單(新增/移除/替換),請相應更新候選名單。然後寫入已確認的清單:
python3 << 'PYEOF'
import json
# 填寫:已確認的競爭對手清單(使用者審閱後)
confirmed = [
# {"name": str, "url": str}
]
json.dump({"confirmed_competitors": confirmed}, open('/tmp/cprf-competitors-confirmed.json', 'w'), indent=2)
print(f"Confirmed {len(confirmed)} competitors for PR research.")
for c in confirmed:
print(f" - {c['name']} ({c['url']})")
PYEOF
步驟 6:三軌公關研究(第二階段)
python3 scripts/research.py \
--phase pr-research \
--competitors /tmp/cprf-competitors-confirmed.json \
--product-analysis /tmp/cprf-product-analysis.json \
--tavily-key "$TAVILY_API_KEY" \
--output /tmp/cprf-pr-raw.json
這會對每個競爭對手執行 3 次搜尋(總計 15 次):
- 軌道 A(編輯):
"[competitor]" featured press coverage TechCrunch Forbes Wired article interview - 軌道 B(Podcast):
"[competitor]" founder CEO podcast interview appeared on episode - 軌道 C(社群):
"[competitor]" site:reddit.com OR site:news.ycombinator.com OR site:producthunt.com
列印報導摘要:
python3 -c "
import json
data = json.load(open('/tmp/cprf-pr-raw.json'))
print(f'Competitors researched: {data[\"competitors_researched\"]}')
print()
for r in data['results']:
print(f'{r[\"competitor\"]}:')
for track, tdata in r['tracks'].items():
n = len(tdata.get('results', []))
print(f' {track:12}: {n} results')
"
如果某個競爭對手的全部 3 個軌道都回傳 0 個結果: 此競爭對手的媒體曝光率極低。在 data_quality_flags 中註記並繼續——跨競爭對手模式分析仍可使用其餘 4 個競爭對手。
步驟 7:模式分析(AI)
列印所有原始公關結果:
python3 -c "
import json
data = json.load(open('/tmp/cprf-pr-raw.json'))
for r in data['results']:
print(f'\n=== {r[\"competitor\"]} ===')
for track, tdata in r['tracks'].items():
print(f'\n--- Track {track.upper()} ---')
print(f'Query: {tdata[\"query\"]}')
print(f'Answer: {tdata.get(\"answer\",\"\")[:400]}')
for item in tdata.get('results', [])[:5]:
print(f' Title: {item[\"title\"]}')
print(f' URL: {item[\"url\"]}')
print(f' Snippet: {item.get(\"content\",\"\")[:200]}')
"
AI 指令: 閱讀上述所有搜尋結果。建立頻道頻率地圖。
步驟 1 -- 將網址正規化為根網域: https://techcrunch.com/2023/06/article-title → techcrunch.com。https://open.spotify.com/episode/... → 識別為 Podcast(spotify 集數)。https://www.reddit.com/r/devops/ → reddit.com/r/devops。
步驟 2 -- 計算出現次數: 有多少不同的競爭對手出現在每個頻道根的結果中?一個同時出現在競爭對手 A 的軌道 A 和競爭對手 B 的軌道 A 的頻道,其頻率計為 2。
步驟 3 -- 頻道分級(遵循 references/tier-scoring.md):
- Tier 1:出現在 3 個以上競爭對手
- Tier 2:恰好出現在 2 個競爭對手
- Tier 3:出現在 1 個競爭對手
步驟 4 -- 提取故事角度來自結果中的文章/集數標題。將每個分類為:funding-announcement / product-launch / founder-story / trend-piece / category-creation / how-to / comparison / award。不要推測——僅分類在標題中可見的角度。
步驟 5 -- 分類每個頻道的類型: editorial / podcast / community / newsletter。
寫入 /tmp/cprf-pr-patterns.json:
python3 << 'PYEOF'
import json
patterns = {
"tier_1_channels": [
# 填寫 -- 出現在 3 個以上競爭對手的頻道
# 每個:{"channel_name": str, "channel_url": str, "channel_type": str,
# "frequency": int, "found_in_competitors": [str],
# "evidence_urls": [str], "story_angles_used": [str],
# "journalist_name": "", "journalist_beat": ""}
],
"tier_2_channels": [
# 填寫 -- 恰好出現在 2 個競爭對手的頻道
# 每個:{"channel_name": str, "channel_url": str, "channel_type": str,
…
[內容因長度限制而截斷]
---
## 步驟 8:識別聯絡人與角度
**AI 指令:** 對於所有 Tier 1 頻道和選定的 Tier 2 頻道,查找負責該頻道的記者或 Podcast 主持人,以及最適合產品的故事角度。使用以下搜尋:
```bash
python3 scripts/research.py \
--phase identify-contacts \
--channels /tmp/cprf-pr-patterns.json \
--tavily-key "$TAVILY_API_KEY" \
--output /tmp/cprf-contacts-raw.json
這會對每個 Tier 1 頻道執行類似 "TechCrunch" reporter editor journalist 或 "Podcast Name" host 的查詢。
AI 指令: 審閱結果並為每個頻道提取:
- 記者的全名(必須出現在 Tavily 片段中)
- 記者關注的領域(來自片段)
- 最相關的故事角度(來自現有競爭對手報導標題)
- 與產品差異化優勢的契合度(高/中/低)
將這些寫入 /tmp/cprf-outreach-plan.json:
python3 << 'PYEOF'
import json
# 載入現有數據
patterns = json.load(open('/tmp/cprf-pr-patterns.json'))
analysis = json.load(open('/tmp/cprf-product-analysis.json'))
outreach_plan = {
"product": analysis['product_name'],
"product_differentiators": analysis['differentiators'],
"tiers": {
"tier_1": [],
"tier_2": []
}
}
# 從 patterns.json 中的 Tier 1 頻道和聯絡人數據填寫
# 每個項目:{
# "channel_name": str,
# "channel_url": str,
# "channel_type": str,
# "journalist_name": str,
# "journalist_email": "", # 選填
# "journalist_beat": str,
# "story_angle": str,
# "angle_classification": str,
# "evidence_competitor": str,
# "evidence_url": str,
# "product_fit": "high" | "medium" | "low",
# "pitch_draft": str
# }
json.dump(outreach_plan, open('/tmp/cprf-outreach-plan.json', 'w'), indent=2)
print('Outreach plan written.')
PYEOF
最終輸出格式
生成一則最終訊息,包含:
摘要
- 產品名稱、L3 利基
- 發現的競爭對手數量、研究的公關管道
- 頻道分級摘要
頻道分級
Tier 1:高優先級
| 頻道 | 類型 | 出現頻率 | 記者/主持人 | 故事角度 | 產品契合度 |
|---|---|---|---|---|---|
| TechCrunch | 編輯 | 4/5 競爭對手 | 記者姓名 | 產品發布 | 高 |
Tier 2:中優先級
...
Tier 3:低優先級
...(僅列出,無信件)
準備發送的冷信件草稿
致:[記者姓名],[頻道名稱]
[根據角度和產品差異化優勢撰寫的 60-80 字個性化信件]
致:[記者姓名],[頻道名稱]
[信件]
...
數據品質標記
| 旗標 | 詳細資訊 |
|---|---|
| 無競爭對手報導 | 以下競爭對手的公關報導為零:[名稱] |
| 使用描述而非 URL | 產品來自使用者貼上的描述,而非直播頁面 |
| 近似匹配 | [Competitor A] 的網域可能為 [competitorA.com] 而非 [competitorA.io] |
參考資料
頻道類型
- 編輯: TechCrunch、Forbes、Wired、VentureBeat、The Verge、CNBC、Bloomberg、Business Insider、Fast Company、Inc.
- Podcast: Spotify、Apple Podcasts、YouTube Podcasts、特定技術/商業 Podcast
- 社群: Reddit(特定子版)、Hacker News、Product Hunt、Dev.to、Indie Hackers
- 新聞通訊: Substack、特定技術/商業新聞通訊