Your competitors are posting on Instagram every day.
Some posts flop. Some go viral. They're constantly testing what works.
Why figure it out yourself when you can just... watch what works for them?
In this tutorial, we'll build an Instagram Competitor Tracker that:
- Monitors any public Instagram account
- Tracks which posts perform best
- Extracts winning content patterns with AI
Ethical espionage. Totally legal.
The Problem with Manual Competitor Research
You could check competitors' profiles manually. But:
- Time-consuming (who has 2 hours/day?)
- No historical data (you see what's there NOW)
- No engagement analysis (can't see what's actually working)
- No pattern detection (your brain misses trends)
Automation fixes all of this.
The Stack
- Node.js: Runtime
- SociaVault API: To fetch Instagram data
- OpenAI API: For content strategy analysis
- node-cron: For scheduled monitoring
Step 1: Setup
mkdir instagram-competitor-tracker
cd instagram-competitor-tracker
npm init -y
npm install axios dotenv openai node-cron
Create .env:
SOCIAVAULT_API_KEY=your_sociavault_key
OPENAI_API_KEY=your_openai_key
Step 2: Fetch Profile Data
Let's start by getting a competitor's profile info.
Create index.js:
require('dotenv').config();
const axios = require('axios');
const OpenAI = require('openai');
const cron = require('node-cron');
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const SOCIAVAULT_BASE = 'https://api.sociavault.com';
async function getProfile(username) {
console.log(`📥 Fetching profile for @${username}...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/instagram/profile`, {
params: { username },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
const profile = response.data.data;
return {
username: profile.username,
fullName: profile.full_name || profile.fullName,
bio: profile.biography || profile.bio,
followers: profile.follower_count || profile.followers,
following: profile.following_count || profile.following,
posts: profile.media_count || profile.posts,
isVerified: profile.is_verified || profile.verified,
isBusinessAccount: profile.is_business_account || profile.isBusiness,
category: profile.category_name || profile.category,
externalUrl: profile.external_url || profile.website
};
} catch (error) {
console.error('Error fetching profile:', error.message);
return null;
}
}
Step 3: Get Recent Posts
Now fetch their recent content:
async function getPosts(username, limit = 30) {
console.log(`📥 Fetching posts from @${username}...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/instagram/posts`, {
params: { username, limit },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
const posts = response.data.data || [];
return posts.map(p => ({
id: p.id || p.pk,
shortcode: p.shortcode || p.code,
type: p.media_type || p.type, // 1=image, 2=video, 8=carousel
caption: p.caption?.text || p.caption || '',
likes: p.like_count || p.likes || 0,
comments: p.comment_count || p.comments || 0,
views: p.view_count || p.views || 0, // for videos/reels
timestamp: p.taken_at || p.timestamp,
url: p.url || `https://instagram.com/p/${p.shortcode || p.code}`
}));
} catch (error) {
console.error('Error fetching posts:', error.message);
return [];
}
}
Step 4: Get Reels Separately
Reels often perform differently—let's track them:
async function getReels(username, limit = 20) {
console.log(`📥 Fetching reels from @${username}...`);
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/instagram/reels`, {
params: { username, limit },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
const reels = response.data.data || [];
return reels.map(r => ({
id: r.id || r.pk,
shortcode: r.shortcode || r.code,
caption: r.caption?.text || r.caption || '',
plays: r.play_count || r.plays || 0,
likes: r.like_count || r.likes || 0,
comments: r.comment_count || r.comments || 0,
timestamp: r.taken_at || r.timestamp,
duration: r.video_duration || r.duration
}));
} catch (error) {
console.error('Error fetching reels:', error.message);
return [];
}
}
Step 5: Analyze Post Performance
Calculate what's actually working:
function analyzePerformance(profile, posts) {
if (!posts || posts.length === 0) return null;
// Separate by type
const images = posts.filter(p => p.type === 1 || p.type === 'IMAGE');
const videos = posts.filter(p => p.type === 2 || p.type === 'VIDEO');
const carousels = posts.filter(p => p.type === 8 || p.type === 'CAROUSEL');
// Calculate engagement per type
const calculateAvg = (arr, key) =>
arr.length ? arr.reduce((sum, p) => sum + (p[key] || 0), 0) / arr.length : 0;
const avgByType = {
images: {
count: images.length,
avgLikes: Math.round(calculateAvg(images, 'likes')),
avgComments: Math.round(calculateAvg(images, 'comments'))
},
videos: {
count: videos.length,
avgLikes: Math.round(calculateAvg(videos, 'likes')),
avgComments: Math.round(calculateAvg(videos, 'comments')),
avgViews: Math.round(calculateAvg(videos, 'views'))
},
carousels: {
count: carousels.length,
avgLikes: Math.round(calculateAvg(carousels, 'likes')),
avgComments: Math.round(calculateAvg(carousels, 'comments'))
}
};
// Overall engagement rate
const totalEngagement = posts.reduce((sum, p) =>
sum + (p.likes || 0) + (p.comments || 0), 0
);
const avgEngagementRate = (totalEngagement / posts.length / profile.followers * 100);
// Find top performers
const sortedByEngagement = [...posts].sort((a, b) =>
(b.likes + b.comments) - (a.likes + a.comments)
);
// Identify posting patterns
const postsByDay = {};
posts.forEach(p => {
if (p.timestamp) {
const date = new Date(p.timestamp * 1000);
const day = date.toLocaleDateString('en-US', { weekday: 'long' });
postsByDay[day] = (postsByDay[day] || 0) + 1;
}
});
return {
overall: {
totalPosts: posts.length,
avgEngagementRate: avgEngagementRate.toFixed(2) + '%',
avgLikes: Math.round(calculateAvg(posts, 'likes')),
avgComments: Math.round(calculateAvg(posts, 'comments'))
},
byType: avgByType,
topPosts: sortedByEngagement.slice(0, 5),
worstPosts: sortedByEngagement.slice(-3),
postingPattern: postsByDay,
bestFormat: Object.entries(avgByType)
.filter(([_, v]) => v.count > 0)
.sort((a, b) => b[1].avgLikes - a[1].avgLikes)[0]?.[0] || 'unknown'
};
}
Step 6: AI Content Strategy Analysis
Extract actionable insights:
async function analyzeContentStrategy(posts, profile) {
console.log('🤖 Analyzing content strategy...');
const topPosts = [...posts]
.sort((a, b) => (b.likes + b.comments) - (a.likes + a.comments))
.slice(0, 15);
const prompt = `
Analyze this Instagram competitor's content strategy based on their top posts.
Account: @${profile.username}
Bio: ${profile.bio}
Followers: ${profile.followers}
Category: ${profile.category || 'Unknown'}
Return JSON with:
{
"contentPillars": [top 3-5 content themes they consistently post about],
"captionPatterns": {
"hooks": [5 opening hooks they use frequently],
"ctas": [common calls to action],
"avgLength": "short/medium/long",
"emojiUsage": "none/minimal/heavy",
"hashtagStrategy": "description of their hashtag approach"
},
"visualStyle": {
"aesthetic": "clean/bold/minimal/colorful/etc",
"brandConsistency": "high/medium/low",
"facesInContent": "always/sometimes/rarely"
},
"postingStrategy": {
"frequencyGuess": "posts per week",
"bestPerformingType": "images/videos/carousels/reels",
"timing": "any patterns noticed"
},
"whatWorks": [5 specific tactics that drive engagement],
"whatToSteal": [5 actionable things to copy for your own account],
"whatToAvoid": [3 things that don't seem to work for them]
}
Top Posts:
${JSON.stringify(topPosts.map(p => ({
type: p.type,
caption: p.caption?.substring(0, 200),
likes: p.likes,
comments: p.comments
})))}
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' }
});
return JSON.parse(completion.choices[0].message.content);
}
Step 7: Full Competitor Report
async function trackCompetitor(username) {
console.log('\n📸 Instagram Competitor Tracker\n');
console.log('═══════════════════════════════════════\n');
// 1. Get profile
const profile = await getProfile(username);
if (!profile) {
console.log('Could not fetch profile.');
return;
}
console.log(`👤 @${profile.username} ${profile.isVerified ? '✓' : ''}`);
console.log(` ${profile.fullName}`);
console.log(` ${profile.category || 'Personal Account'}`);
console.log(` "${profile.bio?.substring(0, 60) || 'No bio'}..."\n`);
console.log(` 📊 ${Number(profile.followers).toLocaleString()} followers`);
console.log(` 📷 ${profile.posts} posts`);
// 2. Get posts
const posts = await getPosts(username);
if (posts.length === 0) {
console.log('\nNo posts found.');
return;
}
console.log(`\n✅ Analyzed ${posts.length} recent posts\n`);
// 3. Performance analysis
const performance = analyzePerformance(profile, posts);
console.log('═══════════════════════════════════════');
console.log('📈 PERFORMANCE METRICS');
console.log('═══════════════════════════════════════\n');
console.log(`📊 Overall:`);
console.log(` Engagement Rate: ${performance.overall.avgEngagementRate}`);
console.log(` Avg Likes: ${performance.overall.avgLikes.toLocaleString()}`);
console.log(` Avg Comments: ${performance.overall.avgComments.toLocaleString()}`);
console.log(`\n📸 By Format:`);
console.log(` Images: ${performance.byType.images.count} posts, ${performance.byType.images.avgLikes.toLocaleString()} avg likes`);
console.log(` Videos: ${performance.byType.videos.count} posts, ${performance.byType.videos.avgLikes.toLocaleString()} avg likes`);
console.log(` Carousels: ${performance.byType.carousels.count} posts, ${performance.byType.carousels.avgLikes.toLocaleString()} avg likes`);
console.log(`\n🏆 Best Format: ${performance.bestFormat.toUpperCase()}`);
console.log('\n🔥 Top Performing Posts:');
performance.topPosts.slice(0, 3).forEach((p, i) => {
console.log(` ${i + 1}. ${(p.likes + p.comments).toLocaleString()} engagement`);
console.log(` "${p.caption?.substring(0, 50) || 'No caption'}..."`);
console.log(` ${p.url}`);
});
// 4. AI Strategy Analysis
const strategy = await analyzeContentStrategy(posts, profile);
console.log('\n═══════════════════════════════════════');
console.log('🎯 CONTENT STRATEGY BREAKDOWN');
console.log('═══════════════════════════════════════\n');
console.log('📌 Content Pillars:');
strategy.contentPillars.forEach((pillar, i) => {
console.log(` ${i + 1}. ${pillar}`);
});
console.log('\n✍️ Caption Patterns:');
console.log(` Length: ${strategy.captionPatterns.avgLength}`);
console.log(` Emoji Usage: ${strategy.captionPatterns.emojiUsage}`);
console.log(` Hashtags: ${strategy.captionPatterns.hashtagStrategy}`);
console.log('\n Top Hooks:');
strategy.captionPatterns.hooks.forEach(h => console.log(` • ${h}`));
console.log('\n🎨 Visual Style:');
console.log(` Aesthetic: ${strategy.visualStyle.aesthetic}`);
console.log(` Brand Consistency: ${strategy.visualStyle.brandConsistency}`);
console.log(` Faces: ${strategy.visualStyle.facesInContent}`);
console.log('\n✅ What Works for Them:');
strategy.whatWorks.forEach((w, i) => console.log(` ${i + 1}. ${w}`));
console.log('\n🎯 STEAL THESE TACTICS:');
strategy.whatToSteal.forEach((s, i) => console.log(` ${i + 1}. ${s}`));
console.log('\n⚠️ What to Avoid:');
strategy.whatToAvoid.forEach((a, i) => console.log(` ${i + 1}. ${a}`));
return { profile, posts, performance, strategy };
}
Step 8: Compare Multiple Competitors
async function compareCompetitors(usernames) {
console.log('\n🏆 COMPETITOR COMPARISON\n');
console.log('═══════════════════════════════════════\n');
const results = [];
for (const username of usernames) {
console.log(`Analyzing @${username}...`);
const profile = await getProfile(username);
const posts = await getPosts(username);
if (profile && posts.length > 0) {
const performance = analyzePerformance(profile, posts);
results.push({ username, profile, performance });
}
await new Promise(r => setTimeout(r, 1000));
}
// Sort by engagement rate
results.sort((a, b) =>
parseFloat(b.performance.overall.avgEngagementRate) -
parseFloat(a.performance.overall.avgEngagementRate)
);
console.log('\n📊 Ranked by Engagement Rate:\n');
results.forEach((r, i) => {
console.log(`${i + 1}. @${r.username}`);
console.log(` Followers: ${r.profile.followers.toLocaleString()}`);
console.log(` Engagement: ${r.performance.overall.avgEngagementRate}`);
console.log(` Avg Likes: ${r.performance.overall.avgLikes.toLocaleString()}`);
console.log(` Best Format: ${r.performance.bestFormat}\n`);
});
// Aggregate insights
console.log('═══════════════════════════════════════');
console.log('💡 INDUSTRY INSIGHTS');
console.log('═══════════════════════════════════════\n');
const avgEngagement = results.reduce((sum, r) =>
sum + parseFloat(r.performance.overall.avgEngagementRate), 0
) / results.length;
console.log(`Average Engagement Rate: ${avgEngagement.toFixed(2)}%`);
console.log(`Leader: @${results[0]?.username} (${results[0]?.performance.overall.avgEngagementRate})`);
return results;
}
Step 9: Automated Monitoring
Track changes over time:
// Store historical data
const competitorHistory = new Map();
async function monitorCompetitor(username) {
const profile = await getProfile(username);
const posts = await getPosts(username, 10); // Just recent posts
const current = {
timestamp: Date.now(),
followers: profile.followers,
avgLikes: posts.reduce((sum, p) => sum + p.likes, 0) / posts.length
};
const history = competitorHistory.get(username) || [];
history.push(current);
competitorHistory.set(username, history);
// Compare to previous
if (history.length > 1) {
const previous = history[history.length - 2];
const followerChange = current.followers - previous.followers;
const engagementChange = current.avgLikes - previous.avgLikes;
console.log(`\n📊 @${username} Update:`);
console.log(` Followers: ${followerChange >= 0 ? '+' : ''}${followerChange.toLocaleString()}`);
console.log(` Avg Likes: ${engagementChange >= 0 ? '+' : ''}${Math.round(engagementChange).toLocaleString()}`);
// Alert on significant changes
if (Math.abs(followerChange) > 10000 || Math.abs(engagementChange) > 1000) {
console.log(` ⚠️ SIGNIFICANT CHANGE DETECTED`);
}
}
}
// Monitor daily
const COMPETITORS = ['competitor1', 'competitor2', 'competitor3'];
cron.schedule('0 9 * * *', async () => {
console.log('\n📅 Daily Competitor Check\n');
for (const username of COMPETITORS) {
await monitorCompetitor(username);
await new Promise(r => setTimeout(r, 2000));
}
});
Step 10: Run It
async function main() {
// Track a single competitor
await trackCompetitor('hubspot');
// Or compare multiple
// await compareCompetitors(['hubspot', 'buffer', 'hootsuite']);
}
main();
Sample Output
📸 Instagram Competitor Tracker
═══════════════════════════════════════
👤 @hubspot ✓
HubSpot
Marketing Software
"Helping millions grow better. 🧡 CRM, marketing, sal..."
📊 748,000 followers
📷 2,847 posts
✅ Analyzed 30 recent posts
═══════════════════════════════════════
📈 PERFORMANCE METRICS
═══════════════════════════════════════
📊 Overall:
Engagement Rate: 0.89%
Avg Likes: 5,234
Avg Comments: 147
📸 By Format:
Images: 8 posts, 4,892 avg likes
Videos: 5 posts, 3,456 avg likes
Carousels: 17 posts, 6,234 avg likes
🏆 Best Format: CAROUSELS
═══════════════════════════════════════
🎯 CONTENT STRATEGY BREAKDOWN
═══════════════════════════════════════
📌 Content Pillars:
1. Marketing tips and strategies
2. Workplace culture and humor
3. Product education
4. Industry news and trends
5. Customer success stories
✍️ Caption Patterns:
Length: medium
Emoji Usage: heavy
Hashtags: 3-5 branded + niche hashtags
Top Hooks:
• "POV: [relatable work situation]"
• "Here's what most marketers get wrong..."
• "We asked 1000 marketers..."
• Question-based hooks
• "Save this for later 👇"
🎯 STEAL THESE TACTICS:
1. Use carousels for educational content - swipe-through drives saves
2. Mix humor with value - relatable memes about marketing
3. Lead with data - "X% of marketers..." hooks work
4. End every carousel with a strong CTA slide
5. Repurpose blog content into visual carousels
Why This Matters
Competitor analysis tools charge:
- Sprout Social: $249+/month
- Iconosquare: $49+/month
- Socialbakers: Custom pricing ($$$$)
You just built a custom solution that does what you need for the cost of API calls.
Plus: you can customize it for YOUR specific competitors and metrics.
Get Started
- Get your SociaVault API Key
- List your top 5 competitors
- Run the tracker
- Steal their winning tactics (legally)
Don't reinvent the wheel. Learn from what's already working.
Top comments (0)