# Twitter (X) List Scraper - Full Tweets, Views & Authors (`dami_studio/twitter-list-scraper`) Actor

Scrape every tweet from an X (Twitter) list. One row per tweet: full text, likes, retweets, replies, quotes, bookmarks and views. Hashtags, media and the author come too. X serves list timelines only to a signed-in session, and this brings its own. $0.40 per 1,000 tweets.

- **URL**: https://apify.com/dami\_studio/twitter-list-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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) List Scraper

Give it an X list id and it walks that list's timeline and writes one row per tweet: the full
untruncated `text`, `likeCount`, `retweetCount`, `replyCount`, `quoteCount`, `bookmarkCount`,
`viewCount`, `createdAt`, `lang`, `hashtags`, `mentions`, `urls`, `media`, and an `author` object
with the handle, display name, follower count and verification flags.

Lists are the timeline worth scraping because somebody already did the filtering. A well-kept list
is a hand-picked set of accounts on one subject, which beats a keyword search that drags in every
spam reply using the word.

### About the login, up front

X only serves list timelines to a signed-in session. There is no guest route to them. The
`ListLatestTweetsTimeline` operation isn't registered on X's guest host at all, and no proxy
changes that.

So the Actor carries an account session of its own. **You don't need to log in or find a cookie.**
But be clear about what that means: something *is* logged in, it's shared across everyone running
the Actor, and X counts rate limits per account rather than per caller. Under heavy concurrent use
it throttles, and your run can come back short with a `BLOCKED` row explaining why.

If your throughput shouldn't depend on how busy the Actor is, put your own cookie in
`sessionCookies` and the run uses only yours. Log in to x.com (a throwaway account is fine), press
F12, open Application → Cookies → `https://x.com`, copy `auth_token` and `ct0`, and paste them as
`auth_token=XXXX; ct0=YYYY`. One line per account. Several lines get rotated, and one going quiet
doesn't stop the run. Nothing you paste there is ever printed to the run log. It does sit in the
run's saved input like every other field, which is the reason to use a throwaway account and not
your real one.

### Input

```json
{
  "startUrls": ["https://x.com/i/lists/34179516"],
  "maxItems": 250
}
```

| Field | What it does |
|---|---|
| `listIds` | The number in `x.com/i/lists/<id>`. One or many. |
| `startUrls` | Or paste the full list URLs; the id is read out of them. |
| `maxItems` | Tweets per run. Default 100, ceiling 5,000. You pay per tweet. |
| `sessionCookies` | Optional. Your own `auth_token=...; ct0=...` lines, for a rate limit nobody else draws on. |
| `proxyConfiguration` | Rotating Apify datacenter addresses by default. |
| `notionConnector` / `notionParentId` | Optional. Push the rows into a Notion data source. |

Run it with no list at all and you get one labelled `_sample` tweet, uncharged, so you can see the
shape before you spend anything.

Metered Apify proxy groups (Residential, Google SERP) are not selectable here. If you pick one it
is swapped for datacenter addresses and the log says so, so a run can't quietly run up a
per-gigabyte bandwidth bill. Your own proxy servers, pasted under Custom proxies, are used exactly
as given.

### A row

```json
{
  "id": "1789000000000000000",
  "url": "https://x.com/nasa/status/1789000000000000000",
  "text": "We're going back to the Moon.",
  "createdAt": "Mon May 13 14:02:11 +0000 2024",
  "lang": "en",
  "replyCount": 312,
  "retweetCount": 1840,
  "likeCount": 21500,
  "quoteCount": 96,
  "bookmarkCount": 410,
  "viewCount": 1230000,
  "isReply": false,
  "isRetweet": false,
  "isQuote": false,
  "conversationId": "1789000000000000000",
  "hashtags": ["Artemis"],
  "mentions": [],
  "urls": [],
  "media": [{ "type": "photo", "url": "https://pbs.twimg.com/media/xxx.jpg" }],
  "author": {
    "id": "11348282",
    "userName": "NASA",
    "name": "NASA",
    "followersCount": 96000000,
    "verified": true
  }
}
```

### Billing

$0.40 per 1,000 tweets ($0.0004 each). Same rate on the free
plan as on any paid one. No volume tiers, no per-list fee.

| Event | Price | Fires |
|---|---|---|
| Tweet scraped | $0.0004 | Once per real tweet written to the dataset |

