# Bluesky Scraper — Posts, Search, Profiles, Comments & Followers (`s-r/bluesky-scraper`) Actor

Collect public Bluesky posts, keyword search results, profiles, replies, followers and following without logging in. Get text, likes, reposts, quotes, media and links as JSON or CSV for social listening, brand monitoring and research.

- **URL**: https://apify.com/s-r/bluesky-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Social media, Marketing, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.20 / 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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bluesky Scraper

Bluesky Scraper collects public Bluesky posts, keyword search results, profiles, replies, followers and following into one structured dataset, without a Bluesky login. Use it for social listening, brand monitoring, community research, content analysis and public account monitoring. You can combine accounts, individual posts and search terms in the same run. Every delivered row identifies its type and the original input that produced it, so you can keep different research questions together without losing their context.

### What Bluesky Scraper collects

Profile rows include the account DID, handle, display name, full biography, profile URL and the follower, following and post counts when the source provides them. Optional fields include the avatar, banner, creation date, verification status and links extracted from the biography. Handles are returned without a leading `@`. If an account has an invalid handle, its public URL uses its stable DID instead.

Post rows include the public URL, globally unique record URI, author identifiers, original text, publication time, likes, reposts, direct reply count, quotes and bookmarks when available. Image, gallery and video links are returned with the post. Outgoing links, hashtags, mentions and source language codes are included where present. Reposts retain the original post author and carry a separate indicator and reposting account.

Comment rows represent public replies to an individual requested post. Each row carries the requested anchor post ID, its own comment ID, parent reference and depth. A direct reply has a null parent comment ID. The original thread root can differ from the requested anchor when you request a post that is itself a reply.

### Use cases for a Bluesky scraper

- **Social listening and brand monitoring.** Search Bluesky posts for a brand, product, campaign hashtag or competitor and track mentions, engagement and language over time.
- **Research and academic datasets.** Build reproducible samples of public Bluesky posts for a topic or time window with `since` and `until`, and keep the exact query that produced each row.
- **Influencer and creator analysis.** Pull a creator's profile, follower count and author feed, then compare likes, reposts and quotes per post.
- **Audience and community mapping.** Export the followers or following list of an account as profile rows, optionally enriched with their own follower and post counts.
- **Conversation analysis.** Fetch the full public reply thread under a post to study reactions, sentiment and who replies to whom.
- **Migration tracking.** Monitor which accounts in your industry are active on Bluesky and how fast their audience grows, by running the same handles on a schedule.

### Input

Supply at least one non-empty list of `handles`, `post_urls` or `queries`. A list accepts up to 100 entries. Accounts may be handles, DIDs or public profile URLs. Post inputs may be public Bluesky post URLs or AT record URIs. Queries accept terms and source-supported operators, including `from:bsky.app` and hashtags.

This input requests the official Bluesky profile without its author feed:

```json
{
  "handles": ["bsky.app"],
  "include_posts": false
}
```

This search input returns up to 250 recent posts for a term used in our collected source responses:

```json
{
  "queries": ["climate"],
  "sort": "latest",
  "max_items": 250
}
```

`include_posts` defaults to true for account inputs. `feed_filter` defaults to `posts_no_replies`; the other supported values are `posts_with_replies`, `posts_with_media` and `posts_and_author_threads`. Reposts remain included unless you set `include_reposts` to false. These switches apply to author feeds.

`include_comments` defaults to true for individual post inputs. It does not fetch a thread for every search or feed result. Set it to false when you only need the selected posts and their engagement counts. `include_followers` and `include_following` default to false. Enable either to receive the related accounts as profile rows with the relationship origin attached.

Follower and following list responses usually omit account counts. Set `enrich_profiles` to true to request those counts as an additional step. Unavailable counts remain absent; a missing value does not imply zero followers. The run reports a warning if a requested enrichment could not complete.

For searches, `since` and `until` accept an ISO date or timestamp, `lang` accepts a two-letter language code and `author` restricts results to an account. `sort` defaults to `latest`. `top` delivers at most 100 posts per query and reports that limit when you request more.

### Output

The default dataset contains `profile`, `post` and `comment` rows. Both `record_type` and `_type` identify the row type. Every row includes `source`, `platform`, `fetched_at`, `input_type`, `input` and a stable `row_id`. Times are normalized to UTC. The run's OUTPUT record contains delivery counts, warnings and completion information.

Profile records use `profile_id`, `profile_url`, `handle`, `name`, `bio`, `followers`, `following` and `posts_count`. Post records use `post_id`, `post_url`, `uri`, `author_handle`, `author_id`, `text`, `posted_at`, `likes`, `shares` and `comments_count`. Comments add `comment_id`, `comment_url`, `parent_comment_id`, `parent_uri`, `thread_root_uri` and `depth`.

`shares` means reposts; quotes have their own `quotes` field. `comments_count` is the source's direct reply count rather than the number of all descendants delivered. Bluesky does not provide a view count, so the scraper does not invent one. Record keys are unique within an author; use `uri` when joining posts globally.

