# Bluesky Posts & Profile Scraper (`maged120/bluesky-scraper`) Actor

Scrape Bluesky profiles and posts: followers, bios, full post text, likes, reposts, replies, images and links, from any profile or keyword search.

- **URL**: https://apify.com/maged120/bluesky-scraper.md
- **Developed by:** [Maged](https://apify.com/maged120) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

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 Posts & Profile Scraper** extracts **profiles and posts from [Bluesky](https://bsky.app)**: bios, **follower counts**, join dates and verification, plus every post's **full text, likes, reposts, replies, quotes, images, videos, links and hashtags**. Scrape any profile's timeline or **search all of Bluesky by keyword or #hashtag**, and get clean, export-ready data.

### What does Bluesky Posts & Profile Scraper do?

- **Profiles**: give it handles or profile links and get one profile row each (name, bio, followers, following, post count, join date, verification, avatar) plus that profile's most recent posts.
- **Keyword & hashtag search**: find posts across the whole network mentioning a keyword or hashtag, newest first or most engaging first.

Every post comes with engagement counts, media, links, hashtags, mentions and a direct link, and is marked as original, reply or repost.

On the Apify platform you also get API access, scheduling, integrations (Google Sheets, Slack, Zapier, Make, webhooks) and run monitoring, so you can track accounts or keywords on autopilot.

### Why scrape Bluesky?

- **Brand & keyword monitoring**: catch every mention of your brand, product or competitors as it happens.
- **Influencer research**: compare accounts by followers, posting frequency and engagement.
- **Social listening & research**: collect posts on a topic or hashtag for sentiment analysis, AI training data or academic research.
- **Content strategy**: find which posts get the most likes, reposts and quotes.
- **Archiving**: keep a timestamped copy of an account's public posts.

### How to scrape Bluesky

1. Open the Actor and go to the **Input** tab.
2. Add handles to **Profiles** (e.g. `bsky.app`, `@jay.bsky.team`, or a profile link), and/or keywords to **Search keywords**.
3. Set how many posts you want per profile and per keyword.
4. Click **Start**.
5. Open the **Output** tab and switch between the **Posts** and **Profiles** views, or download as JSON, CSV, Excel or HTML.

### Input

| Field | Type | Description |
|---|---|---|
| `profiles` | array | Handles (`bsky.app`, `name.bsky.social`, `@name`) or profile links. |
| `maxPostsPerProfile` | integer | Most recent posts per profile. `0` = profile info only. Default `50`. |
| `includeReplies` | boolean | Also collect the profile's replies. Default `false`. |
| `includeReposts` | boolean | Also collect reposts. Default `true`. |
| `includeProfileInfo` | boolean | Add a profile row. Default `true`. |
| `searchQueries` | array | Keywords or `#hashtags` to search across Bluesky. |
| `maxPostsPerSearch` | integer | Posts per keyword. Default `100`. |
| `searchSort` | string | `latest` or `top`. Top returns up to 100 posts per keyword. |

```json
{
    "profiles": ["bsky.app", "https://bsky.app/profile/jay.bsky.team"],
    "maxPostsPerProfile": 100,
    "searchQueries": ["web scraping", "#python"],
    "maxPostsPerSearch": 300,
    "searchSort": "latest"
}
```

### Output

Two kinds of rows, marked by `entityType`. You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

**Profile**

```json
{
    "entityType": "profile",
    "handle": "jay.bsky.team",
    "did": "did:plc:oky5czdrnfjpqslsw2a5iclo",
    "profileUrl": "https://bsky.app/profile/jay.bsky.team",
    "displayName": "Jay 🦋",
    "description": "Founder & Chief Innovation Officer @ Bluesky\n\nWorking on @attie.ai\n\n🌱 🪴 🌳",
    "followersCount": 594638,
    "followsCount": 3985,
    "postsCount": 4165,
    "createdAt": "2022-11-17T06:31:40.296Z",
    "isVerified": true,
    "avatarUrl": "https://cdn.bsky.app/img/avatar/plain/did:plc:oky5czdrnfjpqslsw2a5iclo/...",
    "pinnedPostUrl": null
}
```

**Post**

```json
{
    "entityType": "post",
    "postUrl": "https://bsky.app/profile/jay.bsky.team/post/3mvvdpby3x22t",
    "text": "good dogs",
    "createdAt": "2026-09-19T18:45:07.963Z",
    "languages": ["en"],
    "authorHandle": "jay.bsky.team",
    "authorName": "Jay 🦋",
    "likeCount": 662,
    "repostCount": 23,
    "replyCount": 20,
    "quoteCount": 2,
    "isReply": false,
    "isRepost": false,
    "imageUrls": ["https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:oky5czdrnfjpqslsw2a5iclo/..."],
    "videoUrl": null,
    "externalLink": null,
    "quotedPostUrl": null,
    "hashtags": [],
    "mentions": [],
    "links": [],
    "source": "profile",
    "searchQuery": null
}
```

### Output data fields

| Field | Description |
|---|---|
| `followersCount` / `followsCount` / `postsCount` | Profile audience and activity. |
| `createdAt` (profile) | When the account joined Bluesky. |
| `isVerified` | Whether the account has a valid verification. |
| `text` / `createdAt` / `languages` | Post content and time. |
| `likeCount` / `repostCount` / `replyCount` / `quoteCount` | Engagement. |
| `isReply` / `replyToUrl` / `isRepost` / `repostedBy` | Post type and context. |
| `imageUrls` / `videoUrl` / `externalLink` / `quotedPostUrl` | Attached media, link card and quoted post. |
| `hashtags` / `mentions` / `links` | Tags, mentioned accounts and links in the text. |
| `source` / `searchQuery` | Whether the post came from a profile or a keyword search. |

### How many results will I get?

One row per post, plus one row per profile. 3 profiles × 100 posts + 2 keywords × 300 posts is up to 903 rows. Use the per-profile and per-keyword limits to control volume.

### Tips

- **Monitor a keyword daily**: schedule the Actor with `searchSort: latest` and send new posts to Slack or Google Sheets.
- **Hashtags**: search `#yourtag` to get posts tagged with it.
- **Profile snapshot only**: set **Max posts per profile** to `0` to collect just profile stats for a list of accounts.

### FAQ

**Can I scrape followers lists?** Not at the moment. Open an issue if you need it.

**Is scraping Bluesky legal?** The Actor only collects publicly available posts and profiles. You're responsible for using the data lawfully, especially personal data under GDPR.

**Found a bug or need a custom feature?** Open an issue in the **Issues** tab. Custom solutions are available on request.

# Actor input Schema

## `profiles` (type: `array`):

Bluesky handles (bsky.app, jay.bsky.social, @name) or profile links (https://bsky.app/profile/bsky.app).

## `maxPostsPerProfile` (type: `integer`):

Most recent posts to collect from each profile. 0 = profile info only.

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

Also collect the profile's replies to other posts.

## `includeReposts` (type: `boolean`):

Also collect posts the profile reposted.

## `includeProfileInfo` (type: `boolean`):

Add one row per profile with bio, follower counts, join date and avatar.

## `searchQueries` (type: `array`):

Optional. Find posts across all of Bluesky containing these keywords or #hashtags.

## `maxPostsPerSearch` (type: `integer`):

How many posts to collect for each search keyword.

## `searchSort` (type: `string`):

Latest posts first, or top (most engaging) posts first. Top returns up to 100 posts per keyword.

## Actor input object example

```json
{
  "profiles": [
    "bsky.app"
  ],
  "maxPostsPerProfile": 50,
  "includeReplies": false,
  "includeReposts": true,
  "includeProfileInfo": true,
  "searchQueries": [
    "web scraping"
  ],
  "maxPostsPerSearch": 100,
  "searchSort": "latest"
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "profiles": [
        "bsky.app"
    ],
    "maxPostsPerProfile": 50,
    "searchQueries": [
        "web scraping"
    ],
    "maxPostsPerSearch": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("maged120/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 = {
    "profiles": ["bsky.app"],
    "maxPostsPerProfile": 50,
    "searchQueries": ["web scraping"],
    "maxPostsPerSearch": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("maged120/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 '{
  "profiles": [
    "bsky.app"
  ],
  "maxPostsPerProfile": 50,
  "searchQueries": [
    "web scraping"
  ],
  "maxPostsPerSearch": 100
}' |
apify call maged120/bluesky-scraper --silent --output-dataset

```

## MCP server setup

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