Bluesky Scraper - Posts, Profiles & Keyword Search avatar

Bluesky Scraper - Posts, Profiles & Keyword Search

Pricing

from $1.94 / 1,000 posts

Go to Apify Store
Bluesky Scraper - Posts, Profiles & Keyword Search

Bluesky Scraper - Posts, Profiles & Keyword Search

Scrape Bluesky posts and profiles by handle with no login, or follow a keyword through topic feeds, live capture or, with your app password, the full archive. Each post row holds the text, time, likes, reposts, replies, quotes, link and author. $2.00 per 1,000 posts.

Pricing

from $1.94 / 1,000 posts

Rating

0.0

(0)

Developer

Dami's Studio

Dami's Studio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Share

Give it Bluesky handles and get each author's profile and recent posts, with no login. Give it a keyword instead and it returns matching posts with engagement counts and links.

Keyword search is the part worth reading before you buy. Without your own app password, a keyword run reads public topic feeds or a live capture window, not the full archive. And a post on a topic feed can be there without containing your keyword at all.

InputBluesky handles, a keyword, or both
OutputOne row per post: text, time, likes, reposts, replies, quotes, link and author, plus one profile row per handle with follower counts and bio
Ceiling1,000 posts per handle or per query
Account neededNone for handles. Your own app password for full archive search
Price$2.00 per 1,000 posts on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 2,500

🔍 What Bluesky Scraper does

Point it at handles and it is simple: one profile row with the follower counts and bio, then that author's recent posts, each with text, timestamps and the like, repost, reply and quote counts. Nothing to sign in to.

A keyword is where it forks, and source on every row tells you which lane produced it:

sourceWhat it isNeeds
archive_searchThe real archive search, back through historyYour own app password
topic_feedRecent posts from public Bluesky topic feeds whose own name or description matches your keywordNothing
live_captureBluesky's public post stream watched for a set number of seconds, keeping the posts that matchNothing

On auto it takes the archive when you signed in, topic feeds when a feed matches your keyword, and live capture when neither applies. Every keyless keyword run also writes one free notice row saying which lane ran and why.

📋 What data you get from each Bluesky post

What you getField
The post link and its permanent idpostUrl, uri
The authorauthorHandle, authorName, authorDid
The text, and when it was postedtext, createdAt
Likes, reposts, replies and quoteslikeCount, repostCount, replyCount, quoteCount
Language tags the author's app setlangs
Which lane found it, and whether the text contains your keywordsource, matchedQuery, matchesQueryText
The topic feed it came from, and who runs itfeedName, feedUri, feedCreator
The handle you asked for, or the live watch windowqueriedHandle, capturedDuringSecs
Profile rows: name, bio, followers, following, posts, avatar, banner, join date, linkdisplayName, description, followersCount, followsCount, postsCount, avatar, banner, createdAt, profileUrl

▶️ How to scrape Bluesky posts

  1. Open Bluesky Scraper and click Try for free.
  2. For authors, put handles into Author handles and clear Search query.
  3. For a keyword, type it into Search query. For the full archive, add your handle and an app password from bsky.app.
  4. Set Max items per query/author, then click Start.
  5. If you ran a keyword without credentials, read the notice row first. It says which lane answered.

Start it with nothing filled in and you get one labelled sample row, free.

💰 How much does it cost to scrape Bluesky posts?

$2.00 per 1,000 posts on the Free plan. Paid plans pay a little less: $1.98 on Starter, $1.96 on Scale and $1.94 on Business. On the free plan, the $5 Apify gives you each month covers about 2,500 posts.

Posts are what you pay for. Profile rows, notice rows, the sample row and diagnostic rows are all free, duplicates and posts left out by language are dropped before they reach you, and a run that returns no posts costs you nothing.

One thing to know before you budget: on the topic_feed lane you pay for every post the feed returned, including ones where matchesQueryText is false. If you only want literal matches, sign in for the archive or use live capture.

📥 What you give it

{
"searchQuery": "artificial intelligence",
"searchMode": "auto",
"authorHandles": ["bsky.app"],
"maxItems": 100,
"liveWatchSecs": 60
}
FieldDefaultWhat it is
searchQuerynoneKeywords to match. Quoted phrases stay whole, and several bare words must all appear. The form opens with artificial intelligence filled in.
searchModeautoauto, live or archive. archive refuses to substitute another lane: with no credentials it says so and returns nothing.
authorHandles[]Handles to scrape, like jay.bsky.team. A leading @ is fine. The form opens with bsky.app.
languageanyKeep only posts tagged with this language, like es. Works on author handles and topic feeds; posts in other languages and untagged posts are left out, uncharged. A keyword that would go to the archive or live capture is refused with a free BAD_INPUT row.
blueskyIdentifiernoneYour own handle or email. Only used to reach the archive search.
blueskyAppPasswordnoneAn app password from bsky.app, Settings, Privacy and security, App passwords. Never your account password. Marked secret.
maxItems100Per query and per handle, up to 1,000. Three handles at 100 is up to 300 rows.
liveWatchSecs60How long live capture watches the stream, 10 to 900 seconds. It stops early once maxItems is reached.
notionConnector, notionParentIdnoneOptional. Write every post into your own Notion as well as the dataset.
proxyConfigurationoffOptional, and off by default because a normal run does not need it.

A rejected app password does not stop the run. It logs a warning and falls back to the keyless lanes, so check source on the rows if you expected archive results. With a language set, each handle and each feed is read at most 1,000 posts deep, so a language an account rarely uses can return fewer than maxItems.

📤 What you get back

A real post row from a keyless run, with the text cut short:

