# Instagram Hashtag Scraper · Top Posts & Reels · No Login (`memo23/instagram-hashtag-scraper`) Actor

Scrape any Instagram hashtag — top posts and reels with captions, like and comment counts, play counts, video and thumbnail URLs, creator handle, and tagged accounts. No login, no cookies, no account risk. Many hashtags per run, automatically deduplicated across pages. JSON, CSV, or Excel.

- **URL**: https://apify.com/memo23/instagram-hashtag-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Social media, AI, Automation
- **Stats:** 62 total users, 19 monthly users, 98.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 posts

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 Hashtag Scraper

Turn any Instagram hashtag into a structured stream of posts and reels.
Paste a tag like `coffee`, `#latteart`, or a full explore URL and get captions, engagement counts, play counts, video URLs, and creator handles — no login, no cookies, no account risk.

***

### Why Use This Scraper?

- Top posts and Reels from any public hashtag, in one run
- Real engagement data — likes, comments, and play counts per post
- Direct video and thumbnail URLs, plus the creator's handle and profile picture
- Automatic deduplication — Instagram serves overlapping pages, this actor does not bill you twice for the same post
- Many hashtags per run, processed in parallel
- Flattened rows that drop straight into CSV, Excel, or a spreadsheet
- No login, no cookies, no session tokens — nothing to get your account flagged

***

### Switching From Another Instagram Hashtag Scraper?

The complaint that follows people to this category is a results limit that does not hold — a run that stops early, returns a different count each time, or bills for posts it never delivered.

| What you need | How this Actor answers it |
|---|---|
| Blocked mid-run with `HTTP 429` and no results | It reads Instagram through a data API rather than scraping the site directly, so there is no rate-limit wall to hit — no `429`, no half-empty run to pay for. |
| `maxPostsPerHashtag` should mean what it says | Six runs across six hashtags, six caps hit exactly. 100 requested and 100 delivered on `#coffee`, `#workout`, `#recipe`, `#travel` and `#dance`; 20 requested and 20 delivered on `#espresso`. |
| Carousel posts counted once | A carousel is one row, not one row per slide. In 520 measured rows there were 44 carousels and 44 post IDs — no container-plus-item pair, so no paying twice for one post. |
| No duplicate posts inside a hashtag | 520 rows, 520 distinct post IDs within their hashtag. A post that appears in both Top and Reels is delivered, and charged, once. |
| Reels honouring the limit too | `resultsType: "reels"` paginates on the same counter as `top`. Asking for `both` splits the cap across the two lists, so 100 comes back as 50 top and 50 reels rather than 100 of one and none of the other. |
| Not paying for posts you did not receive | Billing is per delivered row, bounded by the run's charge limit. A run that returns nothing charges for no posts, and error rows are never charged. |
| Knowing when a hashtag simply has less | A tag with nothing to return comes back as an error row naming it and saying why, not as a silently shorter dataset. Speed for reference: 100 posts in 29 seconds, 400 across four hashtags in 52 seconds. |

No login, no cookies, no browser.

### Overview

The Instagram Hashtag Scraper is built for social media managers, trend researchers, brand teams, and agencies who need structured post-level data from Instagram hashtags.

The output is **post-shaped rows**. Each row is one Instagram post or reel, tagged with the hashtag it was found under. If you scrape three hashtags, you get one flat dataset containing all posts from all three, each row carrying its own `hashtag` value so you can filter or pivot by tag.

A post genuinely tagged with two of your hashtags appears once per hashtag, because the row is scoped to the tag it was discovered under. Within a single hashtag, every post is unique.

***

### Supported Inputs

#### Input types

| Input type | Pattern | Example |
|---|---|---|
| Bare tag name | `word` | `coffee` |
| Hash-prefixed tag | `#word` | `#latteart` |
| Explore URL | `instagram.com/explore/tags/<tag>/` | `https://www.instagram.com/explore/tags/espresso/` |

All three forms can be mixed in the same run. Tags are case-insensitive and are normalised to lowercase. Unicode tags (`#café`, `#日本`) are supported.

#### Copy-pasteable input

```json
{
  "hashtags": ["coffee", "#latteart", "https://www.instagram.com/explore/tags/espresso/"],
  "resultsType": "both",
  "maxPostsPerHashtag": 100
}
```

#### Unsupported inputs

- **Profile URLs, post URLs, reel URLs** — this actor takes hashtags only
- **Private or restricted tags** — Instagram serves nothing publicly for these
- **"Recent" / chronological posts** — Instagram no longer serves a public recent feed for hashtags. This actor deliberately offers Top and Reels only rather than shipping a mode that returns nothing. See the FAQ.

***

### Use Cases