You can export the dataset as JSON or CSV through Apify. JSON preserves media arrays and relationship references most naturally. When comparing runs, remember that engagement counts describe the moment the source response was fetched and may change later.

#### Example profile row

This is the real row returned for the input `{"handles": ["bsky.app"], "include_posts": false}`:

```json
{
  "source": "bluesky-scraper",
  "platform": "bluesky",
  "record_type": "profile",
  "_type": "profile",
  "row_id": "[\"profile\",\"handle\",\"bsky.app\",\"did:plc:z72i7hdynmk6r22z27h6tvur\"]",
  "input_type": "handle",
  "input": "bsky.app",
  "fetched_at": "2026-10-05T04:10:22.000Z",
  "profile_url": "https://bsky.app/profile/bsky.app",
  "profile_id": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "handle": "bsky.app",
  "name": "Bluesky",
  "bio": "official Bluesky account (check username👆)\n\nBugs, feature requests, feedback: support@bsky.app",
  "avatar_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreihwihm6kpd6zuwhhlro75p5qks5qtrcu55jp3gddbfjsieiv7wuka",
  "banner_url": "https://cdn.bsky.app/img/banner/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreichzyovokfzmymz36p5jibbjrhsur6n7hjnzxrpbt5jaydp2szvna",
  "followers": 35150226,
  "following": 15,
  "posts_count": 865,
  "lists_count": 18,
  "feeds_count": 7,
  "starter_packs_count": 15,
  "created_at": "2023-04-12T04:53:57.057Z",
  "indexed_at": "2025-10-27T21:05:26.152Z",
  "links": [],
  "pinned_post_uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l",
  "verified_status": "none",
  "labels": []
}
```

#### Example post row

This is the real anchor post row returned for a `post_urls` input, before its reply rows:

```json
{
  "source": "bluesky-scraper",
  "platform": "bluesky",
  "record_type": "post",
  "_type": "post",
  "row_id": "[\"post\",\"post_url\",\"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mv6jm4auic2g\",\"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mv6jm4auic2g\"]",
  "input_type": "post_url",
  "input": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mv6jm4auic2g",
  "fetched_at": "2026-10-05T04:10:22.000Z",
  "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mv6jm4auic2g",
  "post_id": "3mv6jm4auic2g",
  "post_url": "https://bsky.app/profile/bsky.app/post/3mv6jm4auic2g",
  "cid": "bafyreia6h2jdgzc7nuvsfpwdkw3synzz73vkobyamndabthoy4gb5zersq",
  "author_handle": "bsky.app",
  "author_id": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "author_name": "Bluesky",
  "text": "Thanks for being with us here on Bluesky, the official app of sports™️",
  "likes": 326,
  "shares": 16,
  "comments_count": 9,
  "quotes": 2,
  "bookmarks": 1,
  "indexed_at": "2026-09-10T16:59:31.364Z",
  "posted_at": "2026-09-10T16:59:29.774Z",
  "media": [],
  "links": [],
  "mentions": [],
  "hashtags": [],
  "langs": [
    "en"
  ],
  "labels": [],
  "reply_parent_uri": "at://did:plc:2buz4gf5sew3rdwzbxsvcd4s/app.bsky.feed.post/3mv6ghymh3s27",
  "reply_root_uri": "at://did:plc:2buz4gf5sew3rdwzbxsvcd4s/app.bsky.feed.post/3mv6ghymh3s27",
  "is_reply": true,
  "is_repost": false
}
```

### Limits and continuation

`max_items` defaults to 100 and can be set from 1 to 10000. It applies separately to each account feed, each query, each requested post's replies and each account's follower or following list. The account profile and requested anchor post are additional rows. Two queries with a limit of 100 can therefore deliver up to 200 post rows.

Apify automatically follows continuation pages. If you consume individual responses, send the returned `cursor` with the same original input and options to continue. Warnings explain incomplete source results. Large threads may be truncated by Bluesky, and deleted or blocked replies are unavailable. Search results are also subject to source indexing and moderation: pagination cannot guarantee a complete historical archive.

### Bluesky Scraper compared with other options

| | This Bluesky scraper | Bluesky's own API | Browser export extensions | Single-purpose scrapers |
|---|---|---|---|---|
| Code required | No | Yes, plus pagination and rate handling | No | No |
| Login required | No | Search needs extra setup | Varies | Varies |
| Posts, search, profiles, replies and followers in one run | Yes | Separate endpoints | Usually one page at a time | One data type per actor |
| More than 100 search results per query | Yes, with `sort: latest` | Manual cursor handling | Limited by scrolling | Varies |
| Scheduling, webhooks and integrations | Yes, through Apify | Build it yourself | No | Yes |
| Input kept on every row | Yes (`input_type`, `input`) | No | No | Varies |

If you only need a handful of posts once, a browser extension is fine. If you need repeatable datasets, several data types at once, or a Bluesky API without writing and hosting your own client, this actor saves that work.

### Pricing