{
"ok": true,
"type": "post",
"uri": "at://did:plc:thtnfvzp2gvyeke24vvrlh4k/app.bsky.feed.post/3mvhejxpvac2g",
"postUrl": "https://bsky.app/profile/hustlrbase.bsky.social/post/3mvhejxpvac2g",
"authorHandle": "hustlrbase.bsky.social",
"authorName": "Hustle with Elisa",
"authorDid": "did:plc:thtnfvzp2gvyeke24vvrlh4k",
"text": "“Overwhelmed by Managing Multiple Client Accounts?” ... #ClientManagement #Automation #AI",
"createdAt": "2026-09-14T05:22:45.479Z",
"likeCount": 0,
"repostCount": 0,
"replyCount": 0,
"quoteCount": 0,
"langs": [],
"feedName": "Artificial Intelligence",
"feedUri": "at://did:plc:ugbenc43qu4v3hemephm2cry/app.bsky.feed.generator/aaamqhwmov3y4",
"feedCreator": "raefmeeuwisse.bsky.social",
"matchesQueryText": false,
"source": "topic_feed",
"matchedQuery": "artificial intelligence"
}
FieldHow to read it
matchesQueryTextWhether your keyword appears in the post text itself. On the topic_feed lane it is often false, as above: the feed matched your keyword, and the post is just on that feed. Filter on it when you need a literal match.
sourceAlways check this before drawing a conclusion about coverage.
uriStable, so use it to dedupe across runs.
langsOften empty, because many apps set no language tag.

🧾 Reading the output

Five kinds of row can share the dataset.

RowHow to spot itBilled
A posttype: "post"yes
A profiletype: "profile"no
A notice_notice: trueno
A diagnosticok: false and an errorCodeno
The sample row_sample: trueno

Filter on type, not on the presence of text. Notice and profile rows share the dataset with posts, and the default table view renders them as partly blank lines. With a language set, the topic-feed notice row also counts the posts read, left out, and left out for having no tag.

CodeWhat it means
BAD_INPUTYou asked for archive mode with no app password, or set a language on a keyword that would go to the archive or live capture. Nothing was read.
NO_RESULTSThe lane ran and matched nothing, that handle returned neither a profile nor posts, or none of the posts read was in your language. The row says how many were read.
NOT_FOUNDThat handle does not resolve. Check the spelling, including the .bsky.social part.
RATE_LIMITEDBluesky asked for a slower pace. Re-run with a smaller maxItems.
SERVER_ERRORBluesky answered with a server error. Usually passes on its own.
NOT_SERVEDThis post was not served this run. Re-run it later. Until 12 October 2026 this code was called BLOCKED.
NETWORKBluesky could not be reached, including when a supplied credential was refused.
TIME_LIMITThe run got close to its time limit and stopped on its own. What it delivered before that is complete and charged as usual. Give the run more time, or ask for fewer posts, to get the rest.

A run that ends on one of these rows still finishes as succeeded, except one: when the time limit stopped the run before any post or profile came back, it ends as failed, with a TIME_LIMIT row saying so.

💡 What people use it for

  • Watching a handful of accounts daily, deduped on uri, to see what they posted and how it landed.
  • Catching mentions of a product name as they happen, with searchMode on live and a longer window.
  • Pulling a topic feed's recent posts to see who is active in a niche.
  • Backfilling a keyword properly with an app password, then keeping it current with short live runs.

🚧 What it does not do

  • No archive search without your own app password, and no setting here changes that.
  • Live capture only sees the window. It watches the public stream for the seconds you allow and returns the matches inside it. A rare keyword can produce zero.
  • Fresh live posts look unpopular. A post captured seconds after it was written has no likes yet.
  • Author timelines are recent posts only, capped at 1,000 per handle. It is not a full account export.
  • Topic feeds are somebody else's editorial choice. Those rows reflect the feed, not a search.
  • A language goes by the tag the author's app set. Plenty of accounts, news outlets among them, post with no tag at all, and those posts are left out once you pick a language.
  • No threads, no replies as a tree, no follower lists, no direct messages.
  • No account is created for you. The app password stays yours, and archive search runs through your own account.
  • It is not an official Bluesky tool. Dami's Studio is independent and is not affiliated with or endorsed by Bluesky, or by any other company named on this page.

🧭 Which social scraper do you need?

If you wantUse
Posts from Bluesky accounts or a Bluesky searchThis one
Posts from a Mastodon server by hashtag, account or timelineMastodon Scraper
Profiles and recent posts from ThreadsThreads Scraper
Posts from a Lemmy community or instanceLemmy Scraper

❓ Questions people ask

Do I need a Bluesky account to scrape posts?

Only for archive keyword search. Handles, profiles, timelines, topic feeds and live capture all work signed out.

What is a Bluesky app password?

A separate password Bluesky issues for tools, under Settings, Privacy and security. You can revoke it any time, and it is not your account password.

Why did my keyword return posts that do not mention it?

You were on the topic-feed lane. Check source and matchesQueryText on the rows, and read the notice row.

Why did live capture return nothing?

Nobody posted a match during the window. Raise liveWatchSecs, or sign in and use the archive.

Can I call it from code or connect it to an AI assistant?

Yes. The API tab has ready-made code for Python, JavaScript and the command line. For Claude, ChatGPT or another MCP client, connect https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/bluesky-scraper. Either way the run happens on your Apify account at the same price.

These are public posts on a public network. They are still personal data, which GDPR and similar laws cover, so have a reason for collecting them. Apify's write-up on the legality of web scraping is a good starting point, and we are not lawyers.

🆘 If something breaks

Open the Issues tab on the actor page. Send the run ID, the handle or keyword, and the source value on the rows you got. The errorCode on the diagnostic row usually names the problem on its own.