Wattpad Scraper at a glance
Wattpad Scraper is a ready-to-run Apify actor from abotapi for collecting social media data. Its documented output includes cover, story, change type (incremental mode), story id and url, which you can export as JSON, CSV or Excel or read through the Apify API. It costs $1.80 per 1,000 results on the Apify Free plan and from $1.30 on paid plans, with smaller per-run or optional feature charges listed on Apify.
- Runs on
- Apify cloud: console, API, schedules and integrations
- Data type
- Social Media
- Pricing
- $1.80 per 1,000 results (Free plan), from $1.30 on paid plans
- Output
- Documented below · JSON, CSV, Excel
- Inputs
- 32 options
- Run success
- 97.5% of completed public runs in the last 30 days
Scrape Wattpad stories by keyword, tag, category, language or URL. Extract authors, reads, votes, tags, completion status and chapter lists, with optional chapter text, full comment threads, inline paragraph comments and replies.
What Wattpad Scraper can do
- Inline paragraph comments, matched to their paragraph. Wattpad's most distinctive data is the comment a reader leaves on one line of one chapter. This actor returns those, each carrying the id of the paragraph it was written on, so a comment can be placed next to the sentence that caused it. Comments left at the end of a chapter come back in the same pass and are labelled separately.
- One row per story, chapters and comments nested inside. A run that reads ten stories and forty thousand comments exports ten rows, so the dataset stays joinable and you are billed for stories, not for comments.
- The chapter list costs nothing. Every chapter's id, title, link, length, publish stamp and its own read, vote and comment counters arrive inside the story record. Only the chapter body text and the comment thread need extra requests, and a story that takes them is billed one detail-enrichment event once, however many chapters or comment pages it needed.
- Honest filters. The site's index accepts a tag parameter and a maturity parameter and then ignores both, which is easy to mistake for a working filter. This actor applies those two itself, over the data the site returns, and says so in the input rather than handing back an unfiltered set that looks filtered.
- Real end-of-feed detection. The comment feed signals "no more pages" by dropping a key, not by returning an empty list, and a missing comment list means a refused request rather than a quiet thread. Those two cases are told apart, so a blocked request never masquerades as a story with no comments.
- "No results" is checked before it is reported. A search that returns nothing on its first page looks exactly like a search the site briefly declined to answer: same status, same empty body. Before a run reports an empty result set, it spends one small check read that the site always answers. If that check comes back with results, your search really is empty and the run succeeds quietly. If it does not, the run reports a connection problem instead of handing you a successful run with zero rows.
Choose what to collect
These inputs let you configure Wattpad Scraper - Stories, Chapters, Authors & Comments. Open the actor on Apify to enter your values and review the current options.
| Input | What it controls |
|---|---|
modestring · required | Choose 'search' to walk the site's story index by keyword, category, language and completion state, or 'url' to scrape pasted story links, chapter links and plain story ids. |
querystring | Only used when mode = search, ignored in url mode. Free text, for example 'werewolf', 'enemies to lovers' or an author name. Leave empty to read the site's default listing instead of a keyword search. A keyword search reaches a much deeper result set than the category and default listings, which the site caps at 1,500 rows. |
categorystring | Only used when mode = search, ignored in url mode. One of the site's own categories. The list is checked against the site's OWN published category list on every run, so a category the site has renamed or retired is reported by name instead of being dropped silently. Note the site's own behaviour: a category listing is capped at 1,500 rows, far below a keyword search. |
filterstring | Only used when mode = search, ignored in url mode. Keep every story, or keep only the ones the author has marked finished. Applied by the site itself: a completed-only keyword search returns a genuinely smaller result set, not the same set with rows dropped afterwards. |
languagestring | Only used when mode = search, ignored in url mode. The language a story is written in. Applied by the site itself: the name you pick is resolved to the site's own numeric language id against the list it publishes on the day of the run, and that id is sent with the search, so the site returns a smaller result set instead of this actor reading the whole index and dropping most of it. The 26 values offered are the languages the site says stories can be written in; the other languages it publishes exist for its own interface only. A value the site has since retired stops the run by name. Leave on 'Any language' to search every language. |
startUrlsarray | Only used when mode = url, ignored in search mode. Story addresses (/story/-slug, also with /parts), chapter addresses (/-slug), plain numeric story ids, search pages (/search/, with ?page?filter=complete?language=), listings (/stories/, with ?page; ?sort is not offered and warns), author pages (/user/) and the public story links under /api/v3/stories. Mix freely. A ?page or limit link reads that one page and counts as a partial scan. The mobile host and reading lists are not supported and log a warning per link. |
tagsarray | Optional, applies in both modes. Tags written the way they appear under a story, for example 'werewolf', 'enemiestolovers' or 'shortstory'. Capitals and spacing do not matter. A story is kept when it carries at least one of them. Leave empty to keep every story. Because the site's own index ignores its tag parameter, this is applied to the tag list the site returns on every story, so no extra request is made and nothing is charged for it. |
Show 25 more inputs
| Input | What it controls |
|---|---|
matureboolean | On by default, applies in both modes. Leave on to keep every story. Switch off to drop stories the site flags as mature, judged on each story's own flag after it is read. Worth knowing before you rely on it: the site's public search index does not return mature-flagged stories at all, so this bites in url mode and on stories reached by their own link, and changes almost nothing in search mode. |
authorsarray | Author-stories mode: author names (or profile links) whose published stories are returned. While this is set, Start URLs are ignored. |
accessstring | Keep only free or only paywalled stories. Applied by the actor against the paid-story flag the source publishes on each story. |
minimumVotesinteger | Keep only stories with at least this many votes. Applied by the actor after the source answers, so a high value walks more pages. |
minimumReadsinteger | Keep only stories with at least this many reads. Applied by the actor after the source answers, so a high value walks more pages. |
sortBystring | The source has no sort control, so in search mode the actor ranks the first few hundred candidates it reads (highest first) and keeps the top Max results; in URL and author mode it only reorders the first Max results stories collected. A ranked, capped run counts as a partial scan. |
fetchAuthorProfileboolean | Adds an authorProfile block (display name, bio, location, website) with one extra public profile read per distinct author. No extra charge. |
fetchChaptersboolean | On by default and free of charge. Returns the story's chapter list: each chapter's id, title, link, publish and update stamps, length, and its own read, vote and comment counters. These arrive inside the story record, so this costs no extra request and never triggers a detail-enrichment charge on its own. |
fetchChapterTextboolean | Off by default. Downloads the body text of every chapter returned, split into the site's own numbered paragraphs so an inline comment can be matched back to the paragraph it was written on. This is ONE EXTRA REQUEST PER CHAPTER, and a long story can carry over a hundred chapters, so switch it on deliberately. The story is billed a single detail-enrichment event however many chapters it took. |
fetchCommentsboolean | Off by default. Walks the story's comment thread chapter by chapter and returns every comment with its author, body, exact time, reply count and permalink. Both kinds come back in one pass: comments left at the end of a chapter, and inline comments written against one specific paragraph, each carrying the paragraph id it belongs to. Extra requests, billed as the same single detail-enrichment event per story. |
commentScopestring | Used with the comment thread. The site returns end-of-chapter comments and inline paragraph comments in one walk, so all three settings cost the same requests; this decides what is kept in the record. Inline comments carry the paragraph they were written on, end-of-chapter comments do not. |
maxCommentsPerStoryinteger | Cap on how many comments are returned for one story, counted across all of its chapters. Use 0 to return the story without its thread. Popular stories carry hundreds of thousands of comments, so a cap keeps a run predictable. Each record reports how many were returned and whether the thread was exhausted. |
includeRepliesboolean | Off by default. A comment's reply count always comes back; switch this on to also fetch the replies themselves, which is one extra request per comment that has any. Still billed inside the same single detail-enrichment event for that story. |
maxRepliesPerCommentinteger | Used only when replies are fetched. Cap on how many replies are returned under one comment. Use 0 for every reply the site publishes for it. |
maxItemsinteger | Maximum number of stories to return across the whole run. Use 0 for unlimited. Note the site's own limit underneath it: one search scope stops serving rows past result 10,000 even when it advertises a far larger total, and category and default listings stop at 1,500. When a scope is cut short that way the run says so, and incremental mode will not mark anything expired from a scan it knows was incomplete. |
maxPagesinteger | Maximum number of index pages read per search scope. 0 means no limit, which is the default: the run then stops only at Max results, at the end of the results, or at the site's own depth limit. |
resumeFromRunIdstring | Paste a previous run ID or dataset ID to continue a large pull without returning or charging for stories already collected there. Use this after an interrupted run. For recurring daily monitoring of the same search, use Incremental mode below instead. |
incrementalModeboolean | Turn this on for daily or recurring monitoring. The first run returns every matching story as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED stories. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately for each mode, target and filter setup; use State key to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above. |
stateKeystring | Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to let the actor derive a key automatically from the mode, targets and filters, so different searches never mix state with each other. |
emitUnchangedboolean | Off by default. Turn on to also return stories that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you want the full snapshot every run. |
emitExpiredboolean | Off by default. Turn on to also return stories that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search, so never when Max results capped it, never when Resume was used, and never when a scope hit the site's own result-depth limit. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking. |
ignoreFieldsForChangesarray | Optional. Field names to leave out of UPDATED change detection, on top of the built-in handling (read, vote and comment counters, the chapter list, the comment list and run-shape fields never trigger a false UPDATED). Use a plain name such as coverUrl, or a dotted name for a nested field. The fields stay in the output; they only stop deciding UPDATED and stop appearing in changedFields. Changing this list does not mark every tracked story as changed. |
mcpConnectorsarray | Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write or digest. Leave empty to skip; never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com). |
notionParentPageUrlstring | URL or id of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors. |
maxNotifyListingsinteger | Cap on items written to each connector per run. Does not affect the dataset. |
Fields in the output
The documented dataset includes the fields below. Availability can depend on the source page and selected mode.
coverUrlCover (image)titleStorychangeTypeChange type (incremental mode) (text)storyIdStory IDurlURL (link)authorAuthorauthorUrlAuthor profile (link)descriptionBlurblanguageLanguagecategoriesCategories (array)tagsTags (array)isCompletedFinished (boolean)Show 28 more fields
isMatureMature flagged (boolean)ratingRating code (number)copyrightCopyright code (number)numPartsChapters (declared) (number)readCountReads (number)voteCountVotes (number)commentCountComments (declared) (number)wordCountWords (number)firstPartIdFirst chapter IDcreatedAtCreated (date)modifiedAtUpdated (date)chaptersChapters (array)chaptersReturnedChapters returned (number)commentsComment thread (array)commentsReturnedComments returned (number)commentsCompleteThread exhausted (boolean)scanCompleteScope fully scanned (boolean)enrichmentSkippedEnrichment skipped (boolean)scrapedAtScraped at (date)sourceUrlSource (link)changedFieldsChanged fields (incremental mode) (array)firstSeenAtFirst seen (incremental mode) (date)lastSeenAtLast seen (incremental mode) (date)authorDisplayNameAuthor nameauthorProfileAuthor profileaccessStatusAccesslastPublishedAtLast chapter publisheddiscoveryInputFound byExample output
An example from this actor’s documentation. Values are illustrative; this is not a fresh live result.
{
"storyId": "100000001",
"title": "The Sample Werewolf Chronicles",
"url": "https://www.wattpad.com/story/100000001-the-sample-werewolf-chronicles",
"description": "A one paragraph blurb, exactly as the author wrote it.",
"coverUrl": "https://img.example-cdn.test/cover/100000001-256.jpg",
"author": "sample_author",
"authorUrl": "https://www.wattpad.com/user/sample_author",
"language": "English",
"categories": [
"Werewolf"
],
"tags": [
"werewolf",
"alpha",
"mates"
],
"isCompleted": true,
"isMature": false,
"rating": 1,
"copyright": 1,
"numParts": 44,
"readCount": 1234567,
"voteCount": 89012,
"commentCount": 34567,
"wordCount": 412345,
"firstPartId": "200000001",
"createdAt": "2026-01-01T00:00:00Z",
"modifiedAt": "2026-01-02T00:00:00Z",
"chapters": [
{
"chapterId": "200000001",
"title": "Chapter 1: The Sample Beginning",
"url": "https://www.wattpad.com/200000001-sample-chapter-one",
"wordCount": 8123,
"rating": 1,
"isDraft": false,
"createdAt": "2026-01-01T00:00:00Z",
"modifiedAt": "2026-01-01T00:00:00Z",
"readCount": 98765,
"voteCount": 4321,
"commentCount": 876,
"photoUrl": "",
"videoId": "",
"text": "The chapter body appears here when chapter text is switched on.",
"paragraphs": [
{
"id": "5a1b2c3d",
"text": "The first paragraph of the chapter."
}
]
}
],
"chaptersReturned": 1,
"comments": [
{
"commentId": "200000001_5a1b2c3d_1767225600_aaaa1111",
"scope": "inline",
"paragraphId": "5a1b2c3d",
"text": "This line broke me.",
"author": "sample_reader_01",
"authorUrl": "https://www.wattpad.com/user/sample_reader_01",
"createdAt": "2026-01-01T00:00:00Z",
"modifiedAt": "2026-01-01T00:00:00Z",
"status": "active",
"replyCount": 1,
"url": "https://www.wattpad.com/200000001-sample-chapter-one",
"replies": [
{
"commentId": "200000001_5a1b2c3d_1767225601_bbbb2222",
"scope": "inline",
"paragraphId": "5a1b2c3d",
"text": "Same, I had to put the phone down.",
"author": "sample_reader_02",
"authorUrl": "https://www.wattpad.com/user/sample_reader_02",
"createdAt": "2026-01-01T00:00:00Z",
"modifiedAt": "2026-01-01T00:00:00Z",
"status": "active",
"replyCount": 0,
"url": "https://www.wattpad.com/200000001-sample-chapter-one",
"replies": []
}
]
},
{
"commentId": "200000001__1767225602_cccc3333",
"scope": "part",
"paragraphId": null,
"text": "Updating soon, I hope.",
"author": "sample_reader_03",
"authorUrl": "https://www.wattpad.com/user/sample_reader_03",
"createdAt": "2026-01-01T00:00:00Z",
"modifiedAt": "2026-01-01T00:00:00Z",
"status": "active",
"replyCount": 0,
"url": "https://www.wattpad.com/200000001-sample-chapter-one",
"replies": []
}
],
"commentsReturned": 2,
"commentsComplete": false,
"scanComplete": true,
"enrichmentSkipped": false,
"scrapedAt": "2026-01-02T00:00:00Z",
"sourceUrl": "https://www.wattpad.com/story/100000001-the-sample-werewolf-chronicles",
"changeType": "NEW",
"changedFields": [],
"firstSeenAt": "2026-01-02T00:00:00Z",
"lastSeenAt": "2026-01-02T00:00:00Z"
}Ways to use this data
- Fanfiction and reader community research: track tag, category and completion-state trends across genres to see which themes and pairings are growing.
- Content licensing and translation scouting: watch a language, category or tag for newly completed stories worth optioning, without reading the whole index by hand.
- Reader engagement studies: pull the full comment thread, inline and end-of-chapter together, to see exactly where in a story readers react.
- Discovery and recommendation tools: build genre or tag based reading lists straight from the site's own categories and tags.
- Portfolio and rival monitoring for authors: run incremental mode on a story link to see new chapters, retitles and completion-state changes over time, without re-reading engagement counters that move on their own.
- Moderation and brand-safety screening: check a story's mature flag and tags before referencing it in another product.
Before you run
Start with a small input and check the resulting records against your expected fields. Source content and actor options can change; the current Apify listing is the reference for availability and pricing.
Questions about this actor
What data does Wattpad Scraper return?
The documented output for Wattpad Scraper includes cover, story, change type (incremental mode), story id, url, author, author profile and blurb. Availability of each field depends on the source page and the selected mode.
How much does Wattpad Scraper cost?
It costs $1.80 per 1,000 results on the Apify Free plan and from $1.30 on paid plans, with smaller per-run or optional feature charges listed on Apify. The Apify listing shows the current rate for every plan.
Can I get only new or changed stories on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Later runs then return only NEW, UPDATED and REAPPEARED stories, and unchanged ones are not returned or billed unless you turn on Emit unchanged.
Why does search mode never return mature stories?
That is the site's own public index, not a gap in this actor: an anonymous search always comes back with the mature flag false, even on a query that plainly matches mature work. Fetch a mature story by its own link or id in url mode instead, where the flag is reported correctly.
Why did my run fail instead of returning an empty dataset?
If the source refuses every request, the run stops with a clear message so "no stories matched" is never confused with "nothing could be read". Run it again in a few minutes. A search whose first page comes back empty is checked with one extra read before it is ever reported as empty, so a brief refusal is not published as a genuinely empty result.
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 results into Notion, Linear or Airtable.
