Disclosure: I built the Instagram Scraper used in this guide and earn from its usage on Apify.
If you run social for a brand, the questions about competitors are always the same: how often do they post, which posts land, are reels beating photos, and is their engagement going up or down? The official Instagram API will not answer that for accounts you don't manage. It works with Business and Creator accounts you connect yourself, and its view of other accounts is limited (I wrote up the details in Instagram API in 2026).
This guide pulls the public data instead and turns it into those numbers with a few lines of Python. It uses the Instagram Scraper Actor on Apify. You need a free Apify account. You don't need an Instagram account, a login, cookies or an API key.
Step 1: Pull a profile's recent posts
Open the Actor, add one or more usernames under Profiles, and set:
- What to get from profiles:
posts(the feed),reels(the Reels tab) orboth - Add a profile row per username: on, so you get the follower count
- Max posts per input item: how many recent posts per profile
The same run from code:
from apify_client import ApifyClient client = ApifyClient("YOUR_APIFY_TOKEN") run = client.actor("apisamurai/instagram-scraper").call(run_input={ "profiles": ["glossier"], "contentType": "posts", "includeProfile": True, "maxItemsPerSource": 12, }) rows = list(client.dataset(run["defaultDatasetId"]).iterate_items()) Each row has a type: post, reel, comment or profile. A post row looks like this (shortened):
{ "type": "reel", "url": "https://www.instagram.com/reel/...", "caption": "...", "postedAt": "2026-11-01T...", "likes": 6583, "commentsCount": 165, "views": 314374, "owner": { "username": "glossier", "verified": true } } views is filled for reels and null for photos. Missing values are null, not 0, so an average never gets dragged down by fake zeros.
Step 2: Work out the numbers
Engagement rate here is (likes + comments) divided by followers, per post. It is the simplest version and good enough to compare accounts of different sizes.
import statistics from collections import Counter from datetime import datetime profile = next(r for r in rows if r["type"] == "profile") posts = [r for r in rows if r["type"] in ("post", "reel")] followers = profile["followers"] def engagement(p): return ((p["likes"] or 0) + (p["commentsCount"] or 0)) / followers * 100 print(f"@{profile['username']}: {followers:,} followers, {len(posts)} recent posts") for p in sorted(posts, key=engagement, reverse=True)[:3]: print(f"{p['postedAt'][:10]} {p['type']:5} {engagement(p):.2f}% {p['url']}") rates = [engagement(p) for p in posts] print(f"Median engagement: {statistics.median(rates):.2f}%") print("Mix:", dict(Counter(p["type"] for p in posts))) reels = [p for p in posts if p["type"] == "reel" and p["views"]] print(f"Median reel views: {statistics.median(p['views'] for p in reels):,.0f}") dates = sorted(datetime.fromisoformat(p["postedAt"].replace("Z", "+00:00")) for p in posts) days = (dates[-1] - dates[0]).days or 1 print(f"Posts per week: {len(posts) / days * 7:.1f}") What it printed for Glossier's 12 most recent posts on 6 November 2026:
@glossier: 3,123,183 followers, 12 recent posts 2026-10-29 post 0.47% https://www.instagram.com/p/DfFQiN7kYeQ/ 2026-11-02 post 0.45% https://www.instagram.com/p/DfQADIKm3-b/ 2026-10-31 post 0.43% https://www.instagram.com/p/DfKqlddm0Fv/ Median engagement: 0.15% Mix: {'reel': 4, 'post': 8} Median reel views: 124,716 Posts per week: 6.5 Two things worth noticing in this sample: the three best posts by engagement were photo posts, not reels, and the best ones sit at about three times the median. Those are the posts to open and study: the caption, the format and the day it went out.
A median works better than an average here, because one viral post would pull the average far above what a normal post gets.
Step 3: Compare several competitors
Put several usernames in profiles and group the rows by owner.username (or input). The same code then gives you one line per account: followers, median engagement, posts per week and the reels share. For a fair comparison, use the same number of recent posts for every account, or the same date window with Posted on or after.
On the free Apify plan a run returns up to 100 results, so for five accounts use about 15 posts each. Paid plans have no per-run limit.
Step 4: Run it every week
- In Apify Console, save your input as a task and add a schedule, for example every Monday at 9:00.
- Set Posted on or after to a recent date if you only want new posts. The run stops paging once the feed is older than that date, so it also costs less.
- Send the rows where you work: Make, Zapier or n8n can push each run into Google Sheets or Slack, or call the API from your own script and keep a history table. Week-over-week median engagement is the trend line that tells you whether a competitor's content is working better or worse.
If you want what people actually say, turn on Get comments with a small Max comments per post (comments are billed separately).
What it costs
The Actor charges per row, the same on every Apify plan, with no start fee:
| What | Price per 1,000 |
|---|---|
| Posts or reels | $1.50 |
| Profiles | $6.50 |
| Comments | $1.00 |
| Posts fetched by link | $6.50 |
At these prices the run above (12 posts and one profile row) comes to about 2.5 cents. Five competitors with 30 posts each, once a week, is about 26 cents a week. Private or missing profiles cost nothing, and duplicates are never charged.
Limits worth knowing
- Only public data. Private accounts return nothing (a profile row can still show the public bio and counts).
- If a like or comment count is missing, it comes back as
null; the code above counts it as 0, so check those rows before drawing conclusions. - Reels search by keyword returns at most 11 result pages per keyword.
- Use the data responsibly: follow Instagram's terms and the privacy laws that apply to you, especially if you store comments or usernames.
The full field list and FAQ are on the Instagram Scraper page. If you'd rather see every option side by side with the official API, the Instagram API guide covers that.



