# Reddit Search Scraper — Posts by Keyword, Score & Date (`scrapersdelight/reddit-search-scraper`) Actor

Search Reddit by keyword — site-wide or inside chosen subreddits — and get posts with real score, upvote ratio, comment count, author, flair, selftext and timestamps from Reddit's own JSON. Sort by relevance, new or top; date window; strict keyword match; optional top comments. No login, no API key.

- **URL**: https://apify.com/scrapersdelight/reddit-search-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Social media, AI, Marketing
- **Stats:** 3 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 per post returneds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Reddit Search Scraper — Posts by Keyword, Score & Date

Search [Reddit](https://www.reddit.com) by keyword — across all of Reddit or inside the subreddits
you name — and get one clean row per post: **id, subreddit, subreddit\_subscribers, title, author,
selftext, url, domain, permalink, score, ups, upvote\_ratio, num\_comments, num\_crossposts,
created\_iso, edited, over\_18, locked, stickied, link\_flair\_text, post\_hint, thumbnail**, plus the
**query, subreddit, sort and time range it was found under**. Optionally nest each post's **top
comments** inside the row. Every row is read from **Reddit's own JSON**, so `score` and
`num_comments` are the real numbers — **100% filled on 100/100 rows of the shipped default run,
with 98 of 100 scores above 0** — not the zeros an RSS-only scraper has to ship. No login, no
Reddit account, no API key, no CAPTCHA solving.

**Scope: Reddit stops paging a single search at roughly 250 results** — measured live on the
Apify platform on 2026-08-22: one unbounded site-wide search for "web scraping" returned **237
distinct posts across 3 pages and then ran out** (twice — once locally, once on the platform, same
237\). That ceiling is Reddit's, not this Actor's; the way past it is fan-out — **5 queries × 5
subreddits, unbounded, delivered 3,577 distinct posts in 76 seconds** on the platform — which this
Actor does for you, de-duplicated before billing.

```json
{
  "queries": ["web scraping"],
  "sort": "relevance",
  "time": "all",
  "maxItems": 100
}
```

Click **Try for free** and hit **Start** — that block is literally the input the Actor ships with.
It returns **100 posts for $0.20**. A run on those shipped defaults on the Apify platform
(2026-08-22) returned **100 rows in 21 seconds, 100 distinct posts, charged exactly 100**, with
score 100%, num\_comments 100%, flair 61%, thumbnail 40%. Every field in the sample row below came
out of a run like that one.

***

### The wedge: real scores, a date window, strict matching, and comments that only cost you when they exist

Three things the cheap Reddit search scrapers on the Store cannot do, and one they do differently:

- **Real `score` / `upvote_ratio` / `num_comments` on every JSON row.** Reddit restricted its
  public paths in 2025, and a scraper that falls back to Reddit's RSS "recovery" pages cannot fill
  them — an Atom entry carries no score, upvote ratio or comment count at source. This Actor clears
  Reddit's browser challenge in a real Chromium session and then reads `/search.json` the way
  Reddit's own front end does, so the engagement numbers are the live ones. Measured on the
  shipped default: **score 100%, 98/100
  above zero; num\_comments 100%, 100/100 above zero; upvote\_ratio 100%.**
- **A date window on top of Reddit's time filter.** Reddit search has `t=day|week|month|year|all`
  and nothing finer. `dateFrom` / `dateTo` are applied to every result's `created_utc` before it
  is billed, and with `sort: "new"` the Actor stops paging the moment a whole page is older than
  `dateFrom` — so a "last 30 days in r/learnpython" monitor costs you exactly the rows in that
  window. Measured: a 30-day window over 4 searches removed **47 out-of-window posts, none of them
  charged**.
- **Strict keyword match.** Reddit's search is fuzzy and happily returns posts containing none of
  your words. `strictSearch: true` keeps only posts whose title + selftext contain every word of
  the query (case-insensitive; quotes and common stop-words are ignored). Rows it removes are never
  billed. **Do not combine it with Reddit's search operators** — `-selenium` or `title:foo` are
  matched as literal words, so a query using them will filter to zero.
- **Comments as a success-billed add-on, nested in the post row.** Turn on
  `includeTopComments` and each post gains `topComments[]` (id, author, body, score, created time,
  depth, permalink). The **`comments-enriched`** event is charged per post **only when at least one
  comment actually came back** — a post with zero comments, or a thread Reddit would not serve,
  costs nothing extra. Measured: 44 posts, 42 enriched and charged, 2 not. The dataset stays **one
  record type** — one row per post, comments inside it — so your CSV never mixes post rows with
  comment rows.

***

### Read this before you buy rows

1. **Reddit caps one search listing at ~250 posts.** Measured 237 on "web scraping" site-wide,
   all time. If you ask for 1,000 from one query you will get ~240 and a clean SUCCEEDED run, not
   an error — Reddit simply stops handing out pages and the Actor stops with it.
   To get more, add queries (synonyms, phrasings) and/or subreddits — every query × subreddit pair
   is its own 250-post listing, and the Actor de-duplicates across all of them before billing.
2. **`score` is a snapshot, and Reddit fuzzes it.** Reddit deliberately jitters vote counts by a
   few points on every read to defeat vote manipulation. Two runs minutes apart can differ by ±1–3
   on the same post. `upvote_ratio` is the stabler engagement signal.
3. **`dateFrom` / `dateTo` filter AFTER Reddit answers.** Reddit has no exact-date operator, so the
   Actor still pays Reddit one request per 100 results and then keeps the in-window rows. Narrow
   windows over a popular keyword therefore cost time, not money — use `sort: "new"` so the walk
   stops early, and `time` to pre-narrow what Reddit returns.
4. **`transport: "rss"` rows have no score.** If the browser rung cannot open a session at all
   (proxy outage, interstitial not clearing), the Actor falls back to Reddit's Atom search feed
   for that search. Those rows carry `transport: "rss"` and keep only `id`, `name`, `title`,
   `author`, `subreddit`, `subreddit_prefixed`, `permalink`, `created_utc`/`created_iso` and (on
   text posts) `selftext`. **Everything else is `null`, never a fake 0** — including `score`, `ups`,
   `upvote_ratio`, `num_comments`, `subreddit_subscribers`, `url`, `domain`, `over_18`, `locked`,
   flair and thumbnail. Because `over_18` is null there, the NSFW filter cannot drop anything on
   this rung. The shipped default run used the JSON rung for 100/100 rows; the fallback exists so a
   monitor keeps delivering titles and permalinks on a bad day. Set `allowRssFallback: false` if
   you would rather receive nothing than rows without scores.
5. **`selftext` is empty (`""`) for link posts, not null.** 2026-08-22 default run: 100% of rows
   carry the field, **74% have non-empty text**; link posts have an empty string and
   `is_self: false`. Filter on `is_self`.

***

### What you get

One row per unique post, de-duplicated run-wide on Reddit's `t3_` fullname. Timestamps are UTC
(`created_utc` seconds + `created_iso` ISO-8601). `edited` is `false`, or the ISO time of the
last edit.

| Group | Fields | Example |
|---|---|---|
| **Identity** | `id`, `name`, `permalink`, `url`, `domain`, `is_self` | `1tbuq4g` · `t3_1tbuq4g` · `asadfix.github.io` |
| **Where** | `subreddit`, `subreddit_prefixed`, `subreddit_subscribers` | `webscraping` · `r/webscraping` · `101668` |
| **Content** | `title`, `selftext`, `author`, `author_fullname`, `link_flair_text`, `post_hint`, `thumbnail`, `is_video` | `The Complete Web Scraping & Anti-Bot Bypass Guide 2026` · `pimterry` · `Getting started 🌱` |
| **Engagement** | `score`, `ups`, `upvote_ratio`, `num_comments`, `num_crossposts`, `total_awards_received` | `136` · `0.89` · `53` |
| **Time** | `created_utc`, `created_iso`, `edited` | `1778665169` · `2026-05-13T09:39:29.000Z` |
| **Flags** | `over_18`, `spoiler`, `stickied`, `locked`, `distinguished` | `false` |
| **Comments (optional)** | `topComments[]` (`id`, `name`, `parent_id`, `depth`, `author`, `body`, `score`, `created_iso`, `is_submitter`, `permalink`, `post_id`), `commentsEnriched` | 6.5 comments per post at `maxCommentsPerPost: 10` (platform run `4LOznUvChdyGuVfN6`, 30 posts) |
| **Provenance** | `search_query`, `search_subreddit`, `search_sort`, `search_time`, `transport`, `scraped_at` | `web scraping` · `null` (site-wide) · `relevance` · `all` · `json` |

The dataset ships with a saved **table view** (title, subreddit, author, score, upvote ratio,
comments, posted time, flair, query, transport, permalink, link).

***

### Field fill — measured on the shipped default run

100 posts, site-wide "web scraping", sort relevance, all time — platform run `PGCleYzCpe5YwEybM`,
2026-08-22. Sorted by fill, so the sparse fields are impossible to miss.

| Field | Fill | Notes |
|---|---|---|
| `id` / `name` / `permalink` / `url` / `domain` | 100% | |
| `subreddit` / `subreddit_prefixed` / `subreddit_subscribers` | 100% | 57 distinct subreddits in the 100 rows |
| `title` / `author` / `author_fullname` | 100% | `[deleted]` authors are still a string |
| `selftext` / `is_self` | 100% present · **74% non-empty** | empty string on link posts — see gotcha 5 |
| **`score` / `ups` / `upvote_ratio`** | **100%** | **98/100 scores above 0** — the field the RSS scrapers cannot fill |
| **`num_comments`** | **100%** | 100/100 above 0 |
| `num_crossposts` / `total_awards_received` | 100% | real zeros are zeros |
| `created_utc` / `created_iso` / `edited` | 100% | |
| `over_18` / `spoiler` / `stickied` / `locked` / `is_video` | 100% | booleans |
| `search_query` / `search_sort` / `search_time` / `transport` / `scraped_at` | 100% | provenance |
| **`link_flair_text`** | **61%** | only where the subreddit uses flair |
| **`post_hint`** | **55%** | Reddit's media-type hint; absent on plain text posts |
| **`thumbnail`** | **40%** | only link/image posts carry a preview |
| **`search_subreddit`** | **0% on a site-wide run** | `null` by design when you did not restrict to subreddits; 100% on a subreddit-restricted run |
| **`distinguished`** | **0%** | only moderator/admin posts carry it — a real rarity, not a gap |
| **`topComments`** | **0% with the add-on off** | **30/30 posts (100%)** filled on the platform comments run; 42/44 (95%) on a strict 30-day window locally |

Plan against that table and remember it is one keyword. **Every run prints its own counts in the
log** — pages fetched per rung, duplicates dropped, rows removed by each filter, and the uniqueness
verification.

***

### How to run it

#### 1. Site-wide keyword search (the usual choice)

```json
{
  "queries": ["web scraping", "\"headless browser\" -selenium"],
  "sort": "relevance",
  "time": "year",
  "maxItems": 300
}
```

Reddit's own search syntax works inside a query: quotes for an exact phrase, `OR`, a leading `-`
to exclude, and field prefixes (`title:`, `author:`, `subreddit:`, `flair:`, `self:yes`). Each
query is its own search; a post matching two of them is returned and billed once.

#### 2. Inside chosen subreddits

```json
{
  "queries": ["pricing", "refund"],
  "subreddits": ["SaaS", "startups", "r/smallbusiness"],
  "sort": "top",
  "time": "month",
  "maxItemsPerQuery": 50,
  "maxItems": 300
}
```

Every query is run inside every subreddit (2 × 3 = 6 searches), each with its own 250-post
ceiling. `maxItemsPerQuery` stops one broad query from spending the whole budget before the
others run.

#### 3. Keyword monitor — last 30 days, strict, newest first

```json
{
  "queries": ["web scraping"],
  "subreddits": ["webscraping", "learnpython"],
  "sort": "new",
  "dateFrom": "2026-07-23",
  "strictSearch": true,
  "maxItems": 200
}
```

A 2-query × 2-subreddit shape like this, measured **locally** on 2026-08-22: **44 posts delivered,
47 out-of-window posts and 4 duplicates dropped before billing** (with comments on). Save it as a
Task, attach a weekly Schedule, and move `dateFrom` forward each week.

#### 4. Posts with their top comments

```json
{
  "queries": ["best CRM for small business"],
  "includeTopComments": true,
  "maxCommentsPerPost": 10,
  "maxItems": 30
}
```

One extra Reddit request per post; roughly doubles run time. Charged on `comments-enriched` only
for posts where comments actually came back. This exact input on the platform (30 posts):
**38 seconds, 30 rows, 30/30 threads read, charged `post-scraped` 30 + `comments-enriched` 30**.

#### Scheduling and integrations

Save the input as a **Task** (Console → the Actor → *Save as Task*), then attach an Apify
**Schedule**. The dataset is available over the REST API and through the standard Apify
integrations (Zapier, Make, n8n, webhooks, MCP) like any other Actor. From the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~reddit-search-scraper/runs?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"queries":["web scraping"],"subreddits":["webscraping"],"sort":"new","maxItems":100}'
```

***

### Sample row

A real row from the shipped-default run, captured 2026-08-22 17:25 UTC.

```jsonc
{
  "id": "1tbuq4g",
  "name": "t3_1tbuq4g",
  "subreddit": "webscraping",
  "subreddit_prefixed": "r/webscraping",
  "subreddit_subscribers": 101668,
  "title": "The Complete Web Scraping & Anti-Bot Bypass Guide 2026",
  "author": "pimterry",
  "author_fullname": "t2_3vf8x",
  "selftext": "",
  "is_self": false,
  "url": "https://asadfix.github.io/scraping-guide/",
  "domain": "asadfix.github.io",
  "permalink": "https://www.reddit.com/r/webscraping/comments/1tbuq4g/the_complete_web_scraping_antibot_bypass_guide/",
  "score": 136,
  "ups": 136,
  "upvote_ratio": 0.89,
  "num_comments": 53,
  "num_crossposts": 0,
  "total_awards_received": 0,
  "created_utc": 1778665169,
  "created_iso": "2026-05-13T09:39:29.000Z",
  "edited": false,
  "over_18": false,
  "spoiler": false,
  "stickied": false,
  "locked": false,
  "is_video": false,
  "link_flair_text": "Getting started 🌱",
  "post_hint": "link",
  "thumbnail": "https://external-preview.redd.it/OaTdPqed-x064p4EFyiLkNhdfqOJOqbTEScgdj84rqI.png?width=140&height=73&auto=webp&s=614026ad6ac9383ed04d84be45ec98ca40c5a578",
  "distinguished": null,
  "search_query": "web scraping",
  "search_subreddit": null,
  "search_sort": "relevance",
  "search_time": "all",
  "transport": "json",
  "topComments": [],
  "commentsEnriched": false,
  "scraped_at": "2026-08-22T17:25:13.447Z"
}
```

Fields people misread:

- `score` and `ups` are the same number on a post (Reddit no longer exposes downvotes);
  `upvote_ratio` is the share of upvotes, 0–1.
- `search_subreddit` is `null` on a site-wide search and the subreddit name on a restricted one;
  `subreddit` is always where the post actually lives.
- `transport` is `"json"` for a full row and `"rss"` for a fallback row with null scores.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| **🔎 What to search** | | | |
| `queries` | string list | `["web scraping"]` | One keyword phrase per line; Reddit search syntax allowed. Empty = the documented sample. |
| `subreddits` | string list | `[]` | Restrict every query to each listed subreddit. Empty = all of Reddit. |
| `sort` | select | `relevance` | `relevance` · `new` · `top` · `hot` · `comments`. |
| `time` | select | `all` | Reddit's server-side `t=` filter. |
| **🎚️ Filters** | | | |
| `dateFrom` / `dateTo` | string | `""` | `YYYY-MM-DD` (UTC) or ISO-8601; applied to `created_utc` before billing. |
| `strictSearch` | boolean | `false` | Keep only posts whose title + selftext contain every query word. |
| `includeNsfw` | boolean | `true` | Uncheck to exclude over-18 posts. |
| **💬 Comments** | | | |
| `includeTopComments` | boolean | `false` | Nest each post's top comments into `topComments[]`. |
| `maxCommentsPerPost` | integer | `10` | 1–100, Top order, top-level + direct replies. |
| **💷 Limits & cost** | | | |
| `maxItems` | integer | `100` | **Your hard cost cap.** 100 = $0.20, 1,000 = $2.00. 0 = no limit (internally capped at 100,000). |
| `maxItemsPerQuery` | integer | `100` | Cap per query × subreddit search. 0 = unlimited (Reddit stops at ~250 anyway). |
| **⚙️ Advanced** | | | |
| `allowRssFallback` | boolean | `true` | Fall back to Reddit's Atom feed (rows without scores) when the browser rung cannot open. |
| `proxyConfiguration` | proxy | Apify **RESIDENTIAL** | Required for the browser rung — see the ladder below. |
| `requestDelayMs` | integer | `700` | Pacing between Reddit JSON calls. |

***

### Pricing

**$0.002 per post returned — $2 per 1,000**, charged on the `post-scraped` event.
**$0.003 per post comment-enriched**, charged on `comments-enriched` only when the add-on is on
and at least one comment was returned for that post. No monthly fee from this Actor.

| Run | Posts | Cost |
|---|---|---|
| The shipped default (100 posts) | 100 | **$0.20** |
| One keyword to Reddit's ceiling | ~240 | **$0.48** |
| 4 queries × 5 subreddits, 50 each | 1,000 | **$2.00** |
| 100 posts + top comments on all of them | 100 + 100 | **$0.50** |

- **You are charged for rows delivered.** Duplicates across queries and subreddits are dropped
  before billing; posts removed by the date window, strict match or NSFW filter are never charged.
- **Charged first, then pushed** — at a budget cap (`maxTotalChargeUsd` or a free-tier balance)
  you get whole rows and stop, never a half-billed dataset. The Actor reads your remaining charge
  headroom at startup and stops fetching at the affordable count, so a capped run does not burn
  compute on rows it cannot deliver.
- **Comments are never charged for a post that got none.**

Why $0.002 and not the $0.001 floor: this Actor runs a real browser on residential IPs per run to
get real scores. The cheapest Reddit scrapers in the lane sit at $0.00099–$0.00149 (checked
2026-08-22); some of them read Reddit's RSS pages, where no score is available at source.

***

### Honest limits

- **~250 posts per search is Reddit's ceiling**, measured at 237 on 2026-08-22. Fan out across
  queries and subreddits to go wider; there is no input that raises the per-listing limit.
- **Scores are fuzzed by Reddit** by a few points on every read. Treat them as ±3.
- **No comment pagination.** `topComments` is the first `maxCommentsPerPost` comments in Top
  order (top-level + direct replies). "Load more" continuations are not followed; a 2,000-comment
  thread yields at most 100 here.
- **RSS fallback rows have null engagement fields** (`transport: "rss"`). The log and the status
  message say when the fallback was used and for how many searches.
- **No user/profile data beyond the author handle.** `author` and `author_fullname` are what
  Reddit prints on the post; nothing is looked up about the person.
- **Residential proxy is required for the JSON rung.** Datacenter IPs get stuck on Reddit's
  interstitial (4/20 on the sibling actor); raw HTTP is 403 on every IP tier. The default is
  already correct.
- **robots.txt.** Reddit's `robots.txt` (fetched 2026-08-22) reads, verbatim: `User-agent: *` /
  `Disallow: /`, and points to Reddit's *Public Content Policy*. This Actor reads only what a
  logged-out visitor sees and nothing behind a login; whether and how you may use Reddit content
  commercially is governed by that policy and Reddit's User Agreement, and is your responsibility.

***

### How it works, and what it cost to make reliable

Reddit refuses plain HTTP to `/search.json` with its own "snooserv" challenge page from every IP
tier — a client-execution check, not IP reputation. So the Actor launches one real Chromium per run
with a desktop User-Agent, lands on `reddit.com/search/?q=…`, lets the `js_challenge` redirect
settle (~11 s), and then calls Reddit's own `/search.json` (and `/r/<sub>/search.json?restrict_sr=1`,
and `/comments/<id>/.json`) with an in-page `fetch()` — same origin, so the minted challenge cookies
ride along. One browser, then many cheap JSON calls. If a fetch keeps failing mid-session the tab is
navigated straight to the JSON URL as a second rung; if the session is dead it is re-minted once on a
fresh residential IP; if no session can be opened at all, the Atom feed rung takes over.

**Transport ladder, measured 2026-08-22 through Apify (search endpoints only):**

| Rung | `/search.json` (raw HTTP) | `old.reddit.com/search` HTML | `/search.rss` (Atom) | Chromium + in-page `/search.json` |
|---|---|---|---|---|
| Direct, home IP | 0/3 (403) | 0/6 (challenge) | — | — |
| Apify datacenter | 0/5 (403) | 0/7 ("Blocked") | **3/3** (22 entries, no score) | not viable (sibling actor: 4/20 clear the interstitial) |
| Apify **RESIDENTIAL** | 0/5 (403) | 2/3 with rows | **3/3** (22 entries, no score) | **94/94 search pages, 30/30 comment threads** across 8 platform runs (below) |

**Sustained load, all on the Apify platform through RESIDENTIAL, 2026-08-22, build 0.1.2:**

| Run | Input | Result |
|---|---|---|
| `PGCleYzCpe5YwEybM` | the shipped default | 100 rows, 1 page, 21 s |
| `IjYGVDXcGD8DZJwV2` | `{}` (bare input → documented sample) | 100 rows, 1 page, 23 s |
| `sWmNPGsIxfo00IDAe` | `{"helloWorld":123}` | 100 rows, 1 page, 23 s |
| `4LOznUvChdyGuVfN6` | 30 posts + top comments | 30 rows, 30/30 threads, 38 s |
| `5kWkPLhqfTepbALFg` | charge cap $0.011 | 5 rows shipped = 5 billed, 40 s |
| `cjZdZ6SEFV5bFlJKN` | 5 queries × 5 subreddits, unbounded, **120 s timeout** | 2,780 rows, **38 pages**, 80 s, clean budget stop |
| `zANrxAQKoH1gnMvhl` | same, again | 3,577 rows, **48 pages**, 76 s, clean budget stop |
| `u9D7cKnobXM23ZbXk` | 1 query unbounded, 120 s timeout | 237 rows (Reddit's ceiling), 3 pages, 40 s |

**94 / 94 JSON search pages answered (100%), 0 RSS fallbacks, 8 / 8 runs SUCCEEDED.** One
residential exit refused the proxy tunnel on `u9D7cKnobXM23ZbXk` (`ERR_TUNNEL_CONNECTION_FAILED`);
the session was re-minted on a fresh IP and the run completed — that retry is the reason the
number is 8/8 and not 7/8. Peak memory 337–665 MB of the 2,048 MB the Actor declares.

The one failure seen in testing was ours, not Reddit's: build 0.1.1 shipped an unpinned Playwright
that did not match the container's bundled Chromium, so no browser could launch — and the **RSS
fallback rung carried all three proof runs to SUCCEEDED with 22 rows each** (`G3VoWaRajZUPl8qsL`,
`oSOPNIQkpcelyhMrU`, `5mbuHbRdjRPhKj06N`, each labelled *"1 search via the RSS fallback rung"* in
its status message). That is exactly what the rung is for; 0.1.2 pinned the version.

***

### Duplicates — measured

| Walk | Posts returned | Unique | Duplicates |
|---|---|---|---|
| One search, pages 1–3 contiguous to exhaustion ("web scraping", site-wide, platform run `u9D7cKnobXM23ZbXk`) | 237 | **237** | **0.0%** |
| 5 queries × 5 subreddits, 38 pages (platform run `cjZdZ6SEFV5bFlJKN`) | 2,817 | 2,780 | **1.3% dropped before billing** |
| 5 queries × 5 subreddits, 48 pages (platform run `zANrxAQKoH1gnMvhl`) | 3,630 | 3,577 | **1.5% dropped before billing** |
| 2 queries × 2 subreddits, `sort: new`, 30-day window (local) | 48 | 44 | **8.3% dropped before billing** |

Within a single search listing Reddit repeats nothing. Duplicates come from fan-out — the same post
matching "web scraping" and "python" in r/webscraping — and the Actor keeps a **run-wide set keyed
on the `t3_` fullname**, de-duplicates **before** anything is charged, and then **asserts
uniqueness across everything it pushed** before the run is allowed to succeed. Every run logs the
exact duplicate count it dropped. Across runs, move `dateFrom` forward — dedupe is per run.

***

### When a run fails

This Actor fails loudly only where the output would otherwise be silently wrong:

- **Duplicate leak** → the uniqueness assertion fails the run.

Everything else ends **SUCCEEDED with a status message** that says what happened:

- **0 rows** → the message names the cause — no results from Reddit, your filters removed
  everything, or no transport rung answered (usually transient; re-run).
- **Run time budget** → the Actor stops fetching in time to push what it has: *"Stopped early to
  stay inside this run's time limit — N posts delivered and charged."*
- **Charge cap** → *"Stopped at this run's pay-per-event charge cap with N posts delivered. Every
  row delivered was billed."*

***

### Who buys this

- **Social-listening and brand-monitoring teams** — a keyword monitor with real engagement numbers
  and a date window, scheduled weekly, without a Reddit API contract.
- **Product and UX researchers** — `strictSearch` + `subreddits` to pull every on-topic thread
  about a competitor or a feature, with the top comments nested for reading.
- **SEO and content teams** — high-`num_comments`, high-`upvote_ratio` questions in a niche are
  the content briefs; `link_flair_text` separates "Question" from "Showcase".
- **Sales and founder GTM** — "looking for a tool that…" threads in r/SaaS, r/startups and
  r/smallbusiness, newest first, with the author handle to reply to.
- **AI / RAG pipelines** — clean JSON with stable ids, ISO timestamps and the query provenance on
  every row, de-duplicated, no HTML to clean.

***

### Our other Reddit Actor

| Actor | What it is | Why you would use it instead |
|---|---|---|
| [Reddit Subreddit Scraper](https://apify.com/scrapersdelight/reddit-subreddit-scraper) | A subreddit's hot / new / top / rising listing plus full comment trees | When you want *everything* in a subreddit rather than a keyword's matches |

***

### FAQ

**Does this need a Reddit account, a login or an API key?**
No. It reads the same public JSON Reddit's logged-out front end reads, from a real browser session
on a residential IP. No cookie of yours is ever used.

**Are the scores real?**
Yes. 100/100 rows on the shipped default carried `score`, `upvote_ratio` and `num_comments`; 98 of
100 scores were above zero. Only `transport: "rss"` fallback rows have null engagement fields, and
the run tells you when that rung was used.

**How many posts can one query return?**
About 250 — Reddit's own ceiling, measured at 237. Add queries and subreddits to go wider.

**Can I search by exact date?**
Use `dateFrom` / `dateTo` (applied before billing) with `sort: "new"` for an early stop, and `time`
to pre-narrow what Reddit returns.

**Do comments cost extra?**
Only when you turn `includeTopComments` on, and only for posts that actually returned at least
one comment — `comments-enriched` at $0.003 per such post.

**Two runs — will I get duplicates?**
Within a run, never: dedupe is on the `t3_` fullname and asserted before the run can succeed.
Across runs, move `dateFrom` forward.

**Do I need a residential proxy?**
Yes, for the JSON rung; it is the default. Datacenter IPs do not clear Reddit's interstitial.

**Will a run ever succeed with zero rows?**
It will end SUCCEEDED with a status message naming the cause (no results / filtered out / no
transport answered), so a scheduled monitor on a quiet keyword does not get flagged as broken.

***

### Legal & fair use

This Actor reads public Reddit search results and public comment threads as a logged-out visitor.
It does not log in, does not use any account, cookie or API key, and collects nothing behind
authentication. Reddit's `robots.txt` carries a blanket `User-agent: * / Disallow: /` and refers to
Reddit's Public Content Policy; Reddit's User Agreement and that policy govern how its content may
be used. Posts and comments contain user-generated content and author handles, which can be
personal data. **You are responsible for complying with Reddit's terms and with how you use the
data**, including GDPR/CCPA and the rules on unsolicited contact.

Reddit® is a trademark of its owner. This Actor is not affiliated with, endorsed by, or connected
to Reddit, Inc.

***

### Feedback

Found a missing field or want a new filter? Open an issue on the **Issues** tab, and if the Actor
earns it, a review on the **Reviews** tab helps other buyers find it.

# Actor input Schema

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

One keyword phrase per line, exactly as you would type it into Reddit's search box. Reddit's own search operators work: quotes for an exact phrase ("price increase"), OR between alternatives (refund OR chargeback), a minus to exclude (-meme), and field prefixes such as title:, author:, subreddit:, flair:, self:yes. Each query is searched separately; results are de-duplicated across all of them, so a post matching two queries is returned and billed once. Leave empty to run the documented sample ("web scraping").

## `subreddits` (type: `array`):

Leave empty to search all of Reddit. Add subreddit names (programming, r/webscraping, or a full https://www.reddit.com/r/… URL) to run every query INSIDE each listed subreddit instead — 2 queries × 3 subreddits = 6 searches. Reddit caps one search listing at roughly 250 results, so fanning out across subreddits is how you get past that ceiling for a broad keyword.

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

Reddit's own search orderings. Relevance is Reddit's ranking for the keyword; Top is by score inside the time range; New is newest first (combine with a date window to monitor a keyword); Comments is the most-discussed first.

## `time` (type: `string`):

Reddit's server-side time filter — the posts Reddit itself returns for the search. For a precise window on top of this, use the date fields below.

## `dateFrom` (type: `string`):

YYYY-MM-DD (UTC), e.g. 2026-01-01, or a full ISO-8601 timestamp. Only posts created on or after this moment are kept. Reddit's search has no exact-date operator of its own, so this is applied to the created\_utc of every result; pair it with sort = New and the Actor stops paging as soon as a whole page is older than this date.

## `dateTo` (type: `string`):

YYYY-MM-DD (UTC) or ISO-8601. A bare date means the end of that day (23:59:59 UTC). Only posts created on or before this moment are kept.

## `strictSearch` (type: `boolean`):

Reddit's search is fuzzy — it returns posts that mention none of your words. Turn this on to keep only posts whose title + selftext contain EVERY word of the query (case-insensitive; quotes and common stop-words are ignored). Expect fewer rows — often half — but all of them on-topic.

## `includeNsfw` (type: `boolean`):

Uncheck to ask Reddit to exclude over-18 results and to drop any that slip through.

## `includeTopComments` (type: `boolean`):

Reads each post's comment thread (sorted by Top) and nests up to the number below into topComments\[] — id, author, body, score, created time, depth, permalink. Adds one Reddit request per post, so a 100-post run takes roughly twice as long.

## `maxCommentsPerPost` (type: `integer`):

Cap on comments nested into each post (top-level comments and their direct replies, in Top order). Only used when the checkbox above is on. 1–100.

## `maxItems` (type: `integer`):

Stop after this many unique posts across all searches. Your hard cost cap: 100 = $0.20, 1,000 = $2.00. Duplicates and filtered-out posts never count. 0 = no limit (bounded only by Reddit's ~250-per-search ceiling, your query × subreddit fan-out, and the run timeout).

## `maxItemsPerQuery` (type: `integer`):

Cap for each query × subreddit search, so one broad query cannot eat the whole budget before the others run. Reddit itself stops paging a single search at roughly 250 results. 0 = no per-search limit.

## `allowRssFallback` (type: `boolean`):

If the browser session cannot be opened at all (proxy outage, Reddit interstitial not clearing), fall back to Reddit's Atom search feed for that search. Those rows are marked transport: "rss" and carry title, author, subreddit, selftext, permalink and created time — but NO score, upvote ratio or comment count (null, never a fake 0). Uncheck if you would rather get zero rows than rows without scores.

## `proxyConfiguration` (type: `object`):

RESIDENTIAL is required for the browser rung. Measured on the sibling Reddit actor 2026-07-30: residential cleared Reddit's browser challenge 20/20, datacenter got stuck on the ?js\_challenge interstitial (4/20); raw HTTP to /search.json is 403 on every IP tier (2026-08-22). The default is already correct — do not switch it to datacenter.

## `requestDelayMs` (type: `integer`):

Pacing between in-page JSON calls. Lower is faster but more likely to be throttled by Reddit.

## Actor input object example

```json
{
  "queries": [
    "web scraping"
  ],
  "subreddits": [],
  "sort": "relevance",
  "time": "all",
  "dateFrom": "",
  "dateTo": "",
  "strictSearch": false,
  "includeNsfw": true,
  "includeTopComments": false,
  "maxCommentsPerPost": 10,
  "maxItems": 100,
  "maxItemsPerQuery": 100,
  "allowRssFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "requestDelayMs": 700
}
```

# Actor output Schema

## `items` (type: `string`):

One row per unique post returned by Reddit search: id, subreddit, title, author, selftext, url, permalink, score, ups, upvote\_ratio, num\_comments, flair, created time, NSFW/locked/stickied flags, the query and subreddit it was found under, and (optionally) the post's top comments nested in topComments\[]. De-duplicated run-wide on Reddit's t3\_ fullname, so a post matching two queries is billed once.

# 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 = {
    "queries": [
        "web scraping"
    ],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/reddit-search-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 = {
    "queries": ["web scraping"],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/reddit-search-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 '{
  "queries": [
    "web scraping"
  ],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call scrapersdelight/reddit-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/reddit-search-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/EIdbadebSqtaSao6K/builds/6wWFTnLEaVdmgIiqb/openapi.json