| Audience | What they use it for |
|---|---|
| Social media managers | Track which posts win on a branded or campaign hashtag |
| Trend researchers | Measure hashtag volume, format mix, and engagement over time |
| Brand teams | Monitor user-generated content and find brand mentions |
| Influencer agencies | Discover creators already posting in a niche, ranked by real engagement |
| Content strategists | Study the top-performing captions, formats, and reel lengths in a topic |
| Market researchers | Compare engagement across competing hashtags or product categories |

***

### How It Works

![How the Instagram Hashtag Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-instagram-hashtag.png)

1. **Normalise the input** — bare names, `#tags`, and explore URLs all resolve to a clean tag name. Invalid input becomes an error row instead of a wasted request.
2. **Look up the hashtag** — confirms the tag exists and reports its total post count on Instagram. A missing tag returns a clear error row.
3. **Page the ranked lists** — Top posts and/or Reels are fetched page by page.
4. **Deduplicate** — Instagram's Top and Reels lists are live ranked sets whose pages overlap heavily. Every post is deduplicated by ID, so you are never charged twice for the same post, and paging stops as soon as the list stops producing anything new.
5. **Push flat rows** — each post becomes one dataset row, ready for export.

***

### Input Configuration

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `hashtags` | array | Yes | — | Hashtags to scrape. Bare names, `#tags`, or explore URLs. |
| `resultsType` | string | No | `top` | `top`, `reels`, or `both`. |
| `maxPostsPerHashtag` | integer | No | unlimited | Cap on posts collected per hashtag. |
| `postedAfter` | string | No | — | Keep only posts published on or after this date. `YYYY-MM-DD` or an ISO timestamp. |
| `postedBefore` | string | No | — | Keep only posts published on or before this date. A bare `YYYY-MM-DD` includes the whole day. |
| `onlyVideos` | boolean | No | `false` | Skip photo posts, return videos and reels only. |
| `includeAudio` | boolean | No | `false` | Add the sound behind each reel: title, artist, audio id, duration, and whether Instagram marks it trending. Ten reels per request. |

When `resultsType` is `both`, `maxPostsPerHashtag` is split evenly across the two lists, so a cap of 50 returns roughly 25 top posts and 25 reels rather than 50 top posts and nothing else.

#### How far back a hashtag reaches

Instagram's Top and Reels lists are ranked by recency, not paged history, so how far back a run
reaches is set by how busy the hashtag is — not by how hard you page. Measured 2026-08-28 at 100
posts per hashtag:

| Hashtag | Window covered by 100 posts |
|---|---|
| `#travel`, `#dance` | under 1 day |
| `#workout` | ~1.5 days |
| `#coffee` | ~3.5 days |
| `#recipe` | ~4.8 days |

Depth buys some history, but far less than it looks: `#travel` at 100 posts reached back 25 hours,
and at **600** posts still only 31 hours — every one of those 600 came from the same two calendar
days. If you need yesterday on a busy tag, raise `maxPostsPerHashtag`; if you need last month, no
setting will get there, because Instagram does not serve it.

`postedAfter` and `postedBefore` filter what those lists return. They cannot reach past the window
above. Posts excluded by the filter are never charged, and the run's status message reports how
many were skipped. If the whole requested range falls outside what a tag reaches, you get an error
row naming the window that *was* available rather than an empty dataset.

#### Example — top posts only, capped

```json
{
  "hashtags": ["coffee"],
  "resultsType": "top",
  "maxPostsPerHashtag": 50
}
```

#### Example — reels only, for video trend research

```json
{
  "hashtags": ["latteart", "espresso", "coldbrew"],
  "resultsType": "reels",
  "maxPostsPerHashtag": 100,
  "onlyVideos": true
}
```

***

### Output Overview

One dataset row per post. Every row identifies the hashtag it came from and which list it was found in (`top` or `reels`), then carries the post itself: identifiers and URL, media type, timestamp, caption, engagement counts, video metadata, and the creator's public profile fields.

Rows for hashtags that could not be scraped are returned as error rows carrying `hashtag`, `error`, and `message` — these are never charged.

***

### Output Samples

Real row, produced from `hashtags: ["latteart"]` with `resultsType: "both"` (CDN URLs shortened):

```json
{
  "hashtag": "latteart",
  "resultsType": "top",
  "postId": "3950574492081015707",
  "shortCode": "DbTQpHLNUub",
  "postUrl": "https://www.instagram.com/p/DbTQpHLNUub/",
  "mediaType": "video",
  "productType": "clips",
  "takenAt": "2026-07-27T15:13:56Z",
  "caption": "#barista #home #coffee #latteart #kaffee",
  "likeCount": 16,
  "commentCount": 3,
  "playCount": 258,
  "videoDuration": 13.418999671936035,
  "isPaidPartnership": false,
  "ownerUsername": "baris_tant",
  "ownerFullName": "Baristant",
  "ownerId": "14523707386",
  "ownerProfilePicUrl": "https://scontent-lga3-2.cdninstagram.com/v/t51.82787-19/...",
  "thumbnailUrl": "https://scontent-lga3-1.cdninstagram.com/v/t51.82787-15/...",
  "videoUrl": "https://scontent-lga3-1.cdninstagram.com/o1/v/t2/f2/m86/...",
  "locationName": null,
  "taggedUsernames": []
}
```

