# Instagram Scraper (`vortex_data/instagram-scraper`) Actor

One Instagram scraper for everything — profiles, contacts (emails & phones), posts, reels, comments, likers, followers, following, tagged, stories, highlights, hashtags, locations & search. No code: paste usernames or URLs, export to Excel, CSV, JSON or API. Public data needs no login.

- **URL**: https://apify.com/vortex\_data/instagram-scraper.md
- **Developed by:** [VortexData](https://apify.com/vortex_data) (community)
- **Categories:** Social media, Automation, Developer tools
- **Stats:** 166 total users, 54 monthly users, 97.0% runs succeeded, 3 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.10 / 1,000 profile / contacts / searches

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Scraper

**Extract public Instagram data — profiles, contacts, posts, reels, comments, likers, followers, following, tagged posts, stories, highlights, hashtags, locations, and keyword search — then download it as Excel, CSV, JSON, or pull it through the API.** No code. No login needed for public data.

One Actor replaces a dozen single-purpose scrapers: pick **what** you want, paste usernames or links, and press **Start**. Perfect for marketers, agencies, lead generation, researchers, and founders who need Instagram data without writing a line of code.

### What can this Instagram Scraper extract?

You choose one **mode** — *"What do you want to scrape?"* — and the mode decides what every line you paste turns into.

#### ✅ Works without login (no cookie)

| Mode | What you get | Paste |
|---|---|---|
| 👤 **Profile details** | Bio, followers/following/posts counts, category, links, verification, latest posts | a username |
| 📇 **Contacts** (lead-gen) | Emails, phones and links pulled from the bio + business fields | a username |
| 🖼️ **Profile posts** | A user's timeline — captions, likes, comments, media, dates | a username |
| 🎬 **Profile reels** | A user's videos with view/play counts and audio | a username |
| 📄 **Post / Reel detail** | One post or reel in full (plus tagged users and top comments) | a post URL |
| 💬 **Comments** | Every comment with author, likes, replies, timestamp | a post URL, **or a username** — its recent posts are read for you |

#### 🔑 Needs your Instagram login cookie

| Mode | What you get | Paste |
|---|---|---|
| ❤️ **Likers** | Users who liked a post | a post URL, **or a username** — its recent posts are read for you |
| 👥 **Followers** / **Following** | A profile's follower or following list | a username |
| 🏷️ **Tagged posts** | Posts a user is tagged in | a username |
| 📸 **Stories** | A user's currently-active stories | a username |
| ⭐ **Highlights** | A user's highlight reels | a username |
| #️⃣ **Hashtag posts** | Top and recent posts for a hashtag | a `#tag` or word |
| 📍 **Location posts** | Posts from a place | a location id / URL |
| 🔎 **Search** | Matching users, hashtags, or places | use the Search field |

The 🔑 modes need cookies because Instagram hides those pages from logged-out visitors — see the **"Do you need an Instagram login cookie?"** section below. **Comments are not one of them**: they are read from the public post page, so they work with no login. A cookie still helps there — it lets the run page past the first batch on heavily-commented posts and fetch reply threads.

### 🚀 How to scrape Instagram in 3 steps

1. **Choose a mode** under *"What do you want to scrape?"*.
2. **Paste usernames or URLs**, one per line, into *"Instagram URLs or usernames"*. Then set **Results per input** — how many records to get from each line.
3. Press **Start**. When it finishes, open the **Dataset** tab and click **Export → Excel, CSV, or JSON**.

That's it — no setup, no proxy configuration. Want to try it right now? Just press **Start** with the default input and you'll get 100 recent posts from [@humansofny](https://www.instagram.com/humansofny/).

**Bare usernames, `@handles`, `#hashtags` and plain location ids all work** — you don't have to paste full URLs. Lines that don't fit the chosen mode (for example a hashtag pasted into a Followers run) are skipped with a note, so you never get a confusing empty run.

#### How many results will I get?

Your total is simply **Results per input × number of lines**. For example, `100` results per input with 3 profiles pasted = up to **300 records**. Profiles with fewer posts, or short comment/follower lists, return whatever exists.

### 📊 Example output

Every record is one row in your spreadsheet. A **post** looks like this:

```json
{
  "url": "https://www.instagram.com/p/C3TTthZLoQK/",
  "type": "Image",
  "shortCode": "C3TTthZLoQK",
  "caption": "New York City, one story at a time.",
  "hashtags": ["nyc", "humansofny"],
  "likesCount": 40213,
  "commentsCount": 512,
  "timestamp": "2024-02-13T20:49:57.000Z",
  "displayUrl": "https://scontent.cdninstagram.com/...",
  "ownerUsername": "humansofny",
  "ownerFullName": "Humans of New York",
  "ownerIsVerified": true
}
```

A **Contacts** (lead-gen) record projects a profile down to what you need for outreach:

```json
{
  "username": "thecoffeestudio",
  "fullName": "The Coffee Studio",
  "emails": ["hello@coffeestudio.com"],
  "phones": ["+12125559000"],
  "links": ["https://coffeestudio.com/", "https://coffeestudio.com/menu"],
  "followersCount": 48210,
  "isBusinessAccount": true,
  "biography": "Specialty coffee · Order: hello@coffeestudio.com"
}
```

#### Fields you get, by mode

- **🖼️ Posts & Reels (40+ columns):** input URL, caption, hashtags, mentions, likes, comments count, first comment, latest comments preview, timestamp, image URL, all image sizes (carousels included), video URL, view and play counts, duration, alt text, owner (username, name, picture, verified), location, tagged users, coauthors (collabs), music info, product type, sponsored / paid-partnership / ad flags, pinned flag, comments-disabled flag, post URL, and more.
- **👤 Profile details (45 columns):** username, full name, biography, bio hashtags and mentions, bio links, external URL, followers / following / posts counts, verified and private flags, business and professional flags, category, business email and phone, pronouns, profile picture (regular and HD), IGTV and highlight counts, has-Reels / has-Guides, Facebook page, plus the **12 most recent posts inline**, latest IGTV videos, and related profiles.
- **📇 Contacts:** username, full name, biography, `emails`, `phones`, `links`, business email/phone/category, followers count, business and verified flags, profile picture.
- **💬 Comments:** comment id, post id/shortcode/URL, comment URL, text, timestamp, likes, replies count, inline replies, author (username, id, verified, picture).
- **❤️ Likers · 👥 Followers · 👥 Following:** id, username, full name, private flag, verified flag, profile picture.
- **📸 Stories · ⭐ Highlights:** the same rich media fields as posts, plus story expiry time and (for highlights) the highlight id.
- **#️⃣ Hashtag details:** id, name, total posts count, subtitle, description, cover image, top and recent posts.
- **📍 Location details:** id, name, slug, latitude, longitude, full address, phone, website, description, cover image, top and recent posts.

**About the image and video URLs:** they point at Instagram's CDN and are signed
by Instagram, so they **expire after a few days** — download anything you want
to keep rather than storing the link. Every URL is saved on Instagram's globally
routable CDN host, so it opens from any network, not only the one the run used.

### 🔑 Do you need an Instagram login cookie?

**No** — for profile details, contacts, posts, reels, single post/reel detail, and comments. These use only public data, the same anyone sees in an incognito tab.

**Yes** — for the 🔑 modes (likers, followers, following, tagged, stories, highlights, hashtag, location, search) and any private or login-limited page. Instagram simply doesn't serve those to logged-out visitors.

**Optional, but it helps** — comments. The public post page carries the first batch, which is all most posts have. On a post with thousands of comments a cookie lets the run keep paging, and it is what makes reply threads available.

The Actor uses its own managed proxy automatically — there is nothing to configure. When a mode needs cookies, open **Instagram login cookie** and paste your own.

⚠️ **Cookies are sensitive.** They work like a temporary password for your Instagram session. Use only cookies from **your own** account, and don't share them.

#### How to copy your Instagram cookie

**Easiest — a cookie extension:**

1. Install the **EditThisCookie** or **Cookie-Editor** browser extension.
2. Open [instagram.com](https://www.instagram.com/) and log in.
3. Click the extension and **Export** your cookies (as JSON).
4. Paste the whole thing into **Instagram login cookie**.

**Manual — the Cookie header:**

1. Open [instagram.com](https://www.instagram.com/) logged in, press **F12**, and go to the **Network** tab.
2. Refresh the page (**Ctrl+R**) and click the top `www.instagram.com` **document** request.
3. Open **Headers → Request Headers**, find **Cookie**, and copy the whole value (not the word `Cookie:`).
4. Paste it in. The simplest format looks like: `sessionid=...; csrftoken=...; ds_user_id=...; mid=...`

The Actor accepts Cookie-header text, cookie-extension JSON, and Netscape cookie files. It **hides cookie values in logs** and only records which cookie names it installed. For best results include `sessionid`, `csrftoken`, `ds_user_id`, and `mid`.

#### Set your country when you use a cookie

Right below the cookie field there is **Country you use Instagram from**. Instagram watches *where* a session is used: a cookie created in one country but used from another looks stolen, so Instagram challenges it — and a challenged session looks exactly like an expired one, which sends people off to re-export a cookie that was never the problem.

Pick the country you normally open Instagram from and the Actor browses from there. Leave it on **Automatic** when you aren't using a cookie — without a login, spreading across the whole IP pool is the better default.

### 💡 Example use cases

#### 📈 Track 3 competitors' posts from the last week

```json
{
  "resultsType": "posts",
  "directUrls": ["nike", "adidas", "puma"],
  "resultsLimit": 100,
  "onlyPostsNewerThan": "7 days"
}
```

Every post each brand made in the last 7 days — likes, comments, captions, hashtags and image URLs side by side.

#### 📇 Build a lead list of creator emails and phones

```json
{
  "resultsType": "contacts",
  "directUrls": ["thecoffeestudio", "berlinbakes", "studio.mila"],
  "resultsLimit": 1
}
```

One lean row per profile with emails, phones and links pulled from the bio and business fields — ready for outreach. No cookie needed.

#### 💬 Read the comments on a viral reel (no cookie needed)

```json
{
  "resultsType": "comments",
  "directUrls": ["https://www.instagram.com/reel/CXXXXXXX/"],
  "resultsLimit": 500
}
```

Add your cookie to page past the first batch on posts with thousands of comments, and to pull reply threads.

Perfect for sentiment analysis, customer feedback, or spotting influencer mentions.

#### 💬 Read the comments on a list of accounts (no post URLs needed)

```json
{
  "resultsType": "comments",
  "directUrls": ["@nasa", "natgeo", "https://www.instagram.com/spacex/"],
  "resultsLimit": 200
}
```

Paste the accounts themselves and each one is read for the comments under its recent posts — no need to collect post URLs first. **Results per input** stays what it says: `200` here means up to 200 comments *per account*, spread across that account's newest posts, not 200 per post.

Likers works the same way — swap `"resultsType": "likes"` (that one needs your cookie). It is also how you get past Instagram's per-post ceiling of ~100 likers: a profile line spreads your limit across the account's recent posts instead of stopping at one post's 100.

#### 🔥 Find trending posts under a hashtag (needs cookie)

```json
{
  "resultsType": "hashtag",
  "directUrls": ["summertravel"],
  "resultsLimit": 200,
  "sessionCookies": "sessionid=...; csrftoken=...; ds_user_id=...; mid=..."
}
```

Content research, influencer discovery, or catching trends early.

#### 🔎 Discover top hashtags around a topic (needs cookie)

```json
{
  "resultsType": "search",
  "search": "sustainable fashion",
  "searchType": "hashtag",
  "searchLimit": 5,
  "resultsLimit": 50,
  "sessionCookies": "sessionid=...; csrftoken=...; ds_user_id=...; mid=..."
}
```

Finds 5 matching hashtags, then scrapes 50 posts from each — 250 posts of fresh content for your niche.

### 💰 How much does it cost?

You pay **per result** — the price for each record is shown at the **top of this page**. Lightweight results (a comment, a follower row) cost less than a full post, so **you only pay for what you actually pull**. Your first results are typically just a few cents.

Want a predictable spend? Every result is billed as it's saved, so a small **Results per input** keeps any run in check. Runs also honour your Apify account-level usage limits.

### ⚡ How fast is it?

| Job | Typical time |
|---|---|
| Profile with 50 recent posts | 5–10 seconds |
| Profile with 1,000 posts (with date filter) | 30–60 seconds |
| One recent post detail | 3–5 seconds |
| Hashtag with 100 posts | 8–15 seconds |
| 500 comments on one post | 15–30 seconds |

Multiple inputs are scraped **in parallel**, so adding more usernames barely slows things down.

### ❓ FAQ

**Is it legal to scrape Instagram?**
The Actor collects only public Instagram data — the same data any visitor can see. You're responsible for using the output in line with your local laws (especially GDPR when records contain personal data). For a deeper look, read [Apify's guide to the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

**Do I need my own Instagram account?**
Only for the 🔑 modes. Public modes (profile details, contacts, posts, reels, post detail, comments) need no login at all.

**Can I scrape a private account?**
No. Only public profile metadata is available for private accounts — their posts can't be scraped.

**Why are some fields empty?**
For very old posts Instagram doesn't expose every field publicly. The Actor uses a multi-tier fallback to fill in as much as possible — captions, likes, comments, dates and authors come through virtually always.

**Can I run this on a schedule?**
Yes. Open the **Schedules** tab in the Apify Console and add a cron expression — great for daily competitor tracking or weekly trend reports.

**Can I get the data through an API?**
Yes. Apify offers a REST API and SDKs for Python, JavaScript and more. See the **API** tab on this Actor's page for ready-to-paste snippets, or integrate with Make, Zapier, Google Sheets, and others.

**A run saved 0 records — what now?**
Open the run's **Storage → Key-value store** and copy `SUPPORT_DIAGNOSTICS`, then post it with the run log in the **Issues** tab. It captures the mode, input type, and proxy/cookie flags — but never your raw cookies. The most common cause is a 🔑 mode without a login cookie.

### 🤝 Support and feedback

Something not working as expected, or an idea for a new feature? Open an issue on the Actor's **Issues** tab — we read every report.

*Built for marketers, researchers, and curious people. Happy scraping! 📸*

# Actor input Schema

## `resultsType` (type: `string`):

The mode decides how the lines below are read. Modes marked 🔑 need your Instagram login cookie (add it further down). Lines that don't fit the chosen mode are skipped with a note explaining why. In **Contacts**, a profile that turns out to have no email, phone or link is still saved to your dataset but is not charged for.

## `directUrls` (type: `array`):

One input per line, read according to the mode above:

- **Profile modes** (details, contacts, posts, reels, followers, following, tagged, stories, highlights): a username, `@handle`, or `instagram.com/{username}/`.
- **Post modes** (post/reel detail, comments, likers): a post URL — `instagram.com/p/{code}/`, `/reel/{code}/`, `/tv/{code}/`.
- **Comments** and **Likers** read a profile line too — a username, `@handle`, or `instagram.com/{username}/` — and return the comments (or likers) from that account's recent posts, so you can paste the accounts you care about instead of hunting for post URLs first.
- **Hashtag**: a `#tag`, a plain word, or `instagram.com/explore/tags/{tag}/`.
- **Location**: a location id or `instagram.com/explore/locations/{id}/`.
- **Search**: leave empty and use the Search field below.
- A post URL pasted into a profile mode is resolved to the account that posted it, so you can paste a lead's post and get the lead. The reverse works too: a profile pasted into Comments or Likers is read as "under what they posted".

A line that doesn't fit the mode is skipped and the run says exactly why. Type a multi-word phrase here and it is reported as a search phrase — put it in the Search field instead.

## `resultsLimit` (type: `integer`):

How many records to get from each line you paste (each username, post, hashtag, and so on). Your total is simply this × the number of lines — e.g. 100 × 2 profiles = up to 200 records. That holds for a profile pasted into Comments or Likers as well: the limit is the whole line's, spread over that account's recent posts — not a fresh limit per post. Profiles with fewer posts, or short comment/follower lists, return whatever exists. Some data has a ceiling Instagram sets, not you: **Likers** returns at most ~100 accounts *per post*, whatever you ask for here — paste a profile instead and the limit is spread over that account's recent posts, which reaches further.

## `search` (type: `string`):

What to look for on Instagram (a keyword, name, hashtag or place). Works with **any** mode: the matches become the run's targets, so you can search for profiles and scrape their contacts, posts or reels in one run. Leave the URL field empty to run on search results alone. Needs your Instagram cookie. One phrase per run works best — a comma-joined list of phrases is sent to Instagram as a single query and usually matches nothing.

## `searchType` (type: `string`):

What the search should return.

## `searchLimit` (type: `integer`):

How many search matches to scrape.

## `sessionCookies` (type: `string`):

Needed for every 🔑 mode (likers, followers, following, tagged, stories, highlights, hashtag, location, search) and any private / login-limited page. Not needed for profile details, contacts, posts, reels, single post/reel detail, or comments — comments are scraped from the public post page, though a cookie lets the run page deeper and fetch comment replies.

Install the **EditThisCookie** (or Cookie-Editor) extension, log in to instagram.com, export your cookies, and paste them here — or paste your Cookie header directly, e.g. `sessionid=...; csrftoken=...; ds_user_id=...; mid=...`. Use only your own cookies — they are sensitive and work like a temporary password.

## `proxyCountry` (type: `string`):

Instagram watches **where** a session is used from. A cookie created in one country but used from another looks stolen — Instagram challenges it, and the run then reports your cookie as expired when it actually isn't.

Pick the country you normally open Instagram from, and the scraper will browse from there. Only matters when you paste a cookie above — without one, leave it on Automatic so the scraper can spread across the whole IP pool.

API callers may pass any 2-letter country code.

## `onlyPostsNewerThan` (type: `string`):

Optional lower date bound for posts/reels. The scraper stops paging once it reaches older posts.

Formats: `2026-01-15`, `2026-01-15T10:00:00Z`, or `1 day` / `2 weeks` / `3 months` / `1 year`.

## `onlyPostsOlderThan` (type: `string`):

Optional upper date bound. Posts newer than this are skipped. Combine with 'Only newer than' for a date window.

Formats: `2026-01-15`, `2026-01-15T10:00:00Z`, or `1 day` / `2 weeks` / `3 months` / `1 year`.

## `addParentData` (type: `boolean`):

Adds `dataSource` and parent fields (username, shortcode, tag, location id, highlight id). Handy when scraping many inputs into one dataset.

## Actor input object example

```json
{
  "resultsType": "posts",
  "directUrls": [
    "humansofny"
  ],
  "resultsLimit": 100,
  "searchType": "hashtag",
  "searchLimit": 10,
  "proxyCountry": "",
  "addParentData": false
}
```

# Actor output Schema

## `results` (type: `string`):

Open the dataset to browse, filter, or export the results (CSV, Excel, JSON, HTML).

# 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 = {
    "directUrls": [
        "humansofny"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vortex_data/instagram-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 = { "directUrls": ["humansofny"] }

# Run the Actor and wait for it to finish
run = client.actor("vortex_data/instagram-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 '{
  "directUrls": [
    "humansofny"
  ]
}' |
apify call vortex_data/instagram-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vortex_data/instagram-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/U6Qxd7KzcFdL8qBCq/builds/MjG09R5WmadmddKyj/openapi.json
