All projects

Instagram Profile & Posts Fetcher

Headless Python tool that pulls Instagram profiles, posts and Reels via internal REST + GraphQL APIs.

The goal

A repeatable Python tool that pulls profile data, posts and Reels from any Instagram URL without launching a browser — structured JSON output, full pagination with cookies, minimum dependencies.

The solution

A single-CLI Python script that detects URL type and follows the correct API chain: for profiles, seed the session via /username/ HTML to extract LSD+cookies, then call web_profile_info/ (REST) with PolarisProfilePageContentQuery (GraphQL) as 429 fallback. For posts/reels, PolarisProfilePostsQuery with cursor pagination. For individual posts/reels, raw HTML + no-auth oEmbed. Cookie input via string or JSON for >12 pagination.

Highlights

  • Four URL types handled by a single CLI
  • REST → GraphQL fallback eliminates 429 flakes
  • Cookie auth unlocks full pagination
  • No browser — pure requests, dramatically faster than Playwright

The challenge

Instagram's internal GraphQL rejects requests that don't look exactly like browser traffic — missing X-FB-LSD, wrong sec-ch-ua, stale doc_id all fail.

Captured the browser's exact request in DevTools, extracted APP_ID, BLOKS_VERSION_ID, DOC_ID into constants; a seed GET on /username/ pulls LSD + cookies via regex so every subsequent call validates.

The public REST endpoint returns 429 intermittently, especially without cookies.

Two-tier fetch — REST first, GraphQL PolarisProfilePageContentQuery as automatic fallback on 429.

The unauthenticated GraphQL posts query returns only the first 12 items — has_next_page is immediately false.

Cookie input via --cookies (raw string) or --cookies-file (JSON); the session attaches to every request, enabling real end_cursor → has_next_page pagination. README documents the DevTools extraction steps.

Individual post/reel URLs have a different shape — the GraphQL posts query doesn't apply.

For /p/CODE/ and /reel/CODE/, save raw HTML (for downstream extraction) and call /api/v1/oembed/?url=... for canonical metadata (author, thumbnail, media_id). Two outputs per URL.

What was delivered

  • fetch_instagram_profile.py single CLI
  • requirements.txt (minimal deps)
  • Reference captures (web_profile_info, fetch_query, posts_fetch, reels_fetch) + sample responses
  • README documenting cookie extraction and API chain
  • Sample outputs (yarden.sha_profile.json, yarden.sha_posts.json)

Results

4

URL types

Anonymous / Cookies

Auth modes

REST → GraphQL

Fallback

Headless (requests)

Runtime

What it taught me

  • For Instagram / Facebook APIs, capturing the browser's exact request is faster than reverse-engineering
  • Two-tier REST → GraphQL fallback turns intermittent 429s into a non-issue
  • Headless requests is dramatically faster than Playwright when the APIs are usable