Scrapers

Instagram reels

Scrape a public Instagram profile's reels (clip-type posts) with metadata plus direct video URLs. Two modes: unauth path returns the first ~12 reels via web_profile_info, auth path paginates beyond that via user-feed when a sessionid cookie is supplied.

Updated today

Overview

At a glance
Worker
ultron-instagram-reels
Runtime
Python + httpx, no browser
Targets
Public Instagram usernames, reels-only
Cap (unauth)
~12 reels (endpoint cap)
Cap (auth)
500 reels per username
Proxy
Residential US
Default delay
2000 ms, +50% jitter

Sibling to the Instagram trends and Instagram carousels scrapers. Same dataset row shape, different capturedFrom tag so downstream consumers know which surface yielded the row. Reels-mode rows include direct video URLs, which the other two scrapers do not.

Input schema

.actor/INPUT_SCHEMA.jsonjson
1{
2 "usernames": ["nike", "adidas"], // public IG handles, no @
3 "maxItemsPerTarget": 50, // 1 to 500
4 "proxyConfiguration": {
5 "useApifyProxy": true,
6 "apifyProxyGroups": ["RESIDENTIAL"],
7 "apifyProxyCountry": "US"
8 },
9 "requestDelayMs": 2000,
10 "igSessionId": "<secret, optional>", // required for pagination past 12
11 "igDsUserId": "<secret>",
12 "igCsrfToken": "<secret>",
13 "igMid": "<secret>",
14 "igDatr": "<secret>",
15 "igDid": "<secret>",
16 "igRur": "<secret>"
17}

Auth vs unauth path

PathEndpointCap
UnauthGET /api/v1/users/web_profile_info/?username=<u>~12 reels. Hard limit from Instagram.
Auth (cookies)GET /api/v1/feed/user/<userId>/?max_id=<cursor>&only_fetch_first_carousel_media=falseUp to 500 reels per username, paginated.
Note
The unauth path is rate-limited per-IP at roughly 30 requests per hour. With pagination disabled by Instagram, the only way to backfill a full reels archive is via the authenticated path.

Crawl flow

src/__main__.pypy
1# Per username:
2# 1. Resolve username to userId via /api/v1/users/web_profile_info/?username=<u>.
3# Push profile metadata as kind="profile".
4# 2. If no igSessionId in input:
5# Take the embedded reels[] from web_profile_info (capped ~12 by Instagram).
6# Push them as kind="reel" rows. Stop.
7# 3. If igSessionId is present:
8# Build httpx.Client with all 7 cookies + UA from session source.
9# Loop /api/v1/feed/user/<userId>/ with max_id cursor.
10# Per page: filter for media_type == 2 (video) with product_type == "clips".
11# Push each as kind="reel". Stop at maxItemsPerTarget or empty cursor.
12# 4. Sleep requestDelayMs + 50% jitter between requests.

Output shape

dataset-row.jsonjson
1// kind === "profile"
2{
3 "kind": "profile",
4 "source": "instagram",
5 "username": "nike",
6 "userId": "13460080",
7 "followerCount": 316000000,
8 "followingCount": 150,
9 "postCount": 1042,
10 "isVerified": true,
11 "scrapedAt": "2026-05-19T12:00:00Z"
12}
13
14// kind === "reel"
15{
16 "kind": "reel",
17 "source": "instagram",
18 "capturedFrom": "user-feed:nike",
19 "shortcode": "C7xxxxx",
20 "authorUsername": "nike",
21 "caption": "...",
22 "likeCount": 89321,
23 "commentCount": 2342,
24 "viewCount": 1284322,
25 "videoUrl": "https://scontent...mp4",
26 "thumbnailUrl": "https://scontent...jpg",
27 "durationSec": 24,
28 "publishedAt": "2026-05-18T14:00:00Z"
29}

Webhook contract

actor.jsonjson
1{
2 "eventTypes": ["ACTOR.RUN.SUCCEEDED", "ACTOR.RUN.FAILED", "ACTOR.RUN.TIMED_OUT", "ACTOR.RUN.ABORTED"],
3 "requestUrl": "https://scraper-webhook.51ultron.com/apify",
4 "payload": { "actorName": "instagram-reels", "source": "instagram", "datasetId": "<id>" }
5}

File map

actors/instagram-reels
.actor
actor.json
INPUT_SCHEMA.json
src
__main__.py
Dockerfile
requirements.txt
README.md