The `_sample` row isn't charged. Diagnostic rows aren't charged. Tweets are deduplicated by id
within a run, including across several lists, so you never pay twice for the same one.

### Limits

**The built-in session is shared.** X limits per account, so at busy moments runs throttle and can
return fewer tweets than you asked for. Your own cookie in `sessionCookies` fixes that.

**Sessions wear out.** A `429` is treated as a healthy account going too fast: the run backs off
and rotates to another session rather than retiring that one. A real `401`, or a body that says
logged out, evicts the session. When nothing usable is left the run returns the labelled sample row
instead of failing, and charges nothing for it.

**Private and deleted lists aren't readable.** If the session can't see the list, you get a
diagnostic row that says so.

**`maxItems` caps at 5,000 per run.** For a full sweep of a very long timeline, split it across
runs.

**Some fields come back null.** `viewCount` is missing on older tweets and `bookmarkCount` isn't
always exposed. Media URLs expire, so download anything you want to keep.

**Text is returned as X published it**, in the original language. Nothing is translated or scored.

### FAQ

**Do I need cookies?** No. Runs work as-is.

**Then why is `sessionCookies` there?** Because the built-in session is shared and X limits per
account. Your own cookie gives you a limit no other run touches.

**Where's the list id?** The number in the URL: `x.com/i/lists/34179516` → `34179516`. Or paste the
whole URL into `startUrls`.

**Will a different proxy raise the rate limit?** No. The limit that bites on a list timeline is per
account, not per address.

**Why did I get a diagnostic row?** So you know what happened instead of getting silence. Check
`sessionState`: `ok` means the session was fine and the list was the problem, `exhausted` means
nothing was left to read it with. A re-run, or your own cookie, clears the second one.

# Actor input Schema

## `listIds` (type: `array`):

X list ids (the number in x.com/i/lists/<id>).

## `startUrls` (type: `array`):

Or paste list URLs (https://x.com/i/lists/34179516).

## `maxItems` (type: `integer`):

Hard cap on tweets returned across every list. You are charged per tweet, so this is also your budget for the run.

## `maxRunSeconds` (type: `integer`):

Hard wall-clock limit for this run. When it is reached the Actor stops paging and returns everything collected so far, which is complete and correct — it does not fail. Lower it if you want a fast partial answer; raise it for big lists.

## `notionConnector` (type: `string`):

Optional Notion delivery.

## `notionParentId` (type: `string`):

Optional Notion data-source id.

## `sessionCookies` (type: `array`):

Optional. Runs use a built-in session by default, and that session is shared, so under heavy concurrent load it is rate-limited across everyone using it. Supply your own account cookie here and the run uses only yours, giving you a rate limit nobody else draws on. In Chrome, logged in to x.com: F12 -> Application -> Cookies -> https://x.com, and copy auth\_token and ct0. Paste one account per line as 'auth\_token=XXXX; ct0=YYYY'. Several lines means several accounts, rotated. The cookie is never printed to the run log, but it is saved with the run input like any other field, so use a throwaway account.

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

Proxy for the X GraphQL requests. Defaults to rotating Apify DATACENTER addresses. Metered proxy groups (RESIDENTIAL, GOOGLE\_SERP) are not offered by this Actor and are automatically replaced with datacenter addresses. If you have your own residential/ISP proxy servers, paste them in this field (Custom proxies) and they are used exactly as given.

## Actor input object example

```json
{
  "listIds": [
    "34179516"
  ],
  "startUrls": [
    "https://x.com/i/lists/34179516"
  ],
  "maxItems": 100,
  "maxRunSeconds": 240,
  "sessionCookies": [
    "auth_token=abcd1234...; ct0=ef567890..."
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "BUYPROXIES94952"
    ]
  }
}
```

# Actor output Schema

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

Tweets in the 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 = {
    "listIds": [
        "34179516"
    ],
    "maxRunSeconds": 240
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/twitter-list-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 = {
    "listIds": ["34179516"],
    "maxRunSeconds": 240,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/twitter-list-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 '{
  "listIds": [
    "34179516"
  ],
  "maxRunSeconds": 240
}' |
apify call dami_studio/twitter-list-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/twitter-list-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/Sk5DHexHNrZgnRfyZ/builds/Jour7h6V0LOOCk3k2/openapi.json
