# Instagram Profile Scraper With Bio Link & Email Extraction (`scrapio/instagram-profile-scraper`) Actor

Scrapes full data from any public Instagram profile, capturing bio, username, follower and following counts, posts, Reels, highlights, profile image, engagement stats, and URLs. Ideal for influencer research, competitor analysis, brand insights, and large-scale Instagram profiling

- **URL**: https://apify.com/scrapio/instagram-profile-scraper.md
- **Developed by:** [Scrapio](https://apify.com/scrapio) (community)
- **Categories:** Lead generation, Social media, Videos
- **Stats:** 52 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$14.99/month + usage

To use this Actor, you pay a monthly rental fee to the developer. The rent is subtracted from your prepaid usage every month after the free trial period. You also pay for the Apify platform usage, which gets cheaper the higher Apify subscription plan you have.

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

## 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 Profile Data, Bio Links and Emails

Instagram Profile Scraper With Bio Link & Email Extraction pulls structured profile data, expanded bio links, and contact details — emails, phone numbers, and social handles — from any public Instagram profile, returned as typed JSON. Unlike scraping frameworks that return raw HTML, it hands back ready-to-use fields for your CRM, database, or LLM pipeline without any parsing step. Paste a username or profile URL and it fetches follower counts, bio text, and business metadata, expands Linktree- and Beacons-style aggregator links, and mines the bio for real, non-fabricated contact data. This guide covers every input option, output field, and deployment pattern for running it in production.

### 🧭 What Does Instagram Profile Scraper With Bio Link & Email Extraction Do?

Instagram Profile Scraper With Bio Link & Email Extraction turns any public Instagram profile into one structured JSON row. **No Instagram account or cookie is required.** By default it reads Instagram's public, server-rendered profile page — the same content Instagram publishes for search engines and link previews — which carries the bio, the link-in-bio, exact follower/following counts, post count, and the verified/private flags. An optional `sessionId` cookie unlocks a few extra fields (business-account metadata and recent posts) but is never needed for the core contact-mining use case. On top of the base profile fields, this variant adds two features: contact extraction from the bio and external link, and destination-link expansion for link-aggregator pages.

- Full profile metadata: username, full name, biography, follower/following counts, posts count, verified and business-account flags, business category
- Contact mining: `emails[]`, `phoneNumbers[]`, and `socialHandles{}` (WhatsApp, Telegram, Linktree handle) extracted from the bio and external link
- Link-aggregator expansion: destination URLs behind Linktree, Beacons, linkin.bio, campsite.bio, msha.ke, Taplink, AllMyLinks, and other aggregator hosts
- Optional About data: join date and country when `includeAboutSection` is enabled
- Related profiles, latest IGTV videos, and latest posts (captions, hashtags, mentions, media) returned alongside the profile
- Honest nulls: emails, phones, and expanded links are only ever emitted when actually found — never fabricated placeholders

### ⚡ Features & Capabilities

The Actor's capabilities split into the base profile fetch, the contact-mining layer built on top of it, and how it sits alongside the rest of the Scrapio Instagram lineup.

#### Core features

- **Login-free public fetch (default)** — requests the profile page as a non-browser client, which makes Instagram return its fully server-rendered page instead of a JavaScript shell. The Actor reads both the embedded Relay prefetch payload (exact `follower_count`, `following_count`, `biography`, `bio_links`, `is_verified`, `is_private`, numeric user id, highlights) and the `og:`/`description` meta tags (post count, bio, display name). This is a plain HTTP request — typically ~2 seconds per profile, no browser needed
- **Optional interception-based fetch** — when a `sessionId` is supplied, a Playwright browser intercepts Instagram's authenticated GraphQL profile response for the additional business/posts fields
- **Regex-based contact extraction** (`contacts.py`) — pulls `emails[]` via an RFC-shaped email pattern, `phoneNumbers[]` via an 8–15 digit E.164-plausible pattern, and `socialHandles{}` for WhatsApp (`wa.me`/`api.whatsapp.com` links or plain-text "WhatsApp: +...") , Telegram (`t.me`/`telegram.me`), and Linktree usernames
- **Link-aggregator detection and expansion** — recognizes 25 aggregator hosts (`linktr.ee`, `beacons.ai`, `linkin.bio`, `campsite.bio`, `msha.ke`, `taplink.cc`, `allmylinks.com`, `solo.to`, `bio.link`, `carrd.co`, and more) and fetches the aggregator page itself, parsing embedded JSON and anchor tags to return real destination links in `expandedLinks`
- **Optional sessionId authentication** — a Playwright cookie injection (`sessionid` + derived `ds_user_id`) that adds the session-only fields; if the session fails or expires, the Actor automatically falls back to the public path rather than failing the row
- **Honest provenance on every row** — `dataSource` (`public-html` or `authenticated-graphql`), `partialData`, `sessionOnlyFields` (which fields were null because no session was supplied), and `approximateFields` (counts Instagram only published rounded, e.g. `32K Posts`)
- **Residential proxy by default** — `setup_proxy()` defaults to Apify's `RESIDENTIAL` proxy group unless the caller supplies their own `proxyConfiguration`, with 3 retry attempts (`RETRY_ATTEMPTS`) on timeout or error per profile
- **Per-profile fault isolation** — one profile failing (timeout, invalid username, unavailable account) is logged and skipped; it does not abort the rest of the run

#### Instagram Profile Scraper With Bio Link & Email Extraction within the Scrapio data stack

This Actor covers profile data, bio-link expansion, and bio-derived contacts for a known list of usernames or URLs. If you need to *discover* leads rather than enrich a known list, use Instagram Phone Number Scraper & Email Lead Finder or Instagram B2B Email Scraper: Business Type Leads, which search Google or Instagram's hashtag/mentions surfaces for candidate accounts first. For follower lists and shared-audience overlap, use Instagram Followers Scraper: Multi-Profile Analysis. For a scored, outreach-ready lead list instead of raw contact fields, use Instagram Profile Phone Number Scraper: Outreach Lead Scorer.

### Why do developers and data teams scrape Instagram?

#### 🏢 Lead generation and sales outreach teams

Sales and growth teams already have a list of relevant Instagram usernames — competitor followers, hashtag search results, a conference attendee list — and need to know which ones are reachable. Feed `profileTargets` with that list and read back `hasContact`, `emails`, `phoneNumbers`, and `socialHandles` per profile: `hasContact == true` rows go straight into an outreach queue, while `businessCategoryName` and `isBusinessAccount` help route business accounts differently from personal ones. Because `expandLinks` resolves Linktree/Beacons pages first, contacts hidden behind a link-in-bio page are captured too, not just what's visible in the raw `biography` text.

#### 📊 AI training data and RAG indexing

The `biography` field and the `caption`, `hashtags`, and `mentions` sub-fields on each `latestPosts` entry are the highest-information text fields for language tasks — bios carry a subject's self-description in a few dense sentences, and captions carry contextual, informal language tied to real media. For RAG enrichment, indexing `biography` plus `businessCategoryName` gives a retrieval system enough context to answer "who is this account and what do they do" without a separate summarization pass. For training data, `emails`, `phoneNumbers`, and `socialHandles` are useful as a labeled contact-detection dataset — every field arrives as a typed primitive (string, array, or boolean), so no HTML cleanup or regex re-extraction is needed before use.

#### 📱 Competitive and market intelligence

Track a competitor's Instagram presence over time by re-running the Actor against the same `profileTargets` on a schedule and diffing `followersCount`, `followsCount`, `postsCount`, and `externalUrl` between runs. A changed `externalUrl` often signals a new campaign or product launch before it's announced elsewhere, and a jump in `followersCount` combined with new `latestPosts` entries flags an active push worth investigating.

#### 🔬 Research and academic use

Public bio text, business-category distribution, and contact-availability rates (`hasContact`) across a sampled set of profiles support studies on social-commerce adoption, influencer marketing structure, or platform self-presentation norms. This Actor only returns data from profiles that are public at request time — private accounts return no usable data, keeping the dataset within publicly accessible scope.

#### 🎥 Product and SaaS development

Contact-enrichment products, influencer directories, and outreach-scoring tools can build directly on top of `emails`, `phoneNumbers`, `socialHandles`, and `expandedLinks` without writing their own regex or link-aggregator parser — both are already implemented and reused as-is from `src/contacts.py`.

### 🍚 Input Parameters

No input parameter is required — the Actor falls back gracefully but produces no rows if `profileTargets` is empty.

| Parameter | Required | Type | Description | Example Value |
| --- | --- | --- | --- | --- |
| `profileTargets` | No | array | Instagram profiles to mine for contact data. Accepts a full URL (`https://www.instagram.com/username/`), a bare username, `@username`, or `instagram.com/username`. The legacy `startUrls` array (`[{"url": "..."}]`) is also accepted as a fallback. | `["gymshark", "https://www.instagram.com/natgeo/"]` |
| `expandLinks` | No | boolean (default `true`) | When on, if a profile's `externalUrl` is a known link-aggregator page, the Actor fetches it and returns its destination links in `expandedLinks`. | `true` |
| `includeAboutSection` | No | boolean (default `false`) | When on, adds an `about` object with `joinedDate` and `country`, sourced from Instagram's own response. | `false` |
| `sessionId` | No | string (secret) | **Not required.** Optional Instagram `sessionid` cookie (format `<numeric_user_id>:xxxxx…`). Leave empty for the login-free public path (bio, link-in-bio, counts, verified/private, contacts). Supply it only to additionally fill `isBusinessAccount`, `businessCategoryName`, `hasChannel`, `joinedRecently`, `igtvVideoCount`, `profilePicUrlHD`, `latestPosts`, `latestIgtvVideos`, `relatedProfiles`. | *(left empty)* |
| `proxyConfiguration` | No | object | Apify proxy configuration. Defaults to residential proxy with automatic retries; supply your own Apify proxy groups or custom proxy URLs to override. | `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}` |

```json
{
  "profileTargets": ["gymshark", "https://www.instagram.com/natgeo/", "@nike"],
  "expandLinks": true,
  "includeAboutSection": false,
  "sessionId": "",
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

#### Supported URL types and input formats

- **Full profile URL** — `https://www.instagram.com/gymshark/` — parsed via `urlparse`, taking the last non-empty path segment as the username.
- **Bare username** — `natgeo` — used directly once `@` and slashes are stripped.
- **`@username`** — `@nike` — the leading `@` is stripped before the profile URL is built.
- **Legacy object form** — `{"url": "https://www.instagram.com/gymshark/"}` — still read from a `startUrls` array if you supply that key instead of `profileTargets`.

One honest limitation: pasting a bare domain path with no scheme (e.g. `instagram.com/username`, without `https://`) is not parsed the same way as a full URL in the current version — the username extractor treats it as a literal string rather than a URL, which can produce an incorrect username. Use a full `https://` URL, `@username`, or a bare username instead.

### 📦 Output Format

Every successfully scraped profile is pushed as one dataset row combining the base profile fields with the contact-extraction fields added by this variant. Output is a standard Apify dataset — typed JSON per row, with no schema drift between runs beyond Instagram adding or removing a field on its side.

#### Output for Profiles

```json
{
  "inputUrl": "https://www.instagram.com/gymshark",
  "url": "https://www.instagram.com/gymshark",
  "id": "174424367",
  "username": "gymshark",
  "fullName": "Gymshark",
  "biography": "The Original. 💪 Est. 2012 in a garage.\nShop the drop below 👇",
  "externalUrls": [
    { "url": "https://linktr.ee/gymshark", "link_type": "external", "title": "" }
  ],
  "externalUrl": "https://linktr.ee/gymshark",
  "externalUrlShimmed": "https://l.instagram.com/?u=https%3A%2F%2Flinktr.ee%2Fgymshark",
  "followersCount": 6500000,
  "followsCount": 210,
  "hasChannel": false,
  "highlightReelCount": 24,
  "isBusinessAccount": true,
  "joinedRecently": false,
  "businessCategoryName": "Sporting Goods",
  "private": false,
  "verified": true,
  "profilePicUrl": "https://scontent.cdninstagram.com/v/t51.2885-19/gymshark_profile.jpg",
  "profilePicUrlHD": "https://scontent.cdninstagram.com/v/t51.2885-19/gymshark_profile_hd.jpg",
  "igtvVideoCount": 0,
  "postsCount": 6100,
  "about": { "joinedDate": null, "country": null },
  "relatedProfiles": [
    { "id": "1798450931", "full_name": "Gymshark Women", "is_verified": false, "profile_pic_url": "https://scontent.cdninstagram.com/gymshark_women.jpg", "username": "gymsharkwomen", "is_private": false }
  ],
  "latestIgtvVideos": [],
  "latestPosts": [
    {
      "id": "3401234567890123456",
      "type": "Sidecar",
      "shortCode": "C9xY2zAOabc",
      "caption": "New drop is live. Link in bio.",
      "hashtags": ["gymshark", "trainlikeagymshark"],
      "mentions": [],
      "url": "https://www.instagram.com/p/C9xY2zAOabc/",
      "commentsCount": 412,
      "dimensionsHeight": 1350,
      "dimensionsWidth": 1080,
      "displayUrl": "https://scontent.cdninstagram.com/gymshark_post.jpg",
      "alt": "Photo by Gymshark",
      "likesCount": 28450,
      "timestamp": "2026-07-20T09:15:00.000Z",
      "ownerUsername": "gymshark",
      "ownerId": "174424367",
      "images": ["https://scontent.cdninstagram.com/gymshark_post.jpg"],
      "childPosts": [],
      "taggedUsers": []
    }
  ],
  "emails": ["support@gymshark.com"],
  "phoneNumbers": [],
  "socialHandles": { "linktree": "gymshark" },
  "expandedLinks": [
    "https://gymshark.com/collections/new",
    "https://gymshark.com/pages/gymshark-training-app"
  ],
  "hasContact": true
}
```

#### Output for Bio Links & Contact Data

These fields are embedded in the same row above but are worth calling out separately since they are this variant's core differentiator over a plain profile scrape:

```json
{
  "externalUrl": "https://linktr.ee/gymshark",
  "expandedLinks": [
    "https://gymshark.com/collections/new",
    "https://gymshark.com/pages/gymshark-training-app"
  ],
  "emails": ["support@gymshark.com"],
  "phoneNumbers": [],
  "socialHandles": { "linktree": "gymshark" },
  "hasContact": true
}
```

`expandedLinks` is `null` (not `[]`) when `expandLinks` is off, the external link isn't a recognized aggregator, or the aggregator fetch failed — `[]` means the aggregator page was fetched but no destination links were found. `socialHandles` is `null` when none of WhatsApp, Telegram, or a Linktree handle were found in the text.

#### Which fields need a session cookie?

Every row carries a `dataSource` field so you always know what you're looking at.

| Field | Logged out (default) | With `sessionId` |
| --- | --- | --- |
| `username`, `fullName`, `biography` | ✅ | ✅ |
| `externalUrl`, `externalUrls`, `externalUrlShimmed` | ✅ | ✅ |
| `followersCount`, `followsCount` | ✅ exact | ✅ exact |
| `postsCount` | ✅ (rounded on some large accounts — see `approximateFields`) | ✅ exact |
| `verified`, `private`, `profilePicUrl`, `id`, `highlightReelCount` | ✅ | ✅ |
| `emails`, `phoneNumbers`, `socialHandles`, `expandedLinks`, `hasContact` | ✅ | ✅ |
| `isBusinessAccount`, `businessCategoryName` | ❌ `null` | ✅ |
| `hasChannel`, `joinedRecently`, `igtvVideoCount`, `profilePicUrlHD` | ❌ `null` | ✅ |
| `latestPosts`, `latestIgtvVideos`, `relatedProfiles` | ❌ `null` | ✅ |

Session-only fields are emitted as `null`, never as `0`/`false`/`[]`, so a partial row can never be mistaken for a real profile that genuinely has zero posts or no related profiles. The row also lists them explicitly in `sessionOnlyFields`.

```json
{
  "dataSource": "public-html",
  "partialData": true,
  "sessionOnlyFields": ["isBusinessAccount", "businessCategoryName", "hasChannel", "joinedRecently", "igtvVideoCount", "profilePicUrlHD", "latestPosts", "latestIgtvVideos", "relatedProfiles"],
  "approximateFields": ["postsCount"]
}
```

#### Schema stability and export options

Field names stay stable across runs because they come from this Actor's own `USER_FIELDS` mapping, which understands both of Instagram's live data shapes — the public page's payload (`follower_count`, `bio_links`, `pk`, …) and the authenticated GraphQL response (`edge_followed_by.count`, `is_business_account`, …) — and normalizes them to one output schema. A redesign of Instagram's front end doesn't change the output field names. Results are stored in a standard Apify dataset and can be exported as JSON, CSV, Excel, XML, RSS, or an HTML table from the Apify Console or API, or pulled programmatically with the Apify API/SDK. Only rows that are successfully built and pushed are billed under the `row_result` charged event — profiles that fail to load or return no user data are logged and skipped without being pushed, so they are never charged.

### 💡 Instagram Profile Scraper With Bio Link & Email Extraction Strategy Guide

#### 🎯 Strategy 1: Real-time enrichment pipeline

Trigger a run whenever your team adds a new Instagram username to a CRM or spreadsheet: call the Actor with `profileTargets` set to just that username. Read back `hasContact`, `emails`, `phoneNumbers`, `socialHandles`, and `expandedLinks`, and write them onto the existing lead record keyed by `username`. Because `hasContact` is a single boolean, it's a fast filter for a downstream "needs outreach" queue without inspecting every array field individually.

#### 🎯 Strategy 2: Scheduled monitoring and alerting

For a watchlist of accounts (competitors, partners, or influencers), set up an Apify Console Schedule to re-run the Actor daily or weekly against the same `profileTargets`. Store each run's output keyed by `username` and diff `followersCount`, `externalUrl`, and `biography` against the previous run. Alert on a changed `externalUrl` (often a new campaign link) or a `followersCount` jump, rather than alerting on every run regardless of whether anything changed.

#### 🎯 Strategy 3: Bulk dataset build

For a research dataset or a large enrichment backlog, split your full username list into batches and launch one Actor run per batch via the Apify API rather than passing the entire list to a single run — the Actor processes profiles sequentially within a run (one Playwright browser session per profile, not in parallel), so several smaller concurrent runs finish a large list faster than one large run. Aggregate each run's dataset into your warehouse or a CSV export once all runs report a terminal status.

#### Strategy comparison at a glance

| Strategy | Best for | Run pattern | Output format |
| --- | --- | --- | --- |
| Real-time enrichment | Single new leads as they appear | On-demand, single `profileTargets` entry | JSON row appended to CRM/record |
| Scheduled monitoring | Watchlists tracked over time | Apify Console Schedule, recurring | JSON diffed against prior run |
| Bulk dataset build | Large one-off lists | Multiple parallel runs, batched input | Aggregated JSON/CSV export |

### 🌴 Related Instagram Scrapers & Tools

| Scraper Name | What it extracts |
| --- | --- |
| Instagram Phone Number Scraper & Email Lead Finder | Emails and phone numbers discovered via Google-indexed Instagram pages, no login needed |
| Instagram B2B Email Scraper: Business Type Leads | Business/personal emails via hashtag search, mentions mining, or keyword SERP discovery |
| Instagram Profile Phone Number Scraper: Outreach Lead Scorer | Same SERP-based contact mining, plus a 0–100 outreach lead score and grade |
| Instagram Related Person By Niche Content Creator Search | Related/suggested profiles around a seed account, filtered and ranked as niche influencers |
| Instagram Followers Scraper: Multi-Profile Analysis | Follower lists per profile plus shared-follower overlap across multiple profiles |
| Instagram Highlights Scraper: Content Extraction | Highlight metadata and, with a session cookie, full story-item content per highlight |
| LinkedIn B2B Emails Scraper By Phone & Email Finder | The same contact-mining pattern applied to public LinkedIn profiles instead of Instagram |
| Extract Emails Contacts Socials: Verified Phone & Email List | Multi-platform (including Instagram) contact cards — emails, phones, and social profile links |
| Instagram Hashtag & Engagement Scraper | Posts and reels by hashtag, ranked by derived engagement metrics |
| Instagram Comments Scraper With Engagement Analytics | Comments and replies on a post/reel with per-comment engagement scoring |

### How to integrate Instagram Profile Scraper With Bio Link & Email Extraction with your stack

Instagram Profile Scraper With Bio Link & Email Extraction works with any language or tool that can call the Apify API — there is no bespoke API surface beyond standard Actor run and dataset endpoints.

#### Python

```python
import csv
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

profiles = ["gymshark", "natgeo", "nike"]

run_input = {
    "profileTargets": profiles,
    "expandLinks": True,
    "includeAboutSection": False,
}

run = client.actor(
    "Scrapio/instagram-profile-scraper-with-bio-link-email-extraction"
).call(run_input=run_input)

rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())

with open("instagram_contacts.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["username", "hasContact", "emails", "phoneNumbers", "expandedLinks"])
    for row in rows:
        writer.writerow([
            row.get("username"),
            row.get("hasContact"),
            ";".join(row.get("emails") or []),
            ";".join(row.get("phoneNumbers") or []),
            ";".join(row.get("expandedLinks") or []) if row.get("expandedLinks") else "",
        ])

print(f"Wrote {len(rows)} profile rows to instagram_contacts.csv")
```

#### Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });

const run = await client.actor(
  'Scrapio/instagram-profile-scraper-with-bio-link-email-extraction'
).call({
  profileTargets: ['gymshark', 'natgeo'],
  expandLinks: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const withContact = items.filter((row) => row.hasContact);

console.log(`${withContact.length} of ${items.length} profiles returned a contact`);
```

#### Async and scheduled pipelines

There is no webhook-based delivery built into this Actor's own code, so the supported pattern is polling: start a run via the API, poll `client.run(runId).get()` (or its Node equivalent) until the status is terminal, then read the dataset. For recurring monitoring, use an Apify Console Schedule to fire the same `profileTargets` on a cron cadence instead of looping a script.

### 🎯 Who Needs Instagram Profile Scraper With Bio Link & Email Extraction? (Use Cases & Industries)

#### 🏢 Sales and outreach teams

A sales team building a target list of boutique fitness brands feeds their Instagram usernames into `profileTargets` and reads back `emails`, `phoneNumbers`, and `businessCategoryName` to prioritize which accounts have a reachable contact before a single message is sent.

#### 📊 AI and RAG engineering teams

Teams building a retrieval index over creator or brand accounts pull `biography`, `businessCategoryName`, and `latestPosts[].caption` as high-density text fields, and use `emails`/`phoneNumbers`/`socialHandles` as a structured, pre-labeled contact-detection example set — no additional cleaning required.

#### 📱 Competitive intelligence analysts

An analyst tracking three competitor brand accounts re-runs the Actor weekly against the same `profileTargets` and watches `followersCount`, `externalUrl`, and `postsCount` for the week-over-week deltas that signal a new campaign or product push.

#### 🔬 Researchers

Academic researchers studying influencer marketing or business self-presentation on Instagram sample public profiles for `biography` text, `businessCategoryName` distribution, and contact-availability rate (`hasContact`) — scope stays limited to what is public at request time.

#### 🎥 Product and SaaS developers

Developers building a contact-enrichment or influencer-directory product call this Actor as their Instagram enrichment layer, using `emails`, `phoneNumbers`, `socialHandles`, and `expandedLinks` directly rather than building and maintaining their own bio-parsing and link-aggregator logic.

### Is it legal to scrape Instagram?

Scraping publicly accessible Instagram data is generally lawful in the United States: in *hiQ Labs, Inc. v. LinkedIn Corp.* (9th Cir. 2019, cert. denied 2022), the court held that scraping data a website makes publicly available does not violate the Computer Fraud and Abuse Act. That precedent addresses **access**, not Instagram's own Terms of Service — scraping in violation of Instagram's ToS can still expose you to civil claims (breach of contract, account suspension) even where no criminal statute is implicated. Separately, because this Actor extracts profile bios, external links, and derived emails/phone numbers, it returns **personal data** for individuals and business contacts alike, which brings GDPR (EU/UK) and CCPA/CPRA (California) obligations into play for anyone storing or processing it — lawful basis, retention limits, and deletion requests are the operator's responsibility, not something the Actor manages. Instagram Profile Scraper With Bio Link & Email Extraction returns only publicly accessible data. What you do with that data is your responsibility — consult legal counsel for commercial applications involving personal data.

### ❓ Frequently asked questions

#### Does Instagram Profile Scraper With Bio Link & Email Extraction work without an Instagram account?

Yes — completely, and that is the default mode. Instagram serves a fully server-rendered profile page to non-browser clients (this is what powers Google results and link previews), and the Actor reads the profile data straight out of it. Logged out you get `username`, `fullName`, `biography`, `externalUrl`/`externalUrls`, `followersCount`, `followsCount`, `postsCount`, `verified`, `private`, `profilePicUrl`, `highlightReelCount`, and every contact field. Only `isBusinessAccount`, `businessCategoryName`, `hasChannel`, `joinedRecently`, `igtvVideoCount`, `profilePicUrlHD`, `latestPosts`, `latestIgtvVideos` and `relatedProfiles` require a `sessionId`; without one they are returned as `null` rather than as misleading zeros.

#### How does it handle Instagram's anti-scraping measures?

The default path is a single lightweight HTTP request per profile routed through Apify's residential proxy pool, with a fresh IP per retry — there is no browser to fingerprint and no login wall to hit, so it is both faster and more robust than browser automation. A Chromium/Playwright session is only launched when you supply a `sessionId`, and if that authenticated attempt fails the Actor falls back to the public path instead of failing the row.

#### Are the follower counts exact?

Yes in the normal case. The exact integers come from the embedded payload on the public page (e.g. `8,624,703` for @gymshark, matching the logged-in value). If Instagram only publishes a rounded value for a particular field on a particular profile (such as `32K Posts`), the Actor still returns the number but names that field in the row's `approximateFields` array, so rounded values are never presented as exact.

#### Can I run it at scale without getting blocked?

Profiles within a single run are processed one at a time, each with its own browser session, rather than in parallel — there is no documented concurrency or rate limit built into the Actor's code beyond the 3-attempt retry per profile. For large lists, splitting input across multiple parallel runs is the documented approach in the Strategy Guide above, rather than assuming unlimited throughput from one run.

#### How fresh is the data it returns?

Fully live per run — every profile is fetched fresh from Instagram at the moment the Actor runs. Nothing is cached or reused between runs; re-running against the same username always issues a new request.

#### Is it legal to scrape public Instagram profiles?

Yes, in the sense that *hiQ Labs, Inc. v. LinkedIn Corp.* (9th Cir. 2019) establishes that accessing publicly available web data is not a CFAA violation — but that doesn't override Instagram's Terms of Service, and it doesn't remove your obligations under data protection law once you're handling personal contact data. See "Is it legal to scrape Instagram?" above for the full breakdown.

#### Which fields work best for AI training and RAG indexing?

For RAG: `biography`, `businessCategoryName`, and `latestPosts[].caption` carry the most descriptive natural-language content per profile. For training data: `emails`, `phoneNumbers`, and `socialHandles` are the most structurally consistent fields across records — each returns as a typed array, object, or `null`, with no HTML entities or inconsistent formatting to normalize before use.

#### Does this Actor return personal data, and who is responsible for handling it lawfully?

Yes — biographies, external links, and any extracted emails, phone numbers, or social handles are personal data under GDPR and CCPA/CPRA when they identify an individual. The Actor returns only publicly available data at the time of the run; establishing a lawful basis for storing, processing, or contacting people from that data is the responsibility of whoever runs the Actor, not the Actor itself.

#### Does it work with Claude, ChatGPT, and other AI agent tools?

There is no MCP server for this Actor in its current configuration, but it is callable as a standard HTTP endpoint through the Apify API by any agent framework that can make a REST call — every response is typed JSON, so no HTML parsing is needed before passing results into an LLM context window.

#### What happens if the profile's external link isn't a recognized link-aggregator?

`expandedLinks` is returned as `null`, not an empty array or a guess — the Actor only expands a fixed, known list of aggregator hosts (Linktree, Beacons, linkin.bio, and others). A personal website or a direct e-commerce link is left as-is in `externalUrl` and is not expanded.

#### What happens if a profile is private or doesn't exist?

The Actor logs the failure (invalid username, private/unavailable account, or Instagram returning no user object) and skips that profile — it does not stop the rest of the run, and no row is pushed or charged for a profile that fails to resolve.

### ℹ️ Disclaimer

Instagram Profile Scraper With Bio Link & Email Extraction extracts only publicly available data from Instagram. This tool is intended for lawful use cases only. Users are responsible for complying with Instagram's terms of service and applicable data protection laws in their jurisdiction.

# Actor input Schema

## `profileTargets` (type: `array`):

📥 **Instagram profiles whose bio + external link you want contact data from.**

Accepts any of:
• 🌐 Full URL: `https://www.instagram.com/username/`
• 👤 Username only: `username`
• 🔗 `instagram.com/username` or `@username`

Examples: `natgeo`, `https://www.instagram.com/gymshark/`

💡 Backward-compatible: a legacy `startUrls` array is still accepted if you supply it instead.

## `expandLinks` (type: `boolean`):

🔗 **When ON, if a profile's external link is a link-aggregator** (linktr.ee, beacons.ai, linkin.bio, campsite.bio, msha.ke, taplink, allmylinks, …) **the actor fetches that page and returns its destination links** in `expandedLinks`.

The aggregator page is a normal website (not Instagram), so this fetch is independent of Instagram's login-wall. If the fetch is blocked, `expandedLinks` is emitted as `null` — never fabricated.

## `includeAboutSection` (type: `boolean`):

🔍 When enabled, also emits the account's join date and country (from Instagram's response), in addition to the contact fields.

## `sessionId` (type: `string`):

🔑 **Optional. Leave this empty unless you need the extra fields below.**

Without a session the Actor reads Instagram's public server-rendered profile page and returns: `username`, `fullName`, `biography`, `externalUrl` + `externalUrls` (link-in-bio), `followersCount`, `followsCount`, `postsCount`, `verified`, `private`, `profilePicUrl`, `highlightReelCount` — plus all the contact fields (`emails`, `phoneNumbers`, `socialHandles`, `expandedLinks`).

➕ Adding a valid `sessionid` cookie (format `<numeric_user_id>:xxxxx…`) additionally fills these, which are **null** when logged out: `isBusinessAccount`, `businessCategoryName`, `hasChannel`, `joinedRecently`, `igtvVideoCount`, `profilePicUrlHD`, `latestPosts`, `latestIgtvVideos`, `relatedProfiles`.

Every row reports `dataSource` (`public-html` / `authenticated-graphql`) and `partialData` so you always know which you got.

⚠️ Treat a session cookie as a secret.

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

⚙️ Proxy configuration. Defaults to residential proxy with automatic retries for reliability; supply your own Apify proxy groups or custom proxy URLs here to use them instead.

## Actor input object example

```json
{
  "profileTargets": [
    "https://www.instagram.com/gymshark/"
  ],
  "expandLinks": true,
  "includeAboutSection": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All scraped items in the Actor's default dataset.

# 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 = {
    "profileTargets": [
        "https://www.instagram.com/gymshark/"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapio/instagram-profile-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 = {
    "profileTargets": ["https://www.instagram.com/gymshark/"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapio/instagram-profile-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 '{
  "profileTargets": [
    "https://www.instagram.com/gymshark/"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call scrapio/instagram-profile-scraper --silent --output-dataset

```

## MCP server setup

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