Error row for a tag with nothing to scrape — misspelled, brand new, or restricted. Instagram
reports a post count of 0 for all three, so the row names the tag rather than guessing which:

```json
{
  "hashtag": "thistagdoesnotexistatall",
  "error": "hashtag_empty",
  "message": "Hashtag #thistagdoesnotexistatall has no public posts on Instagram (Instagram reports 0). It may be misspelled, brand new, or restricted."
}
```

A tag Instagram says *does* have posts but which serves none back gets `error: "no_posts_returned"`
instead, and that run is reported as failed — an empty result there is not something the run can
prove is legitimate.

A tag whose posts all fall outside `postedAfter` / `postedBefore` gets
`error: "date_range_out_of_reach"`, carrying the window the ranked list actually reached:

```json
{
  "hashtag": "travel",
  "error": "date_range_out_of_reach",
  "message": "Hashtag #travel: 234 post(s) found, all outside the requested date range. Instagram's Top and Reels lists for this tag currently reach 2026-08-27T11:59:44.000Z to 2026-08-28T13:10:18.000Z. Widen the range, or raise maxPostsPerHashtag — more depth reaches a little further back, but these lists are ranked by recency and cannot be paged into older history."
}
```

***

### Key Output Fields

**Source**
`hashtag`, `resultsType`

**Post identity**
`postId`, `shortCode`, `postUrl`, `mediaType`, `productType`, `takenAt`

**Content**
With `includeAudio` on, reel rows also carry `musicId`, `musicTitle`, `musicArtist`, `musicIsOriginalAudio`, `musicDurationMs` and `audioIsTrending`. Photos and carousels have no sound and leave all six null.

`caption`, `taggedUsernames`, `locationName`, `isPaidPartnership`

**Engagement**
`likeCount`, `commentCount`, `playCount`

**Media**
`thumbnailUrl`, `videoUrl`, `videoDuration`

**Creator**
`ownerUsername`, `ownerFullName`, `ownerId`, `ownerProfilePicUrl`

***

### FAQ

**Can I get recent / chronological posts for a hashtag?**
No, and no actor can reliably. Instagram stopped serving a public chronological hashtag feed — the underlying endpoint returns an empty page for every tag. Rather than ship a "Recent" option that silently returns nothing, this actor offers Top and Reels only.

**How many posts can I get per hashtag?**
Instagram's Top and Reels lists are ranked sets of limited depth, not full archives. Expect a few hundred posts per hashtag in practice. Setting `maxPostsPerHashtag` to 10,000 will not produce 10,000 rows — paging stops once the list stops returning new posts.

**Why do I sometimes get fewer posts than I asked for?**
Either the hashtag's ranked list ran out, or `onlyVideos` filtered out photo posts. Both are logged.

**Is the same post ever returned twice?**
Not within a hashtag. Instagram's pages overlap heavily and the actor deduplicates by post ID, so you are never billed twice for the same post. A post tagged with two of your hashtags does appear once under each, because each row is scoped to its tag.

**Does it return comments or the list of likers?**
No. This actor returns post-level data including comment and like *counts*. For comment text see the Instagram Comments Scraper, and for the people who liked a post see the Instagram Post Likers Scraper.

**Do I need an Instagram account, login, or cookies?**
No. Nothing is authenticated and no session is used, so there is no account to get flagged.

**What happens with a private or restricted hashtag?**
You get an error row explaining it, and you are not charged for it.

***

### Support

Found a bug or need a field that is not there? Open an issue on the actor's **Issues** tab and it will be looked at.

Email: muhamed.didovic@gmail.com

***

### Additional Services

Need a custom scraper, a tailored export format, or an integration into your own pipeline? Custom builds are available — get in touch at muhamed.didovic@gmail.com.

***

### Explore More Scrapers

- **Instagram Profile Scraper** — profiles, posts and comments from any public account
- **Instagram Stories & Highlights Scraper** — active stories and permanent highlights
- **Instagram Followers & Following Scraper** — full follower and following lists
- **Instagram Post Likers Scraper** — who liked any public post or reel
- **Instagram Comments Scraper** — full comment threads with replies
- **Instagram Similar Profiles & Lookalike Finder** — accounts similar to any profile
- **Instagram Reels Search & Viral Finder** — find trending reels by keyword