The actor uses a result event for each delivered dataset row, regardless of whether it is a profile, post or comment. At launch that is $0.0012 per row, so 1,000 rows cost $1.20; the Apify pricing panel on this page always shows the current event price. Empty results incur no result events and there is no actor start event in this product's charging configuration. A run with partial results charges only for the rows it delivers, and continuation pages do not charge duplicate rows again.

Users without a paid Apify plan receive at most ten dataset rows per run. Their requested volume is limited before collection, and delivery and result events are capped at ten. This cap applies to the combined dataset across all row types and inputs. Paid users retain their requested limits.

### FAQ

#### Do I need a Bluesky account?

No account credentials are required for the public data this actor currently collects. Private, deleted or blocked records are not made available by the scraper. Search availability depends on what Bluesky exposes publicly and can change.

#### Can I search Bluesky posts without an account?

Yes. Put your terms in `queries` and the actor returns matching public posts with their engagement counts. Use `author` or the `from:` operator to search posts by one user, `lang` to restrict the language, and `since` and `until` for a date range.

#### Can I fetch more than 100 search results?

Yes, use `sort: latest` and increase `max_items`. Collection continues through older time windows and removes repeated record URIs. `sort: top` remains limited to one source page with at most 100 results. Dense groups of identical timestamps can limit further progress; the run reports that condition.

#### How do I export Bluesky followers?

Add the account to `handles` and set `include_followers` (or `include_following`) to true. Each related account becomes a profile row that you can download as CSV or JSON from the dataset. Set `include_posts` to false if you only want the list.

#### Why are follower counts missing from follower rows?

The public relationship lists omit these counts. Enable `enrich_profiles` to request full profiles for those accounts. If a profile is unavailable, its missing count stays absent and the run reports incomplete enrichment.

#### Are all replies in a large thread included?

Bluesky can omit replies from large threads. The actor reports an incomplete thread when the available direct replies do not match the source count, or unavailable reply nodes are present. A successful run may therefore contain useful comment rows together with a source warning.

#### How do I separate different research inputs?

Filter on `input_type` and `input`. These fields retain the original account, URL or query, while `record_type` separates profiles, posts and comments. The same post found through two different inputs retains both contexts; repeated rows within a continuation are removed.

#### Can I use this Bluesky scraper as an API?

Yes. Every Apify actor can be started through the Apify API or client libraries, and the dataset can be read as JSON or CSV when the run finishes. You can also schedule runs or send results to other tools with webhooks and integrations.

# Actor input Schema

## `handles` (type: `array`):

Bluesky handles, DIDs or profile URLs. Each account produces a profile and the requested feed or relations.

## `post_urls` (type: `array`):

Public bsky.app post URLs or at:// post URIs. Replies are returned as separate comment rows.

## `queries` (type: `array`):

Search terms or operators such as from:bsky.app and #photography. Latest search supports time-window pagination.

## `include_posts` (type: `boolean`):

Fetch author feeds for handles.

## `include_reposts` (type: `boolean`):

Include reposts in author feeds, retaining the original author and marking the repost.

## `include_comments` (type: `boolean`):

Fetch available replies for post_urls. Large threads may be incomplete at the source.

## `include_followers` (type: `boolean`):

Fetch followers as profile rows.

## `include_following` (type: `boolean`):

Fetch accounts followed by each handle as profile rows.

## `enrich_profiles` (type: `boolean`):

Fetch profile counts for followers and following in batches of up to 25. Unavailable counts remain absent.

## `feed_filter` (type: `string`):

Author feed filter. posts_no_replies still includes reposts when include_reposts is enabled.

## `sort` (type: `string`):

Search order. top returns at most 100 posts per query. latest continues through time windows.

## `since` (type: `string`):

Search lower bound, an ISO 8601 date or timestamp. Must be earlier than until.

## `until` (type: `string`):

Initial search upper bound, an ISO 8601 date or timestamp.

## `lang` (type: `string`):

Search language, a two-letter language code.

## `author` (type: `string`):

Restrict searches to this Bluesky handle or DID.

## `max_items` (type: `integer`):

Limit per input and per phase: posts per account or query, replies per post, followers and following per account. Anchor profiles and posts are additional rows.

## `cursor` (type: `string`):

Opaque continuation from a previous response. Send the same input and options, with this cursor.

## Actor input object example

```json
{
  "handles": [
    "bsky.app"
  ],
  "include_posts": true,
  "include_reposts": true,
  "include_comments": true,
  "include_followers": false,
  "include_following": false,
  "enrich_profiles": false,
  "feed_filter": "posts_no_replies",
  "sort": "latest",
  "max_items": 100
}
```

# Actor output Schema

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

One row per delivered profile, post or comment.

## `output` (type: `string`):

Counts, warnings and delivery status.

# 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 = {
    "handles": [
        "bsky.app"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/bluesky-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 = { "handles": ["bsky.app"] }

# Run the Actor and wait for it to finish
run = client.actor("s-r/bluesky-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 '{
  "handles": [
    "bsky.app"
  ]
}' |
apify call s-r/bluesky-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/bluesky-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/lyymHdORgivcEr8gi/builds/jUioL6Xol1a7y9P9h/openapi.json
