Mastodon Scraper - Trending Profiles, Posts & Replies
Pricing
from $1.00 / 1,000 results
Mastodon Scraper - Trending Profiles, Posts & Replies
Scrape trending Mastodon profiles and related posts from any Mastodon instance. Extract usernames, bios, follower counts, avatars, profile fields, posts and thread replies. Supports nested profile reviews or flat review output for easier analysis.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Share
Mastodon Scraper: Trending Profiles, Posts and Replies
Mastodon Scraper turns any public Mastodon server into structured data. Get profiles with bios, follower counts and custom fields, their recent posts and the replies underneath them, or bulk posts collected by hashtag at volume. Discover accounts through currently trending posts and the public profile directory, or paste exact profile and post links to scrape only what you want. Export to JSON, CSV or Excel, or pull results through the API. Defaults to mastodon.social and works on any public Mastodon instance.
Why This Scraper?
- Three output shapes. Profiles with reviews nested inside, a flat stream of reviews, or bulk keyword posts, so you pick the shape your pipeline needs.
- Two discovery sources, merged. Authors of currently trending posts and the active public directory, deduplicated automatically into one list.
- Full conversation context. Every post's reply thread is captured alongside it, tagged separately from the profile's own posts.
- Deep history on demand. Walk a single profile's post history, or a hashtag's public timeline, back through thousands of posts via automatic load-more pagination.
- URL mode. Paste exact profile or post links to scrape only the accounts and posts you choose.
- Works on any public Mastodon server. Point the instance setting at mastodon.social or any other server address.
- Nothing is dropped from the author account. Every post and reply keeps its author's complete upstream account object; the post's own fields are the mapped set documented below (a poll, application info, and a few link-card details are not carried through).
Use Cases
- Community and trend research: track who and what topics are trending on a Mastodon server over time.
- Social listening: bulk-collect posts under a hashtag to study a topic or event at scale.
- Account monitoring: watch specific profiles for new posts and replies on a recurring schedule.
- Fediverse research and journalism: build datasets of public conversation threads for analysis.
- Community growth tracking: monitor follower counts and posting activity for chosen accounts.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
recordType | "review" |
id | "112233445566778899" |
reviewType | "post" (also "reply") |
sourceKeyword | "climate" (keyword mode only; null otherwise) |
contentText | "Sharing our monthly update on the project roadmap." |
createdAt | "2026-03-14T09:12:00.000Z" |
language | "en" |
url | "https://mastodon.example/@exampleuser/112233445566778899" |
repliesCount | 14 |
reblogsCount | 37 |
favouritesCount | 120 |
inReplyToId | null |
isReblog | false |
rebloggedByAcct | null (set to the booster's handle when isReblog is true) |
tags | ["climate", "sustainability"] |
mediaAttachments | [] (or the attached image/video objects) |
sensitive | false |
authorAcct | "exampleuser" |
authorDisplayName | "Example User" |
authorUrl | "https://mastodon.example/@exampleuser" |
account | { ... complete upstream author object ... } |
Profile records (profiles mode) carry acct, username, displayName, bio, followersCount, followingCount, statusesCount, url, avatar, createdAt and any custom profile fields the account has set, plus a nested reviews array (that profile's own posts and replies) and reviewCount. Review records also carry contentHtml, editedAt, visibility, spoilerText, inReplyToAccountId, mentions and link-card fields (cardUrl, cardTitle) alongside the columns above. Every profile and review record additionally keeps the complete upstream Mastodon account object under account, so nothing the source API returns is ever dropped.
Every post or reply record also carries flat convenience fields: uri, text, author (id, username, acct, displayName, avatar, followersCount, followingCount, statusesCount, url), engagement (replies, reblogs, favourites), media (type, url, previewUrl), isBoost, isReply, source, instance and crawledAt. They are additive and never trigger an UPDATED change in incremental mode.
How to Use
- Pick a mode:
profiles(nested reviews under each profile),reviews(flat, one record per post or reply), orkeyword(bulk posts by topic). - For profiles or reviews mode, pick a profile source (trending, directory, or both), or paste profile and post URLs instead to scrape exactly those.
- Set the limits for your mode (max profiles, posts per profile, replies per post, or max posts for keyword mode) and turn Include replies on or off.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Trending profiles with their reviews (default):
{"mode": "profiles","profileSource": "both","includeReplies": true,"maxProfiles": 10,"maxReviewsPerProfile": 5,"maxRepliesPerPost": 10}
Reviews only, capped, no replies (faster and cheaper):
{"mode": "reviews","profileSource": "trending","includeReplies": false,"maxProfiles": 20,"maxReviewsPerProfile": 10,"maxReviews": 200}
Bulk posts by keyword (high volume, load-more pagination):
{"mode": "keyword","keywords": ["news", "art"],"includeReplies": false,"maxPosts": 5000}
URL mode (specific profiles). The urls field now defaults to a short list with one example of every supported URL shape, so the input form shows what is supported. Default change: that example list is scraped only when Output mode is set to urls; if you leave the list unedited in profiles, reviews or keyword mode it is ignored and the run behaves as before. Any list you edit or paste is always used (keyword mode never uses URLs). URLs that match no supported shape are skipped with a warning in the run log, never silently misread.
| URL shape | Example | Result |
|---|---|---|
| Profile | https://mastodon.social/@Mastodon | that profile |
| Profile replies tab, paginated tab | /@Mastodon/with_replies, /@Mastodon/with_replies?max_id=1 | that profile |
| Profile RSS | /@Mastodon.rss | that profile |
| ActivityPub profile | /users/Mastodon | that profile |
| Web-client profile | /web/@Mastodon | that profile |
| Profile on another instance | https://fosstodon.org/@fosstodon | that profile |
| Post | /@Mastodon/117466985005297414 | that post and its author |
| Post embed | /@Mastodon/117466985005297414/embed | that post |
| ActivityPub post | /users/Mastodon/statuses/117466985005297414 | that post |
| Web-client post | /web/statuses/117466985005297414 | that post |
| Tag page | /tags/news | recent posts with the tag (at most maxProfiles posts per tag URL; since, until and sortBy apply) |
| Explore page | /explore, /explore/posts | authors of trending posts |
| Profiles directory | /directory | active directory profiles |
| JSON API | /api/v1/accounts/lookup?acct=, timelines/tag/, trends/statuses, directory, statuses/ID/context | mapped to the matching shape above |
Posts on another instance and other explore tabs are skipped with a warning. Keywords and the accounts field accept plain names (a leading # or @ is ignored); a keyword or account with no match is reported with a warning.
URL mode (specific profiles and posts):
{"mode": "urls","urls": ["https://mastodon.social/@Mastodon","https://mastodon.social/@Mastodon/117466985005297414"],"includeReplies": false,"maxReviewsPerProfile": 3}
An edited list also works in profiles or reviews mode without setting mode to urls.
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/mastodon-social-scraper").call(run_input={"mode": "keyword", "keywords": ["news"], "maxPosts": 100})for post in client.dataset(run["defaultDatasetId"]).iterate_items():print(post["authorAcct"], post["contentText"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/mastodon-social-scraper').call({ mode: 'keyword', keywords: ['news'], maxPosts: 100 });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Resume and recurring updates
Two different things, both optional:
- Resume from a previous run (
resumeFromRunId): paste a previous run or dataset ID to continue an interrupted crawl. The actor repeats the same search and skips every record it already collected there, so you are not billed twice for the same post or profile. It walks to the same depth as the original run rather than digging further into history, so resume soon after an interruption for the most complete pickup of anything still missing. - Incremental mode (
incrementalMode): turn this on for recurring monitoring of the same search (daily or weekly). The actor remembers its own baseline in a key-value store, keyed by mode, instance and your query settings (includingaccounts,includeBoostsandsince/untilwhen set; themax*depth limits are excluded, so two runs of the same search with different limits still share one baseline; no run or dataset ID to paste). The first run returns everything asNEW. Later runs return onlyNEW,UPDATEDandREAPPEAREDrecords by default. Turn onemitUnchangedoremitExpiredonly if you also want those rows back, since both are extra billed items.
What is compared: every profile field and every post field. What is excluded from the comparison (but still returned): the engagement counters of posts nested inside a profile record, the embedded author's follower, following and post counters on a post record, list ordering, and any field you name in ignoreFieldsForChanges. EXPIRED is only produced after a run that reached the natural end of every scope, so it is never produced for explore (trending/directory) discovery, for runs that include a pasted explore, directory or tag URL, or when a cap, Resume or a failed fetch cut the run short, and replies never expire.
When incremental mode is on, every pushed record gains four fields: changeType (NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED), changedFields (which fields differed from the last run), firstSeenAt and lastSeenAt.
EXPIRED rows (a profile or post that was present before but can no longer be found) are only produced once a run has scanned its whole tracked scope without being cut short by a cap, a failed page, an instance error, or Resume; a partial scan cannot tell "gone" apart from "not reached yet". Turning emitExpired on is also what starts tracking absence at all: with it off, a record that stops appearing simply keeps its last-known state instead of being marked gone, and a record can only be reported REAPPEARED after it was first marked gone by an earlier complete run that had emitExpired on. The actor does not distinguish a post the author deleted from one that disappeared for any other reason (moderation, an account closing, and so on); EXPIRED only means the record could no longer be found in a complete scan. In explore mode specifically, "complete scan" means every profile in that run's discovered top-N from the trending and directory listings was processed, not that every account on the instance was checked; since the trending list and the public directory's ranking can reshuffle between runs, an account that simply drops out of this run's top-N (while still existing) is indistinguishable from one that's genuinely gone, so emitExpired in explore mode can mark, and bill, EXPIRED rows for accounts that are still there. In keyword mode, a reply on a post whose parent turned out UNCHANGED this run is not re-fetched, so a run that completes normally can still tombstone replies it simply didn't re-check that run, which then bill again as REAPPEARED once the parent post next changes; this is a known limitation of keyword mode with Emit expired on, not a sign the reply is actually gone.
Use State key to name a monitoring campaign explicitly, or to intentionally share state across two differently-configured runs; leave it empty to let the actor derive one automatically so different searches never collide.
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step that happens after the scrape; the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | profiles | profiles (nested reviews), reviews (flat), or keyword (bulk posts by topic). |
instanceUrl | string | https://mastodon.social | Any public Mastodon server to read from. |
profileSource | string | both | trending, directory, or both, merged and deduped. Used only when no URLs are given. |
onlyLocal | boolean | false | Directory source: return only accounts local to the chosen instance. |
mode (urls) | string | urls scrapes exactly the listed URLs and returns the profiles output. | |
urls | array | one example per URL shape (see URL mode) | Mastodon URLs of any supported shape. An edited list applies in every mode except keyword. |
accounts | array | (none) | Plain profile names (Mastodon, @Mastodon, user@other.instance). |
keywords | array | (none; prefill: ["news"]) | Topics to bulk-scrape in keyword mode, matched as tags (with or without a leading #). |
includeReplies | boolean | true | Fetch the reply thread under each post. |
includeBoosts | boolean | true | Keep posts a profile boosted from other accounts; turn off for own posts only. |
since | string | none | Keep only posts created on or after this date (YYYY-MM-DD or ISO datetime). Applied to the fetched window per profile, pasted tag URL, keyword tag or pasted post URL. |
until | string | none | Keep only posts created on or before this date (YYYY-MM-DD or ISO datetime). Applied to the fetched window per profile, pasted tag URL, keyword tag or pasted post URL. |
sortBy | string | newest | newest, oldest, mostFavourited, mostReblogged or mostReplied, applied per profile, pasted tag URL or keyword tag. |
hashtags | array | none | Alias of keywords (tags without the # symbol); merged with it. |
maxProfiles | integer | 10 | Minimum 1. Maximum profiles to scrape (profiles mode, or as review sources in reviews mode). Also caps the recent posts fetched per pasted tag, explore or directory URL. |
maxProfiles (max) | integer | Maximum 500. | |
maxReviewsPerProfile | integer | 5 | Minimum 1. Posts to fetch per profile, walked via load-more. |
maxReviewsPerProfile (max) | integer | Maximum 5000. | |
maxRepliesPerPost | integer | 10 | Minimum 0. Replies to keep per post when Include replies is on. |
maxRepliesPerPost (max) | integer | Maximum 200. | |
maxReviews | integer | 1000 | Minimum 0. Hard cap on total reviews returned in reviews mode; 0 (older saved tasks) is treated as 1000. |
maxReviews (max) | integer | Maximum 100000. | |
maxPosts | integer | 500 | Minimum 1. Hard cap on total posts returned in keyword mode, split evenly across keywords. |
maxPosts (max) | integer | Maximum 100000. | |
resumeFromRunId | string | (none) | Continue one specific interrupted run or dataset, without returning or billing records already collected there. |
incrementalMode | boolean | false | Turn on for recurring monitoring; see "Resume and recurring updates" above. |
stateKey | string | (none) | Name or share an incremental-mode monitoring campaign explicitly. |
emitUnchanged | boolean | false | Also return UNCHANGED records (incremental mode only; bills extra rows). |
emitExpired | boolean | false | Also return EXPIRED records (incremental mode only; bills extra rows). |
ignoreFieldsForChanges | array | (none) | Field names left out of change detection (incremental mode only), e.g. bio; use reviews.name for fields inside a profile's reviews list. The fields are still returned. |
proxy | object | Apify Proxy | Connection settings. |
mcpConnectors | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
notionParentPageUrl | string | (none) | Notion connector only: page under which item pages are created. |
maxNotifyListings | integer | 50 | Minimum 1. Cap on items written to each connector per run; does not affect the dataset. |
maxNotifyListings (max) | integer | Maximum 1000. |
Caps: each profile returns at most maxReviewsPerProfile posts (default 5, maximum 5000), each post at most maxRepliesPerPost replies (default 10, maximum 200), and reviews mode at most maxReviews records in total (default 1000, maximum 100000). The per-profile default stays at 5 because every delivered post and reply is a billed record.
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"recordType": "profile","id": "990011223","acct": "exampleuser","username": "exampleuser","displayName": "Example User","bio": "Notes on open community projects and weekend hiking trips.","followersCount": 4820,"followingCount": 312,"statusesCount": 1560,"url": "https://mastodon.example/@exampleuser","avatar": "https://files.mastodon.example/accounts/avatars/000/990/011/223/original/avatar.png","bot": false,"createdAt": "2019-05-02T00:00:00.000Z","fields": [{ "name": "Homepage", "value": "example.org" }],"reviewCount": 2,"reviews": [{"recordType": "review","id": "112233445566778899","reviewType": "post","contentText": "Sharing our monthly update on the project roadmap.","createdAt": "2026-03-14T09:12:00.000Z","repliesCount": 14,"reblogsCount": 37,"favouritesCount": 120,"url": "https://mastodon.example/@exampleuser/112233445566778899","authorAcct": "exampleuser"},{"recordType": "review","id": "112233445566778900","reviewType": "reply","contentText": "Thanks for the detailed writeup, this is helpful context.","createdAt": "2026-03-14T09:40:00.000Z","inReplyToId": "112233445566778899","authorAcct": "sampleuser2"}]}
Output views
The dataset has three views in the Console. Overview shows profiles and posts together. Posts and replies gives one row per post or reply, with the reviews nested inside each profile row unwound into their own rows. Changes (incremental) lists rows reported by incremental mode with what changed.
Plan Requirement
A paid Apify plan is required to run through Apify Proxy: free plans do not include proxy access. The actor starts on the standard Apify Proxy connection and tries a residential one only if that connection is refused (an ordinary timeout or error just retries on a fresh connection of the same kind) before giving up. It also rotates to a fresh exit when Mastodon rate-limits one (HTTP 429, about 300 requests per 5 minutes per address), so large or frequent runs may be slower. Residential is available under Connection if you prefer it, and you can supply your own proxy URLs instead.
When no page can be read the run says so and returns nothing, with one of these messages in the log and status:
- The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required. If you entered your own proxy URLs, check that their credentials are valid.
- No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Mastodon pages.
- Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search.
When the selected connection is unavailable but a backup one works, the log says the pages were served over the backup connection and some pages may still be unavailable.
FAQ
How much does it cost?
You pay per run start and per record returned. In profiles mode, a surcharge applies once per profile whose replies were fetched and actually included in the pushed record; reviews and keyword mode never add this surcharge, since every reply there is already its own billed record. The Pricing tab shows the current rates. Use the max limits to cap the cost of any run.
Is it legal to scrape Mastodon?
This actor collects only information a Mastodon server already serves through its own public API to any visitor. You are responsible for how you use the result: usernames, bios and post content can identify real people, so follow the instance's terms, respect any account that has set itself to a locked or non-discoverable state, and get legal advice before any commercial redistribution.
Can I get only new or changed posts on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared records since the last run, and anything unchanged is not billed unless you turn Emit unchanged on.
What is the difference between Resume and Incremental mode?
Resume continues one specific interrupted run using a run or dataset ID you paste in; it is a one-time cleanup step. Incremental mode is for scheduling the same search again and again and getting a self-tracked diff each time, with no ID to paste. Use Resume after a run stops partway through; use Incremental mode for ongoing monitoring.
Why did my run finish with zero results instead of failing?
If a mode's source could not be reached at all, or nothing matched your input (an empty keyword list, URLs that do not resolve, and so on), the run finishes successfully with an explanatory status message and no dataset items, so you are never billed for a result you did not get. A run only fails outright when the input itself cannot be honored, such as a resumeFromRunId that does not resolve to a run or dataset you can access, or turning on Incremental mode together with resumeFromRunId while that search already has saved incremental state.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each record into Notion, Linear or Airtable.
🔗 Want more social media data?
Pair this actor with these related scrapers from the same team:
| 📱 TikTok Profile, Hashtag, Search, Video & Trending Scraper Scrape TikTok without login. Extract profiles, bios, follower stats and videos; search by... | 📱 Lemon8 Profile Scraper Scrape Lemon8 user profiles with automated multi-profile discovery. Extract profile data... |
| 📱 Lemon8 Search Scraper Scrape Lemon8 posts by keyword across multiple regions. Extract posts, images, videos... | 📱 Truth Social Scraper Scrape public Truth Social profiles, posts, media, metrics, author data, and complete... |
| 📱 Lemon8 Feed Scraper Scrape Lemon8 feeds across 22 categories and 10+ regions. Extract posts, images, videos... | 📱 Reddit⚡ Lightning Scrape Reddit posts, comments, and media from any subreddit or user profile - no login or... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.