Browse the full collection at [apify.com/memo23](https://apify.com/memo23).

***

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/instagram-hashtag-scraper`).

**Purpose:** Scrapes public Instagram hashtags into post-level rows (Top posts and/or Reels), with automatic cross-page deduplication.

**Minimal input:**

```json
{ "hashtags": ["coffee"], "resultsType": "top", "maxPostsPerHashtag": 20 }
```

**Output:** one dataset row per post — hashtag, resultsType, postId, shortCode, postUrl, mediaType, productType, takenAt, caption, likeCount, commentCount, playCount, videoDuration, isPaidPartnership, ownerUsername, ownerFullName, ownerId, ownerProfilePicUrl, thumbnailUrl, videoUrl, locationName, taggedUsernames, and (with includeAudio) musicId, musicTitle, musicArtist, musicIsOriginalAudio, musicDurationMs, audioIsTrending. Failed hashtags return `{hashtag, error, message}` instead.

**Behaviors an agent should know:**

- Always set `maxPostsPerHashtag`. Uncapped runs page until Instagram stops returning new posts.
- Instagram's ranked lists are shallow — expect a few hundred posts per tag, not thousands, regardless of the cap.
- With `resultsType: "both"` the cap is split evenly between Top and Reels.
- There is no "recent" mode; Instagram's public chronological hashtag feed returns nothing, so only `top`, `reels`, and `both` exist.
- Billing: one `post` event per post row delivered. Error rows are never charged. Duplicate posts within a hashtag are deduplicated before billing.
- No login is used, so no authenticated-only fields (viewer state, private accounts) are available.

***

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Meta Platforms, Inc. or any of its subsidiaries. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available Instagram hashtag pages — no authenticated endpoints, paid features, or content behind the instagram.com login wall. Users are responsible for ensuring their use complies with instagram.com's Terms of Service, applicable data-protection law (GDPR, CCPA, etc.), and any contractual obligations of their own organization.

***

### SEO Keywords

instagram hashtag scraper, scrape instagram hashtag, instagram hashtag API, instagram.com scraper, Apify instagram, hashtag posts scraper, instagram reels scraper, instagram top posts scraper, social media scraper, hashtag analytics data, instagram engagement data, instagram trend research, user generated content scraper, influencer discovery data, instagram content strategy, brand monitoring scraper, social listening data, instagram marketing intelligence, no login instagram scraper

# Actor input Schema

## `hashtags` (type: `array`):

Instagram hashtags to scrape. Accepts bare names (coffee), #-prefixed tags (#coffee), or explore URLs (https://www.instagram.com/explore/tags/coffee/) — one per line, mixed formats are fine. Any tag that is misspelled, has no public posts, or cannot be read comes back as an error row naming that tag, so a short result is never a mystery.

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

Top posts (Instagram's ranked best-performing posts for the tag), Reels (short videos tagged with it), or both. A post that appears in both lists is returned — and charged — only once.

## `maxPostsPerHashtag` (type: `integer`):

Cap how many posts to collect per hashtag. Leave empty for as many as Instagram will serve. Note Instagram's top/reels lists are live ranked sets of limited depth — very large numbers will stop early once the list stops returning new posts.

## `postedAfter` (type: `string`):

Keep only posts published on or after this date. Accepts YYYY-MM-DD or a full ISO timestamp. Note this filters what Instagram serves, it cannot reach further back: Top and Reels are ranked by recency, so a busy hashtag may only reach back a day and a quiet one under a week. Raising Max posts per hashtag reaches a little further. Excluded posts are never charged.

## `postedBefore` (type: `string`):

Keep only posts published on or before this date. A bare YYYY-MM-DD includes the whole of that day. Excluded posts are never charged.

## `onlyVideos` (type: `boolean`):

Skip photo posts and return only videos and reels. Applies to both result types. Default: off.

## `includeAudio` (type: `boolean`):

Add the sound behind each reel: track or original-audio title, artist, audio id, duration, and whether Instagram marks the sound as trending. Fetched ten reels per request, so it adds little time. Photos and carousels carry no audio and stay empty.

## Actor input object example

```json
{
  "hashtags": [
    "coffee",
    "#latteart",
    "https://www.instagram.com/explore/tags/espresso/"
  ],
  "resultsType": "top",
  "maxPostsPerHashtag": 100,
  "postedAfter": "2026-08-01",
  "postedBefore": "2026-08-28",
  "onlyVideos": false,
  "includeAudio": false
}
```

# Actor output Schema

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

No description

# 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 = {
    "hashtags": [
        "coffee"
    ],
    "maxPostsPerHashtag": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/instagram-hashtag-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 = {
    "hashtags": ["coffee"],
    "maxPostsPerHashtag": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/instagram-hashtag-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 '{
  "hashtags": [
    "coffee"
  ],
  "maxPostsPerHashtag": 100
}' |
apify call memo23/instagram-hashtag-scraper --silent --output-dataset

```

## MCP server setup

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