Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Production-quality Twitter/X scraping skill for discovering SaaS opportunities. Navigate Twitter programmatically to identify unaddressed user frustrations, high-intent signals, and emerging market trends. Uses TwitterAPI.io for fast, cost-effective data extraction with simple API key authentication. Provides tactical navigation (user discovery, hashtag analysis), advanced filtering (intent patterns, sentiment scoring, velocity tracking), and idea synthesis (trend persistence, cross-pollination
.claude/skills/valtterimelkko-scraping-twitter/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 262% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 125% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 168% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 262% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 749% | 0% |
Production-quality Twitter/X scraping toolkit for discovering unaddressed user frustrations and emerging market trends for software development.
This skill provides a comprehensive framework for Twitter-based SaaS opportunity discovery through three distinct layers:
Powered by TwitterAPI.io - no Twitter developer account required, simple API key authentication, and cost-effective pricing at ~$0.15 per 1,000 tweets.
The skill uses the hierarchical credential loading strategy from saas-idea-finder:
~/.bashrc (fallback)Environment variables:
bashexport TWITTERAPI_KEY="your_twitterapi_key"
Optional vault/secret store setup:
bashvault kv put secret/skills-apis/twitterapi/api_key value="your_api_key"
| User Type | Rate Limit | Cost | |-----------|------------|------| | Free Trial | 1 req / 5 seconds | $0.10 free credit | | Paid | 20+ QPS | $0.15 per 1,000 tweets | | Enterprise | 1000+ QPS | Custom pricing |
pythonimport asyncio from scripts import TwitterScraper, OpportunityAnalyzer async def main(): # Initialize scraper (auto-loads credentials) scraper = TwitterScraper() # Search tweets tweets, pagination = await scraper.search_tweets("#buildinpublic", limit=50) # Analyze for opportunities analyzer = OpportunityAnalyzer() for tweet in tweets: analysis = analyzer.analyze_tweet(tweet) if analysis["intent_signals"]: print(f"💡 Opportunity: {tweet.text[:100]}...") print(f" Problem: {analysis['problem_statement']}") await scraper.close() asyncio.run(main())
pythonfrom scripts import TwitterScraper, IdeaSynthesizer async def main(): scraper = TwitterScraper() synthesizer = IdeaSynthesizer(scraper) # Comprehensive analysis across hashtags report = await synthesizer.analyze_opportunity( hashtags=["buildinpublic", "indiehacker", "SaaS"], keywords=["automation", "AI", "workflow"], known_competitors=["Zapier", "Notion", "Airtable"], min_opportunity_score=60 ) print(f"Found {report['summary']['high_opportunity_count']} high-opportunity signals") print(f"Average score: {report['summary']['average_opportunity_score']}") for opp in report['opportunities'][:5]: signal = opp['opportunity'] print(f"\n🎯 {signal['opportunity_score']}/100 - {signal['priority'].upper()}") print(f" Problem: {signal['problem_statement'][:100]}...") print(f" Action: {signal['recommended_action']}") await scraper.close()
pythonfrom scripts import TwitterScraper, TwitterNavigator scraper = TwitterScraper() navigator = TwitterNavigator(scraper) # Find influencers in your target niche users = await navigator.find_relevant_users( niche_keyword="ecommerce", limit=10, min_followers=5000 ) for user in users: print(f"@{user['user'].username}: {user['user'].followers:,} followers") print(f" Relevance: {user['relevance_score']:.1f}") print(f" Influence: {user['influence_score']:.1f}")
python# Discover relevant hashtags and their metrics hashtags = await navigator.get_relevant_hashtags( niche_keyword="productivity", limit=10 ) for hashtag in hashtags: print(f"#{hashtag.hashtag}:") print(f" Tweets: {hashtag.tweet_count}") print(f" Unique authors: {hashtag.unique_authors}") print(f" Velocity: {hashtag.velocity} tweets/hour") print(f" Related: {', '.join(hashtag.related_hashtags[:5])}")
python# Find "power users" who spark high-engagement conversations contributors = await navigator.identify_top_contributors( hashtag="buildinpublic", limit=15, activity_lookback=100 ) for c in contributors[:5]: print(f"@{c['username']}: {c['tweet_count']} tweets") print(f" Avg engagement: {c['avg_engagement_per_tweet']}") print(f" Quality score: {c['quality_score']}")
python# Deep dive into a conversation thread thread = await navigator.explore_conversation_thread( tweet_id="1234567890", max_depth=3 ) print(f"Thread tweets: {len(thread.tweets)}") print(f"Total replies: {len(thread.replies)}") print(f"Unique authors: {thread.unique_authors}") print(f"Total engagement: {thread.total_engagement}")
pythonfrom scripts.filters import IntentFilter, filter_high_intent_tweets # Filter for high-intent phrases intent_filter = IntentFilter() filtered_tweets = intent_filter.filter(tweets) # Or use the convenience function high_intent = filter_high_intent_tweets(tweets, min_confidence=0.3) for tweet in high_intent: matches = tweet.metadata.get("intent_matches", []) print(f"🎯 {tweet.text[:80]}...") print(f" Signals: {', '.join(matches)}")
pythonfrom scripts.filters import NegativeSearchFilter, find_churn_signals # Find people quitting products (ready-to-churn users) churn_signals = find_churn_signals( tweets, target_products=["Salesforce", "HubSpot"] # Optional: focus on specific products ) for signal in churn_signals: print(f"⚠️ {signal['churn_type'].upper()}: {signal['tweet'].text[:80]}...") if signal['product']: print(f" Product: {signal['product']}") print(f" Context: {signal['context'][:150]}...")
pythonfrom scripts.filters import VelocityFilter, find_controversial_tweets # Find tweets with high engagement velocity velocity_filter = VelocityFilter( min_likes_per_hour=1.0, min_replies_per_hour=0.1, min_engagement_ratio=0.05 ) hot_tweets = velocity_filter.filter_by_velocity(tweets) # High reply-to-like ratio = controversial/painful controversial = find_controversial_tweets(tweets, min_ratio=0.3) for tweet, ratio in controversial[:5]: print(f"🔥 {ratio:.2f} ratio: {tweet.text[:80]}...") print(f" {tweet.reply_count} replies / {tweet.engagement} likes")
pythonfrom scripts.filters import CompetitorFilter # Track mentions of known competitors competitor_filter = CompetitorFilter( known_competitors=["Salesforce", "HubSpot", "Zapier"] ) mentions = competitor_filter.extract_mentions(tweets) for brand, mentions_list in mentions.items(): print(f"📊 {brand}: {len(mentions_list)} mentions") for m in mentions_list[:3]: print(f" {m['mention_type']} - {m['intent']}") # Find feature gaps gaps = competitor_filter.find_competitor_gaps(tweets) for gap in gaps[:5]: print(f"🕳️ Gap: {gap['gap_description']}") print(f" Confidence: {gap['confidence']}")
pythonfrom scripts.filters import HashtagFilter, filter_by_hashtags # Filter tweets by specific hashtags hashtag_filter = HashtagFilter(["buildinpublic", "indiehacker"]) filtered = hashtag_filter.filter(tweets) # Or use the convenience function filtered = filter_by_hashtags(tweets, ["saas", "b2b"]) # Analyze hashtag usage analysis = hashtag_filter.analyze_hashtags(tweets) for hashtag, stats in analysis.items(): print(f"#{hashtag}: {stats['tweet_count']} tweets, " f"{stats['unique_authors']} authors, " f"avg {stats['avg_engagement']:.1f} engagement")
pythonfrom scripts import TrendAnalyzer trend_analyzer = TrendAnalyzer(scraper) # Check if a problem is a "fad" or "chronic pain point" report = await trend_analyzer.check_trend_persistence( keyword="inventory management", hashtags=["ecommerce", "shopify"], days=30 ) print(f"Keyword: {report.keyword}") print(f"Is persistent: {report.is_persistent}") print(f"7-day mentions: {report.mention_count_7d}") print(f"30-day mentions: {report.mention_count_30d}") print(f"Growth rate: {report.growth_rate:+.1f}%") print(f"Prediction: {report.prediction}")
pythonfrom scripts import CrossPollinationAnalyzer cross_analyzer = CrossPollinationAnalyzer(scraper) # Find similar problems across different niches report = await cross_analyzer.find_cross_pollination( hashtag_a="EtsySeller", hashtag_b="AmazonFBA" ) print(f"Similarity score: {report['similarity_score']:.2f}") print(f"Opportunity type: {report['opportunity_type']}") print(f"Shared keywords: {', '.join(report['shared_keywords'][:10])}") # Pain points in both communities for pain in report['pain_points_a'][:5]: print(f" A: {pain}") for pain in report['pain_points_b'][:5]: print(f" B: {pain}")
pythonfrom scripts import IdeaSynthesizer synthesizer = IdeaSynthesizer(scraper) # Full synthesis pipeline report = await synthesizer.analyze_opportunity( hashtags=["buildinpublic", "indiehacker", "SaaS"], keywords=["automation", "AI", "workflow"], known_competitors=["Zapier", "Make", "n8n"], min_opportunity_score=65 ) summary = report['summary'] print(f"Priority distribution: {summary['priority_distribution']}") print(f"Avg opportunity score: {summary['average_opportunity_score']}") print(f"Avg frustration score: {summary['average_frustration_score']}") print("\nTop suggested features:") for feature, count in summary['top_suggested_features'][:5]: print(f" • {feature} ({count} mentions)") print(f"\nRecommendation: {summary['recommendation']}")
python@dataclass class ScrapedTweet: id: str platform: str = "twitter" text: str = "" url: str = "" author_username: str = "" author_name: str = "" author_verified: bool = False author_followers: int = 0 engagement: int = 0 # Like count reply_count: int = 0 retweet_count: int = 0 quote_count: int = 0 view_count: Optional[int] = None created_at: str = "" language: str = "en" metadata: Dict[str, Any] = field(default_factory=dict) # Computed fields pain_score: float = 0.0 opportunity_score: float = 0.0 is_high_opportunity: bool = False @property def total_engagement(self) -> int: """Calculate total engagement across all metrics.""" return self.engagement + self.reply_count + self.retweet_count + self.quote_count
python@dataclass class OpportunitySignal: opportunity_score: float # 0-100 priority: str # high, medium, low, monitor intent_score: float frustration_score: float velocity_score: float trend_score: float monetization_score: float problem_statement: str target_audience: str suggested_features: List[str] tech_stack_hints: List[str] competitor_gaps: List[str] recommended_action: str
bash# All tests python3 tests/run_tests.py # Or directly python3 tests/test_scraper.py
Tests validate:
Don't just search for what people want. Search for "I'm quitting Product]":
python# This finds ready-to-churn users and tells you what NOT to build churn_signals = find_churn_signals(tweets, target_products=["CompetitorX"]) for signal in churn_signals: print(f"Why they're leaving: {signal['context']}") # Build the OPPOSITE of these complaints
Tweets with high replies relative to likes often indicate:
pythoncontroversial = find_controversial_tweets(tweets, min_ratio=0.3) # These are goldmines for SaaS opportunities
If the same problem exists in #EtsySeller AND #AmazonFBA:
pythonreport = await cross_analyzer.find_cross_pollination("EtsySeller", "AmazonFBA") if report['similarity_score'] > 0.3: print("Cross-niche opportunity detected!")
TwitterAPI.io supports Twitter's advanced search operators:
python# Search by user tweets, _ = await scraper.search_tweets("from:elonmusk", limit=50) # Search by mention tweets, _ = await scraper.search_tweets("@twitter", limit=50) # Search with minimum engagement tweets, _ = await scraper.search_tweets("python min_retweets:10", limit=50) # Exclude replies tweets, _ = await scraper.search_tweets("saas -filter:replies", limit=50) # Date range tweets, _ = await scraper.search_tweets("startup since:2025-01-01", limit=50) # Language filter tweets, _ = await scraper.search_tweets("programming lang:en", limit=50)
The scraper implements multiple resilience patterns:
Set up your credentials:
bash# Environment variable export TWITTERAPI_KEY="your_api_key" # Or vault vault kv put secret/skills-apis/twitterapi/api_key value="your_api_key"
The scraper automatically handles 429s with exponential backoff. To reduce frequency:
_min_delay in scraper)If you get empty results:
skills-global/scraping-twitter/
├── scripts/
│ ├── twitter_scraper.py # Core scraper with TwitterAPI.io
│ ├── analysis.py # Pain point & opportunity analysis
│ ├── navigation.py # User/hashtag discovery
│ ├── synthesis.py # Trend & cross-pollination analysis
│ ├── filters.py # Semantic filtering
│ ├── credentials.py # Credential loading (saas-idea-finder pattern)
│ └── utils.py # Dataclasses & utilities
├── tests/
│ ├── test_scraper.py # Scraper tests
│ └── run_tests.py # Test runner
└── SKILL.md # This filesaas-idea-finder patternThis skill follows the same patterns and robustness standards as saas-idea-finder and scraping-reddit.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | fail→pass | 8,017 | 2,989 | -63% | 1 | 1 | 0% | 1,376 | 4,979 | +262% | 0 | 0 | — |
case-01 | fail→fail | 10,734 | 6,860 | -36% | 1 | 1 | 0% | 1,724 | 4,750 | +176% | 0 | 0 | — |
case-02 | pass→pass | 7,407 | 2,500 | -66% | 1 | 1 | 0% | 568 | 4,750 | +736% | 0 | 0 | — |
case-03 | pass→pass | 3,405 | 4,029 | +18% | 1 | 1 | 0% | 550 | 4,881 | +787% | 0 | 0 | — |
case-04 | pass→pass | 2,572 | 4,625 | +80% | 1 | 1 | 0% | 445 | 5,246 | +1079% | 0 | 0 | — |
case-05 | pass→pass | 8,958 | 8,424 | -6% | 1 | 1 | 0% | 1,407 | 5,791 | +312% | 0 | 0 | — |
case-06 | fail→pass | 12,893 | 6,632 | -49% | 1 | 1 | 0% | 2,527 | 5,686 | +125% | 0 | 0 | — |
case-08 | pass→pass | 6,554 | 2,474 | -62% | 1 | 1 | 0% | 1,187 | 4,826 | +307% | 0 | 0 | — |
case-09 | pass→pass | 5,518 | 3,982 | -28% | 1 | 1 | 0% | 911 | 4,709 | +417% | 0 | 0 | — |
case-10 | pass→pass | 2,498 | 2,518 | +1% | 1 | 1 | 0% | 424 | 4,768 | +1025% | 0 | 0 | — |
case-11 | pass→pass | 8,503 | 1,403 | -83% | 1 | 1 | 0% | 1,370 | 4,556 | +233% | 0 | 0 | — |
case-12 | pass→pass | 7,441 | 2,201 | -70% | 1 | 1 | 0% | 1,505 | 4,741 | +215% | 0 | 0 | — |
case-13 | pass→pass | 4,538 | 4,729 | +4% | 1 | 1 | 0% | 832 | 5,251 | +531% | 0 | 0 | — |
case-14 | pass→pass | 3,108 | 2,695 | -13% | 1 | 1 | 0% | 496 | 4,835 | +875% | 0 | 0 | — |
case-15 | fail→pass | 11,018 | 3,275 | -70% | 1 | 1 | 0% | 1,836 | 4,923 | +168% | 0 | 0 | — |
case-16 | fail→pass | 7,989 | 3,564 | -55% | 1 | 1 | 0% | 1,393 | 5,041 | +262% | 0 | 0 | — |
case-17 | pass→pass | 5,597 | 4,797 | -14% | 1 | 1 | 0% | 926 | 5,220 | +464% | 0 | 0 | — |
case-18 | pass→pass | 4,989 | 1,618 | -68% | 1 | 1 | 0% | 819 | 4,597 | +461% | 0 | 0 | — |
case-19 | fail→pass | 3,557 | 1,744 | -51% | 1 | 1 | 0% | 546 | 4,635 | +749% | 0 | 0 | — |
case-20 | pass→pass | 5,247 | 4,229 | -19% | 1 | 1 | 0% | 855 | 4,925 | +476% | 0 | 0 | — |
case-21 | pass→pass | 4,238 | 4,612 | +9% | 1 | 1 | 0% | 520 | 4,977 | +857% | 0 | 0 | — |
case-22 | pass→pass | 3,203 | 3,307 | +3% | 1 | 1 | 0% | 525 | 4,947 | +842% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +23 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.