# Instagram Posts Scraper (`neuro-scraper/instagram-posts-scraper`) Actor

Stop wasting hours manually analyzing competitors. Automate your growth! Instantly extract top-performing Instagram posts, uncover hidden trends, and turn raw data into your ultimate advantage.

- **URL**: https://apify.com/neuro-scraper/instagram-posts-scraper.md
- **Developed by:** [Neuro Scraper](https://apify.com/neuro-scraper) (community)
- **Categories:** AI, Automation, Social media
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Instagram Post Scraper
Instagram Post Scraper is a fast, standalone Python tool that extracts detailed public data from Instagram profiles without requiring a login, cookies, or browser automation. It collects engagement metrics (likes, comments, views), full media content (images, videos, sidecars), captions, hashtags, and music metadata. It's designed for researchers, marketers, and developers who need structured Instagram data exported straight to JSON.

What is Instagram Post Scraper?
Instagram Post Scraper is a highly optimized Python script that bypasses the limitations of web-based scraping by interacting directly with Instagram's internal mobile REST API. Because it leverages the same endpoints used by the Instagram web application, it accesses rich data that is often missing or restricted in basic HTML scraping.

Unlike many other solutions, this scraper requires no login, no passwords, and no session cookies. It programmatically fetches a public profile to extract a fresh csrf\_token, then seamlessly paginates through the user's feed.

It is completely media-type aware. It intelligently parses Image (type 1), Video (type 2), and Sidecar (carousel, type 8) posts, ensuring that video-specific fields (like view counts and duration) or carousel-specific fields (like child images) are extracted correctly and structured predictably.

What Instagram data is publicly available to scrape?
Data Category	Publicly Available	Restricted behind login
Captions, hashtags, mentions	Yes	—
Likes, comments, view/play counts	Yes	—
Video, audio, thumbnail URLs	Yes	—
Owner username, full name, ID	Yes	—
Timestamp, tagged users, co-authors	Yes	—
Private-account posts	No	Requires following + login
Full comment threads	No	Requires login
This scraper only collects data that is publicly visible to logged-out users.

What data can I extract with Instagram Post Scraper?
Identity & Ownership Fields
Field Name	Description
id	Instagram's internal numeric ID for the post
shortCode	The short alphanumeric code used in the public URL
url	The full public URL of the Instagram post
inputUrl	The profile URL that was scraped to find this post
type	Media type: Image, Video, or Sidecar
productType	Instagram's internal product categorization (e.g., clips, feed, carousel\_container)
ownerUsername	The username of the account that posted the content
ownerFullName	The display name of the posting account
ownerId	Instagram's internal numeric user ID of the owner
coauthorProducers	Array of co-authors on the post (includes id, username, fullName)
taggedUsers	Array of users tagged in the media
Engagement & Media Metric Fields
Field Name	Description
likesCount	Total number of likes
commentsCount	Total number of comments
videoViewCount	Number of views (Video only)
videoPlayCount	Number of plays / loops (Video only)
videoDuration	Length of the video in seconds (Video only)
dimensionsWidth	Original width of the media in pixels
dimensionsHeight	Original height of the media in pixels
timestamp	ISO-8601 formatted publishing timestamp
Content, Media & Comments Fields
Field Name	Description
caption	The full text caption
hashtags	Array of hashtags extracted from the caption
mentions	Array of @mentions extracted from the caption
alt	Accessibility caption (alt text) for images
displayUrl	Best available thumbnail / display image URL
images	Array of all available image resolution URLs
videoUrl	Direct URL to the MP4 video file
musicInfo	Audio metadata (artist, song name, muting status, original audio flag)
childPosts	Array of individual media items within a Sidecar/Carousel post
isPaidPartnership	Boolean indicating if the post is a paid partnership
isCommentsDisabled	Boolean indicating if comments are turned off
Media-Type Specific Behavior
The scraper is strictly typed and handles media types differently to prevent data pollution:

Image Posts: videoUrl, videoViewCount, videoPlayCount, videoDuration, and musicInfo are always null. childPosts is an empty array \[].
Video Posts: Extracts the MP4 link into videoUrl, populates the video metrics, and attempts to extract musicInfo. Cover frames are placed in images.
Sidecar (Carousel) Posts: The top-level post acts as a container. video\* metrics are null. The childPosts array contains the actual media (which can be a mix of Image and Video types), each with its own specific videoUrl, images, and dimensions.
How to configure Instagram Post Scraper
Configuration Options
You can adjust the scraping parameters by editing the variables at the top of the final\_post.py script.

Parameter	Type	Default	Description
TARGET\_USERNAME	string	"cristiano"	The Instagram username (handle) to scrape.
MAX\_ITEMS	int/None	100	The maximum number of posts to scrape. Set to None to scrape the entire account.
OUTPUT\_FILE	string	"insta\_post.json"	The filename where the JSON data will be saved.
REQUEST\_DELAY	float	1.5	Seconds to pause between page requests to avoid rate limits.
Example Configuration
python

## ==============================================================================

## USER CONFIGURATION  <- edit these

## ==============================================================================

TARGET\_USERNAME = "natgeo"      # Instagram username to scrape
MAX\_ITEMS       = 50            # Maximum posts to collect (None = unlimited)
OUTPUT\_FILE     = "natgeo\_data.json"
REQUEST\_DELAY   = 2.0           # seconds between pages
How to use Instagram Post Scraper
Prerequisites: You need Python 3.7+ installed. You also need the requests library.
bash

pip install requests
Set the Target: Open final\_post.py in a text editor and change TARGET\_USERNAME to the account you want to scrape.
Set the Limit: Change MAX\_ITEMS to the number of posts you need (e.g., 500).
Run the Script: Execute the file from your terminal.
bash

python final\_post.py
View Output: The scraper will print its progress to the console. Once finished (or manually stopped), your data is safely stored in the insta\_post.json (or your configured OUTPUT\_FILE) in the same directory.
Output Format
The scraper saves data incrementally to a JSON file. The root structure contains metadata about the scrape, and a posts array containing the scraped items.

json

{
"username": "cristiano",
"total\_posts\_scraped": 100,
"scraped\_at": "2026-08-06T15:30:00Z",
"posts": \[ ... ]
}
Example Output — Image Post
json

{
"inputUrl": "https://www.instagram.com/cristiano/",
"id": "3952122654932253942",
"type": "Image",
"shortCode": "DbYwp2MArz2",
"caption": "Cuando te vuelves hacia la luz del sol, las sombras caen detrás de ti. 🌅",
"hashtags": \[],
"mentions": \[],
"url": "https://www.instagram.com/p/DbYwp2MArz2/",
"commentsCount": 219302,
"likesCount": 13284301,
"timestamp": "2026-08-01T12:00:00.000Z",
"ownerFullName": "Cristiano Ronaldo",
"ownerUsername": "cristiano",
"ownerId": "173560420",
"isCommentsDisabled": false,
"firstComment": "",
"latestComments": \[],
"productType": "feed",
"taggedUsers": \[],
"coauthorProducers": \[],
"isPaidPartnership": false,
"alt": null,
"displayUrl": "https://scontent-sin2-3.cdninstagram.com/v/t51...",
"dimensionsHeight": 4096,
"dimensionsWidth": 3116,
"images": \[
"https://scontent-sin2-3.cdninstagram.com/v/t51..."
],
"childPosts": \[],
"videoUrl": null,
"videoViewCount": null,
"videoPlayCount": null,
"videoDuration": null,
"musicInfo": null
}
Example Output — Video Post
json

{
"inputUrl": "https://www.instagram.com/cristiano/",
"id": "3951593428073548188",
"type": "Video",
"shortCode": "DbW4UlRF4mc",
"caption": "Try my Game Day Blast recipe. Stay sharp. Stay ready. #FuelLikeRonaldo",
"hashtags": \[
"FuelLikeRonaldo"
],
"mentions": \[],
"url": "https://www.instagram.com/p/DbW4UlRF4mc/",
"commentsCount": 19415,
"likesCount": 2030859,
"timestamp": "2026-07-31T08:30:00.000Z",
"ownerFullName": "Herbalife",
"ownerUsername": "herbalife",
"ownerId": "15890625",
"isCommentsDisabled": false,
"firstComment": "",
"latestComments": \[],
"productType": "clips",
"taggedUsers": \[
{
"id": "173560420",
"username": "cristiano",
"fullName": "Cristiano Ronaldo"
}
],
"coauthorProducers": \[
{
"id": "173560420",
"username": "cristiano",
"fullName": "Cristiano Ronaldo"
}
],
"isPaidPartnership": false,
"alt": null,
"displayUrl": "https://scontent-sin2-3.cdninstagram.com/v/t51...",
"dimensionsHeight": 1920,
"dimensionsWidth": 1080,
"images": \[
"https://scontent-sin2-3.cdninstagram.com/v/t51..."
],
"childPosts": \[],
"videoUrl": "https://scontent-sin2-3.cdninstagram.com/o1/v/t2/f2/m86/AQO2D...",
"videoViewCount": 62058800,
"videoPlayCount": 62058800,
"videoDuration": 19.562000274658203,
"musicInfo": {
"artist\_name": "herbalife",
"song\_name": "Original audio",
"uses\_original\_audio": true,
"should\_mute\_audio": false,
"should\_mute\_audio\_reason": "",
"audio\_id": "27581399904859106"
}
}
Example Output — Sidecar/Carousel Post
json

{
"inputUrl": "https://www.instagram.com/cristiano/",
"id": "3956550032798302915",
"type": "Sidecar",
"shortCode": "DbofUrJAMLD",
"caption": "My toys 🚀",
"hashtags": \[],
"mentions": \[],
"url": "https://www.instagram.com/p/DbofUrJAMLD/",
"commentsCount": 219927,
"likesCount": 15220275,
"timestamp": "2026-08-04T21:05:40.000Z",
"ownerFullName": "Cristiano Ronaldo",
"ownerUsername": "cristiano",
"ownerId": "173560420",
"isCommentsDisabled": false,
"firstComment": "",
"latestComments": \[],
"productType": "carousel\_container",
"taggedUsers": \[],
"coauthorProducers": \[],
"isPaidPartnership": false,
"alt": null,
"displayUrl": "https://instagram.fdac2-2.fna.fbcdn.net/v/t51...",
"dimensionsHeight": 4096,
"dimensionsWidth": 3072,
"images": \[
"https://instagram.fdac2-2.fna.fbcdn.net/v/t51..."
],
"childPosts": \[
{
"id": "3956548597398537282",
"type": "Image",
"displayUrl": "https://instagram.fdac2-2.fna.fbcdn.net/v/t51...",
"images": \[
"https://instagram.fdac2-2.fna.fbcdn.net/v/t51..."
],
"dimensionsHeight": 4096,
"dimensionsWidth": 3072,
"videoUrl": null,
"videoDuration": null,
"taggedUsers": \[],
"alt": null
},
{
"id": "3956548585218255013",
"type": "Video",
"displayUrl": "https://instagram.fdac2-2.fna.fbcdn.net/v/t51...",
"images": \[
"https://instagram.fdac2-2.fna.fbcdn.net/v/t51..."
],
"dimensionsHeight": 1920,
"dimensionsWidth": 1080,
"videoUrl": "https://instagram.fdac2-2.fna.fbcdn.net/v/t51...mp4",
"videoDuration": 5.4,
"taggedUsers": \[],
"alt": null
}
],
"videoUrl": null,
"videoViewCount": null,
"videoPlayCount": null,
"videoDuration": null,
"musicInfo": null
}
Field Reference (Complete)
Field Name	Type	Applies To	Description
inputUrl	String	All	Profile URL scraped
id	String	All	Numeric post ID
type	String	All	Image, Video, or Sidecar
shortCode	String	All	URL identifier
caption	String	All	Post text
hashtags	Array	All	Tags extracted from caption
mentions	Array	All	Mentions extracted from caption
url	String	All	Public post URL
commentsCount	Integer	All	Number of comments
likesCount	Integer	All	Number of likes
timestamp	String	All	Publish date (ISO)
ownerFullName	String	All	Owner display name
ownerUsername	String	All	Owner handle
ownerId	String	All	Owner numeric ID
isCommentsDisabled	Boolean	All	True if comments disabled
firstComment	String	All	Placeholder (always empty string)
latestComments	Array	All	Placeholder (always empty array)
productType	String	All	Instagram internal type
taggedUsers	Array	All	Users tagged in post
coauthorProducers	Array	All	Co-authors on the post
isPaidPartnership	Boolean	All	True if paid partnership
alt	String	All	Image description
displayUrl	String	All	Primary thumbnail URL
dimensionsHeight	Integer	All	Media height
dimensionsWidth	Integer	All	Media width
images	Array	All	List of image resolutions
childPosts	Array	Sidecar	Media items within carousel
videoUrl	String	Video	Direct MP4 link
videoViewCount	Integer	Video	Views
videoPlayCount	Integer	Video	Plays
videoDuration	Float	Video	Length in seconds
musicInfo	Object	Video	Artist, song, and mute data
How does it work?
Instagram Post Scraper does not use Selenium, Playwright, or web scraping libraries like BeautifulSoup. It acts as an API client:

Token Extraction: It makes a standard HTTP GET request to the target profile (https://www.instagram.com/{username}/). It parses the raw HTML using regex to find the csrf\_token, which is required for API access.
REST API Access: Using the csrf\_token and a hardcoded Instagram Web App ID (936619743392459), it queries Instagram's internal mobile REST endpoint: api/v1/feed/user/{username}/username/. This endpoint returns rich, un-minified JSON data intended for mobile apps and modern web clients.
Pagination: The API response contains a next\_max\_id and more\_available flag. The script passes next\_max\_id back to the endpoint in a loop to fetch older posts, batch by batch, until it hits MAX\_ITEMS or runs out of posts.
Data Normalization: Raw data is passed through \_map\_item(), which normalizes it into the clean schema above.
Incremental Saving: The scraper saves the complete list of collected posts to the JSON file after every single page request. If the script crashes or is interrupted, you do not lose the data collected so far.
How does Instagram Post Scraper differ from the official Instagram API?
Feature	Instagram Graph API	Instagram Post Scraper
Access scope	Only accounts you own/manage	Any public account
Account requirement	Business/Creator account + Facebook Page	None
Setup time	Days (App review & configuration)	Minutes (Run script)
Media types	Basic feed media	Images, Reels, Videos, full Carousels
Data returned	Officially sanctioned metrics	Raw internal metrics & media URLs
Rate Limits & Error Handling
Instagram restricts how rapidly you can request pages without an authenticated session.

REQUEST\_DELAY: The script pauses for 1.5 seconds (by default) between pagination requests. Decreasing this heavily risks a temporary IP block (HTTP 429 Too Many Requests).
Error Handling: The requests module is configured with raise\_for\_status(). If a page fails (e.g., due to rate limiting or a network error), the script catches the exception, prints an error message, stops paginating, and saves all data collected up to that point. It will not crash and delete your data.
Legal Considerations
Scraping publicly available data—data that any logged-out visitor to Instagram can view in their browser—is generally lawful. This scraper only collects public data; it cannot access private accounts or login-gated content. However, because the output includes personal data (usernames, names), regional privacy laws (like GDPR or CCPA) may apply to how you store or process the data after collection. This is factual context, not legal advice.

Frequently Asked Questions
Does it require an Instagram account? No. It requires no login, passwords, or session cookies.

How many posts can I scrape? You can scrape up to the entirety of a public user's feed by setting MAX\_ITEMS = None. Keep in mind that extremely large accounts may trigger rate limits halfway through.

Does it handle carousel/sidecar posts? Yes. It correctly parses Sidecar posts and places all contained images and videos inside the childPosts array.

What if a username is private? The scraper will fail to fetch the feed because private accounts do not expose timeline data to logged-out users.

Can I change the output filename? Yes, edit the OUTPUT\_FILE variable at the top of the script.

What Python version is required? Python 3.7 or newer is recommended.

What happens when Instagram changes its API? If Instagram drastically changes its endpoint structure or requires strict authentication for logged-out users, the scraper may fail. Currently, the api/v1/feed endpoint is highly stable.

Can I scrape multiple users? Out of the box, the script takes a single TARGET\_USERNAME. However, you can easily wrap the scrape() function in a Python for loop to process a list of usernames.

Troubleshooting
UnicodeEncodeError on Windows: The script contains a built-in fix (io.TextIOWrapper) to force UTF-8 encoding on Windows consoles when printing emojis.
HTTP 429 Too Many Requests: Instagram has temporarily rate-limited your IP. Stop the scraper, wait 15-30 minutes, and try again. Consider increasing REQUEST\_DELAY.
Empty results: If the output is empty, ensure the target account actually has posts and is not set to Private.
Could not extract csrf\_token: Instagram may be presenting a login wall or captcha to your IP address. This can happen if you scrape too aggressively from a datacenter IP. Try running from a residential connection or VPN.
Changelog / Version History
v1.0.0 - Initial release. Supports REST API pagination, Image/Video/Sidecar typing, and incremental JSON saving.

# Actor input Schema

## `username` (type: `string`):

The Instagram username (e.g. cristiano) to scrape posts from.

## `max_items` (type: `integer`):

Maximum number of posts to scrape.

## `proxyConfiguration` (type: `object`):

Select proxies to use. Residential proxies give the best results for Instagram.

## Actor input object example

```json
{
  "username": "cristiano",
  "max_items": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "username": "cristiano",
    "max_items": 100,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("neuro-scraper/instagram-posts-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "username": "cristiano",
    "max_items": 100,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("neuro-scraper/instagram-posts-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "username": "cristiano",
  "max_items": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call neuro-scraper/instagram-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neuro-scraper/instagram-posts-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/5G0kCfVUbDmCp76Ek/builds/UMOUj5NhPhaWz1JcZ/openapi.json
