- Profile auditor (bio quality, posting patterns, growth readiness) - Tweet composer (hooks, threads, validation, 30+ proven patterns) - Content planner (weekly calendars with format mix) - Competitor analyzer (competitive intel via data import) - Growth tracker (snapshot-based progress tracking + milestone projection) - Algorithm reference doc (ranking signals, timing, format performance) - 226-line SKILL.md with practical playbook (no fluff) - Security audit: PASS (0 findings)
236 lines
8.1 KiB
Python
236 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
X/Twitter Competitor Analyzer — Analyze competitor profiles for content strategy insights.
|
||
|
||
Takes competitor handles and available data, produces a competitive
|
||
intelligence report with content patterns, engagement strategies, and gaps.
|
||
|
||
Usage:
|
||
python3 competitor_analyzer.py --handles @user1 @user2 @user3
|
||
python3 competitor_analyzer.py --handles @user1 --followers 50000 --niche "AI"
|
||
python3 competitor_analyzer.py --import data.json
|
||
"""
|
||
|
||
import argparse
|
||
import json
|
||
import sys
|
||
from dataclasses import dataclass, field, asdict
|
||
from typing import Optional
|
||
|
||
|
||
@dataclass
|
||
class CompetitorProfile:
|
||
handle: str
|
||
followers: int = 0
|
||
following: int = 0
|
||
posts_per_week: float = 0
|
||
avg_likes: float = 0
|
||
avg_replies: float = 0
|
||
avg_retweets: float = 0
|
||
thread_frequency: str = "" # daily, weekly, rarely
|
||
top_topics: list = field(default_factory=list)
|
||
content_mix: dict = field(default_factory=dict) # format: percentage
|
||
posting_times: list = field(default_factory=list)
|
||
bio: str = ""
|
||
notes: str = ""
|
||
|
||
|
||
@dataclass
|
||
class CompetitiveInsight:
|
||
category: str
|
||
finding: str
|
||
opportunity: str
|
||
priority: str # HIGH, MEDIUM, LOW
|
||
|
||
|
||
def calculate_engagement_rate(profile: CompetitorProfile) -> float:
|
||
if profile.followers <= 0:
|
||
return 0
|
||
total_engagement = profile.avg_likes + profile.avg_replies + profile.avg_retweets
|
||
return (total_engagement / profile.followers) * 100
|
||
|
||
|
||
def analyze_competitors(competitors: list) -> list:
|
||
insights = []
|
||
|
||
# Engagement comparison
|
||
engagement_rates = []
|
||
for c in competitors:
|
||
er = calculate_engagement_rate(c)
|
||
engagement_rates.append((c.handle, er))
|
||
|
||
if engagement_rates:
|
||
top = max(engagement_rates, key=lambda x: x[1])
|
||
if top[1] > 0:
|
||
insights.append(CompetitiveInsight(
|
||
"Engagement", f"Highest engagement: {top[0]} ({top[1]:.2f}%)",
|
||
"Study their top posts — what format and topics drive replies?",
|
||
"HIGH"
|
||
))
|
||
|
||
# Posting frequency
|
||
frequencies = [(c.handle, c.posts_per_week) for c in competitors if c.posts_per_week > 0]
|
||
if frequencies:
|
||
avg_freq = sum(f for _, f in frequencies) / len(frequencies)
|
||
insights.append(CompetitiveInsight(
|
||
"Frequency", f"Average posting: {avg_freq:.0f}/week across competitors",
|
||
f"Match or exceed {avg_freq:.0f} posts/week to compete for mindshare",
|
||
"HIGH"
|
||
))
|
||
|
||
# Thread usage
|
||
thread_users = [c.handle for c in competitors if c.thread_frequency in ("daily", "weekly")]
|
||
if thread_users:
|
||
insights.append(CompetitiveInsight(
|
||
"Format", f"Active thread users: {', '.join(thread_users)}",
|
||
"Threads are a proven growth lever in your niche. Publish 2-3/week minimum.",
|
||
"HIGH"
|
||
))
|
||
|
||
# Reply engagement
|
||
reply_heavy = [(c.handle, c.avg_replies) for c in competitors if c.avg_replies > c.avg_likes * 0.3]
|
||
if reply_heavy:
|
||
names = [h for h, _ in reply_heavy]
|
||
insights.append(CompetitiveInsight(
|
||
"Community", f"High reply ratios: {', '.join(names)}",
|
||
"These accounts build community through conversation. Ask more questions in your tweets.",
|
||
"MEDIUM"
|
||
))
|
||
|
||
# Follower/following ratio
|
||
for c in competitors:
|
||
if c.followers > 0 and c.following > 0:
|
||
ratio = c.followers / c.following
|
||
if ratio > 10:
|
||
insights.append(CompetitiveInsight(
|
||
"Authority", f"{c.handle} has {ratio:.0f}x follower/following ratio",
|
||
"Strong authority signal — they attract followers without follow-backs",
|
||
"LOW"
|
||
))
|
||
|
||
# Topic gaps
|
||
all_topics = []
|
||
for c in competitors:
|
||
all_topics.extend(c.top_topics)
|
||
|
||
if all_topics:
|
||
from collections import Counter
|
||
common = Counter(all_topics).most_common(5)
|
||
insights.append(CompetitiveInsight(
|
||
"Topics", f"Most covered topics: {', '.join(t for t, _ in common)}",
|
||
"Cover these topics to compete, but find unique angles. What are they NOT covering?",
|
||
"MEDIUM"
|
||
))
|
||
|
||
return insights
|
||
|
||
|
||
def print_report(competitors: list, insights: list):
|
||
print(f"\n{'='*70}")
|
||
print(f" COMPETITIVE ANALYSIS REPORT")
|
||
print(f"{'='*70}")
|
||
|
||
# Profile summary table
|
||
print(f"\n {'Handle':<20} {'Followers':>10} {'Posts/wk':>10} {'Eng Rate':>10}")
|
||
print(f" {'─'*20} {'─'*10} {'─'*10} {'─'*10}")
|
||
for c in competitors:
|
||
er = calculate_engagement_rate(c)
|
||
print(f" {c.handle:<20} {c.followers:>10,} {c.posts_per_week:>10.0f} {er:>9.2f}%")
|
||
|
||
# Insights
|
||
if insights:
|
||
print(f"\n {'─'*66}")
|
||
print(f" KEY INSIGHTS\n")
|
||
|
||
priority_order = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
|
||
sorted_insights = sorted(insights, key=lambda x: priority_order.get(x.priority, 3))
|
||
|
||
for i in sorted_insights:
|
||
icon = {"HIGH": "🔴", "MEDIUM": "🟡", "LOW": "⚪"}.get(i.priority, "❓")
|
||
print(f" {icon} [{i.category}] {i.finding}")
|
||
print(f" → {i.opportunity}")
|
||
print()
|
||
|
||
# Action items
|
||
print(f" {'─'*66}")
|
||
print(f" NEXT STEPS\n")
|
||
print(f" 1. Search each competitor's profile on X — note their pinned tweet and bio")
|
||
print(f" 2. Read their last 20 posts — categorize by format and topic")
|
||
print(f" 3. Identify their top 3 performing posts — what made them work?")
|
||
print(f" 4. Find gaps — what topics do they NOT cover that you can own?")
|
||
print(f" 5. Set engagement targets based on their metrics as benchmarks")
|
||
print(f"\n{'='*70}\n")
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="Analyze X/Twitter competitors for content strategy insights",
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog="""
|
||
Examples:
|
||
%(prog)s --handles @user1 @user2
|
||
%(prog)s --import competitors.json
|
||
|
||
JSON format for --import:
|
||
[{"handle": "@user1", "followers": 50000, "posts_per_week": 14, ...}]
|
||
""")
|
||
|
||
parser.add_argument("--handles", nargs="+", default=[], help="Competitor handles")
|
||
parser.add_argument("--import", dest="import_file", help="Import from JSON file")
|
||
parser.add_argument("--json", action="store_true", help="Output JSON")
|
||
|
||
args = parser.parse_args()
|
||
|
||
competitors = []
|
||
|
||
if args.import_file:
|
||
with open(args.import_file) as f:
|
||
data = json.load(f)
|
||
for item in data:
|
||
competitors.append(CompetitorProfile(**item))
|
||
elif args.handles:
|
||
for handle in args.handles:
|
||
if not handle.startswith("@"):
|
||
handle = f"@{handle}"
|
||
competitors.append(CompetitorProfile(handle=handle))
|
||
|
||
if all(c.followers == 0 for c in competitors):
|
||
print(f"\n ℹ️ Handles registered: {', '.join(c.handle for c in competitors)}")
|
||
print(f" To get full analysis, provide data via JSON import:")
|
||
print(f" 1. Research each profile on X")
|
||
print(f" 2. Create a JSON file with follower counts, posting frequency, etc.")
|
||
print(f" 3. Run: {sys.argv[0]} --import data.json")
|
||
print(f"\n Example JSON:")
|
||
example = [asdict(CompetitorProfile(
|
||
handle="@example",
|
||
followers=25000,
|
||
following=1200,
|
||
posts_per_week=14,
|
||
avg_likes=150,
|
||
avg_replies=30,
|
||
avg_retweets=20,
|
||
thread_frequency="weekly",
|
||
top_topics=["AI", "startups", "engineering"],
|
||
))]
|
||
print(f" {json.dumps(example, indent=2)}")
|
||
print()
|
||
return
|
||
|
||
if not competitors:
|
||
print("Error: provide --handles or --import", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
insights = analyze_competitors(competitors)
|
||
|
||
if args.json:
|
||
print(json.dumps({
|
||
"competitors": [asdict(c) for c in competitors],
|
||
"insights": [asdict(i) for i in insights],
|
||
}, indent=2))
|
||
else:
|
||
print_report(competitors, insights)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|