DEV Community

Cover image for Build an Instagram Competitor Tracker to Steal Their Strategy
Olamide Olaniyan
Olamide Olaniyan

Posted on

Build an Instagram Competitor Tracker to Steal Their Strategy

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:

  1. Monitors any public Instagram account
  2. Tracks which posts perform best
  3. 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
Enter fullscreen mode Exit fullscreen mode

Create .env:

SOCIAVAULT_API_KEY=your_sociavault_key
OPENAI_API_KEY=your_openai_key
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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 [];
  }
}
Enter fullscreen mode Exit fullscreen mode

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 [];
  }
}
Enter fullscreen mode Exit fullscreen mode

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'
  };
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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));
  }
});
Enter fullscreen mode Exit fullscreen mode

Step 10: Run It

async function main() {
  // Track a single competitor
  await trackCompetitor('hubspot');

  // Or compare multiple
  // await compareCompetitors(['hubspot', 'buffer', 'hootsuite']);
}

main();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  1. Get your SociaVault API Key
  2. List your top 5 competitors
  3. Run the tracker
  4. Steal their winning tactics (legally)

Don't reinvent the wheel. Learn from what's already working.


Top comments (0)