# Twitter (X) Profile Posts Scraper (`outspoken_strategy/twitter-profile-posts-scraper`) Actor

Paste an X profile URL and get that account's posts for any period. Date-range filtering, Posts/Replies/Media tabs, retweet, reply, language and engagement filters, auto-pagination. Returns text, author, likes, retweets, replies, quotes, views and media.

- **URL**: https://apify.com/outspoken\_strategy/twitter-profile-posts-scraper.md
- **Developed by:** [code craker](https://apify.com/outspoken_strategy) (community)
- **Categories:** Social media, News, Other
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 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

## Twitter (X) Profile Posts Scraper

Paste an **X/Twitter profile URL** and get that account's posts — for any period you choose. No official API needed. The actor opens the profile, auto-paginates, and exports every matching post as structured JSON: text, author, likes, retweets, replies, quotes, views, media, hashtags and timestamps. Download as JSON, CSV or Excel, or read it from the Apify API.

The output is built for pipelines: flat items with `id_str`, `url`, `text`, `created_at`, engagement counts and a full `user` author object.

### Features

- 🔗 **Just paste a profile URL** — `https://x.com/nasa`, `@nasa` or `nasa` all work, and so does any link on the profile (a post URL resolves to its author). Scrape many profiles per run.
- 📅 **Date-range filtering** — `From`/`To` dates, both inclusive. Give a range and the actor automatically switches to X advanced search (`from:handle since: until:`), which **jumps straight to the period** instead of scrolling the whole timeline back to it.
- 🗂 **Posts / Posts & replies / Media tabs** — pick which part of the profile to read.
- 🎛 **Content filters** — replies, retweets, pinned post, language, has-media, has-links, questions, and minimum likes / retweets / replies.
- ♾ **Auto-pagination** — scrolls until your target count is reached, the date range is passed, or the feed runs dry (up to 5,000 posts).
- 💾 **Incremental results** — posts are pushed as each profile finishes, so long runs never lose collected data to a timeout.
- 🔐 **Flexible authentication** — session cookies, secret env vars, or fully automated login with session caching.
- 🛠 **Debuggable failures** — on empty runs the actor saves a screenshot and the page HTML to the run's key-value store.

### Use cases

- Tracking a competitor's, executive's or brand's posting activity over a period
- Building a dated archive of an account's posts
- Social listening, sentiment analysis and engagement benchmarking
- Market and academic research

### Quick start

1. Paste the profile URL (e.g. `https://x.com/nasa`).
2. Set **From date** / **To date** if you want a specific period.
3. Provide a session (see **Authentication**) — a throwaway X account's cookies or credentials.
4. Run. Posts appear in the dataset as each profile completes.

### Input

| Field | Type | Description |
| --- | --- | --- |
| `profileUrl` | string | The profile to scrape. Full URL, `@handle` or bare handle. Several can be pasted one per line. |
| `profileUrls` | array | Multiple profiles — each scraped separately. `numberOfTweets` applies per profile. |
| `tab` | string | `posts`, `replies` (Posts & replies) or `media`. Default `posts`. |
| `numberOfTweets` | integer | Target posts per profile, after filtering (max 5000). Default 100. |
| `scrapeAll` | boolean | Auto-paginate until the target is reached. When `false` only the first page is returned. Default `true`. |
| `timeSince` / `timeUntil` | string | `yyyy-mm-dd` date bounds. **Both inclusive**, UTC. |
| `scrapeMode` | string | `auto` (search when dates are set, timeline otherwise), `search`, or `timeline`. Default `auto`. |
| `includeReplies` | boolean | Include the account's replies. Forced on for the `replies` tab. Default `false`. |
| `includeRetweets` | boolean | Include retweets; the original is exposed in `retweeted_status`. Default `true`. |
| `includePinned` | boolean | Include the pinned post (which can be far older than the rest). Default `true`. |
| `onlyProfileAuthor` | boolean | Drop posts written by other accounts (the `replies` tab pulls in parent posts as context). Default `true`. |
| `language` | string | ISO 639-1 code, e.g. `en`. |
| `minFaves` / `minRetweets` / `minReplies` | integer | Engagement floors. `0` disables. |
| `mediaHasMedia` / `mediaHasLinks` | boolean | Only posts with media / with links. |
| `contentQuestionMarks` | boolean | Questions only (search mode). |
| `cookies` | array | Cookies of a logged-in X session (Cookie-Editor / EditThisCookie JSON export). |
| `twUsername` / `twPassword` / `twEmail` | string | Credentials for automated login (2FA must be disabled). |
| `proxyConfiguration` | object | Proxy settings. Residential strongly recommended. |
| `headless` | boolean | Uncheck only for local debugging. |

#### Scrape modes — which one to use

A profile timeline **cannot be filtered by date**: reaching a range from two years ago means scrolling past every post in between. X advanced search can, so:

| | Advanced search (`from:handle since: until:`) | Profile timeline |
| --- | --- | --- |
| Date ranges | ✅ jumps straight to the period | ⚠️ must scroll back to it |
| Engagement floors (`min_faves`…) | ✅ applied server-side | filtered client-side |
| Pinned post | not marked | ✅ detected (`is_pinned`) |
| Best for | any dated query | "just give me the newest posts" |

`auto` picks search whenever a date is set, and the timeline otherwise. Both modes go through the same filters afterwards, so they return the same set.

#### Authentication

X login-walls profiles for anonymous visitors, so the actor needs a session. It resolves one in this priority order:

1. the `cookies` input (per-run),
2. the `TW_COOKIES` secret env var (JSON cookie array or a `name=value; name2=value2` header string),
3. the session cached in the `twitter-session` key-value store by a previous automated login,
4. automated login with `twUsername`/`twPassword` (or the `TW_USERNAME`/`TW_PASSWORD`/`TW_EMAIL` secret env vars) — the fresh session is cached for future runs.

**Always use a throwaway account, never a personal one.** Accounts used for scraping can be restricted by X.

### Output

One dataset item per post:

```json
{
    "id": "1234567890123456789",
    "id_str": "1234567890123456789",
    "url": "https://x.com/nasa/status/1234567890123456789",
    "twitterUrl": "https://twitter.com/nasa/status/1234567890123456789",
    "text": "Post text...",
    "full_text": "Post text...",
    "lang": "en",
    "created_at": "2026-01-15T09:30:00.000Z",
    "createdTime": "2026-01-15T09:30:00.000Z",
    "favorites": 12,
    "retweets": 3,
    "replies": 4,
    "quotes": 1,
    "bookmarks": 2,
    "views": 4520,
    "hashtags": ["space"],
    "mentions": ["esa"],
    "urls": ["https://nasa.gov/article"],
    "media": [{ "type": "photo", "url": "https://pbs.twimg.com/media/...", "expanded_url": "...", "video_url": null }],
    "is_retweet": false,
    "is_quote": false,
    "is_reply": false,
    "is_pinned": false,
    "retweeted_status": null,
    "quoted_status": null,
    "in_reply_to_status_id_str": null,
    "in_reply_to_screen_name": null,
    "conversation_id_str": "1234567890123456789",
    "possibly_sensitive": false,
    "user": {
        "id_str": "987654321",
        "name": "NASA",
        "screen_name": "nasa",
        "url": "https://x.com/nasa",
        "description": "Explore the universe",
        "followers": 1500,
        "following": 300,
        "tweets": 8200,
        "verified": false,
        "blue_verified": true,
        "avatar": "https://pbs.twimg.com/profile_images/...",
        "created_at": "2015-03-01T00:00:00.000Z",
        "location": "Houston"
    },
    "username": "nasa",
    "authorName": "NASA",
    "author": { "id": "987654321", "name": "NASA", "username": "nasa", "profileUrl": "https://x.com/nasa" },
    "profileHandle": "nasa",
    "profileUrl": "https://x.com/nasa",
    "tab": "posts",
    "scrapeMode": "search",
    "scrapedAt": "2026-08-13T12:00:00.000Z"
}
```

Posts are pushed newest-first per profile.

### How it works

The actor opens the profile (or its advanced-search equivalent) in a Playwright-driven Chrome with an authenticated session, intercepts the `UserTweets` / `UserTweetsAndReplies` / `UserMedia` / `SearchTimeline` GraphQL responses X streams to the page, and scrolls to auto-paginate. Parsing walks the GraphQL JSON generically instead of hardcoding one payload path, so it survives X's frequent payload-shape changes. Both feeds are reverse-chronological, so in timeline mode the scroll stops as soon as it drops below `timeSince` rather than reading the account's whole history.

### FAQ & troubleshooting

**Why do I get 0 results?** Check the run log first — it names the cause. Most common: an expired/missing session, a blocked proxy IP, a date range in which the account posted nothing, or filters (`includeReplies`, `includeRetweets`, `min*`) excluding everything. `DEBUG_SCREENSHOT` and `DEBUG_HTML` in the run's key-value store show exactly what the browser saw.

**A date range returns fewer posts than the profile shows.** By default replies are excluded (`includeReplies: false`). Turn it on, or pick the "Posts & replies" tab.

**Timeline mode with an old date range is slow.** That's inherent — the timeline has no date filter, so it must be scrolled back to the range. Leave `scrapeMode` on `auto` and the actor uses search instead.

**Login fails — what now?** The error message says which check X raised (identity confirmation, confirmation code/2FA, Arkose CAPTCHA, suspension) and what to do about it. Logging the account in manually once from the proxy's region usually clears security checks. When in doubt, export cookies from a logged-in browser session and pass those instead.

**Navigation timeouts?** Use Apify Proxy with the `RESIDENTIAL` group; datacenter IPs are widely blocked by X.

**Private/suspended profiles** are skipped with a warning; the rest of the run continues.

**Is this legal?** The actor extracts only publicly visible data. You are responsible for complying with X's Terms of Service and applicable laws (e.g. GDPR) for your use case — consult a lawyer if unsure.

# Actor input Schema

## `profileUrl` (type: `string`):

The X/Twitter profile page to scrape, e.g. https://x.com/nasa. An @handle or a bare handle also works, and so does any URL on the profile (a status link is resolved to its author). You can paste several profiles, one per line. For a clean list use "profileUrls" below instead.

## `profileUrls` (type: `array`):

Several profiles to scrape — each is scraped separately and the results are combined. "Number of posts" applies PER profile.

## `tab` (type: `string`):

Which part of the profile to scrape: "Posts" (the main timeline), "Posts & replies" (includes the account's replies), or "Media" (only posts containing photos/videos).

## `numberOfTweets` (type: `integer`):

Maximum number of posts to return per profile (after filtering).

## `scrapeAll` (type: `boolean`):

Keep scrolling/paginating until the target count is reached, the date range is passed, or the feed runs dry. When unchecked only the first page of the profile is returned.

## `timeSince` (type: `string`):

Only return posts published on or after this date (format: yyyy-mm-dd). Inclusive.

## `timeUntil` (type: `string`):

Only return posts published on or before this date (format: yyyy-mm-dd). Inclusive — posts made on this day are returned.

## `scrapeMode` (type: `string`):

"Auto" (recommended) uses advanced search when a date range is set and the profile timeline otherwise. "Search" always uses from:handle advanced search — best for date ranges and engagement filters. "Timeline" always scrolls the profile page itself — best for simply grabbing the newest posts, and the only mode that sees pinned posts.

## `includeReplies` (type: `boolean`):

Include posts that are replies to someone. Forced on when the "Posts & replies" tab is selected.

## `includeRetweets` (type: `boolean`):

Include the account's retweets of other people's posts. The original post is exposed in the "retweeted\_status" field.

## `includePinned` (type: `boolean`):

Include the profile's pinned post. It can be much older than the rest of the timeline, so uncheck it to keep a date-filtered run clean.

## `onlyProfileAuthor` (type: `boolean`):

Drop posts written by other accounts. The "Posts & replies" tab pulls in the parent posts of each reply as conversation context; this filter removes them. Leave checked unless you want that context.

## `language` (type: `string`):

Restrict results to a language (ISO 639-1 code, e.g. "en", "fr").

## `minFaves` (type: `integer`):

Only posts with at least this many likes. 0 disables the filter.

## `minRetweets` (type: `integer`):

Only posts with at least this many retweets. 0 disables the filter.

## `minReplies` (type: `integer`):

Only posts with at least this many replies. 0 disables the filter.

## `mediaHasMedia` (type: `boolean`):

Only posts containing a photo or video. Forced on when the "Media" tab is selected.

## `mediaHasLinks` (type: `boolean`):

Only posts containing links.

## `contentQuestionMarks` (type: `boolean`):

Only posts phrased as questions (adds the ? search operator). Search mode only.

## `cookies` (type: `array`):

Cookies of a logged-in X session, exported with a browser extension such as Cookie-Editor or EditThisCookie (JSON array of {name, value, domain, ...}). REQUIRED in practice: X profiles are login-walled for anonymous visitors. The "auth\_token" and "ct0" cookies carry the session. Alternatively set the TW\_COOKIES secret environment variable on the actor (same JSON, or a 'name=value; name2=value2' string) to avoid passing cookies in every run. Use a throwaway account, never your personal one.

## `twUsername` (type: `string`):

Username/email/phone of a throwaway X account. If set together with twPassword (or via the TW\_USERNAME/TW\_PASSWORD secret env vars), the actor logs in itself and caches the session, so you don't need to paste cookies. The account must have 2FA disabled. Prefer setting these as secret environment variables on the actor rather than per run.

## `twPassword` (type: `string`):

Password for the account in twUsername. Stored encrypted. Prefer the TW\_PASSWORD secret environment variable instead of putting it in run input.

## `twEmail` (type: `string`):

Email of the account, used only when X asks to confirm the identity during automated login ("Enter your phone number or email"). Also settable via the TW\_EMAIL env var.

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

Proxy to route traffic through. Residential proxies with a country set are strongly recommended for X — the random global residential pool often hands out dead exit IPs, so the actor defaults RESIDENTIAL to US exits when no country is chosen.

## `headless` (type: `boolean`):

Uncheck to run a headed browser (useful only for local debugging).

## Actor input object example

```json
{
  "profileUrl": "https://x.com/nasa",
  "profileUrls": [],
  "tab": "posts",
  "numberOfTweets": 100,
  "scrapeAll": true,
  "scrapeMode": "auto",
  "includeReplies": false,
  "includeRetweets": true,
  "includePinned": true,
  "onlyProfileAuthor": true,
  "minFaves": 0,
  "minRetweets": 0,
  "minReplies": 0,
  "mediaHasMedia": false,
  "mediaHasLinks": false,
  "contentQuestionMarks": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "headless": true
}
```

# Actor output Schema

## `posts` (type: `string`):

The scraped posts as dataset items — one item per post, with text, author, publish date, engagement counts (likes, retweets, replies, quotes, views) and media.

# 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 = {
    "profileUrl": "https://x.com/nasa",
    "profileUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("outspoken_strategy/twitter-profile-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 = {
    "profileUrl": "https://x.com/nasa",
    "profileUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("outspoken_strategy/twitter-profile-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 '{
  "profileUrl": "https://x.com/nasa",
  "profileUrls": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call outspoken_strategy/twitter-profile-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,outspoken_strategy/twitter-profile-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/ZHJT7NWUMTxNghj7u/builds/8i8K4rl3CP4SVNGkS/openapi.json
