# Reddit Image & Video Scraper (`apt_marble/reddit-image-video-scraper`) Actor

Pull every image, GIF, gallery and video out of Reddit as direct, full-resolution URLs. Point it at subreddits, user profiles or post links and get one row per media file — width, height, duration, audio flag, file type — with the parent post title, author, score and link. No account needed.

- **URL**: https://apify.com/apt\_marble/reddit-image-video-scraper.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Social media, Videos, Automation
- **Stats:** 1 total users, 1 monthly users, 94.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.40 / 1,000 media row saveds

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/platform/actors/running/actors-in-store#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 Image & Video Scraper

Turn any Reddit feed into a clean list of media files. Point this actor at subreddits, user profiles or individual post links and it returns one row per picture, GIF and video — the full-resolution image URL, the Reddit-hosted MP4 with its DASH and HLS streams, plus width, height, duration, audio flag and file extension — attached to the post it came from (title, author, subreddit, score, permalink, date). A 20-image gallery becomes 20 rows, ready to hand straight to a downloader, an image pipeline or a spreadsheet. No Reddit account and no login required.

### What you can do with it

- **Build a media dataset** — sweep r/pics or any niche community and collect thousands of direct image URLs with dimensions and scores attached.
- **Archive a creator's work** — pass a list of usernames and pull every picture and video they have submitted, newest first or highest scoring first.
- **Feed a computer-vision or training pipeline** — filter to `image` and `gallery` only, set a minimum score, and get a quality-filtered set of full-resolution URLs.
- **Track what visual content performs** — every media row carries the parent post's score, upvote ratio and comment count, so you can rank formats, subreddits and creators by engagement.
- **Grab the media from specific threads** — paste a handful of post permalinks and get every asset those posts contain, including all gallery pages.
- **Monitor a community's visual output on a schedule** — run it daily with `sort: new` to capture what was posted since yesterday.

### What you get

One row per media file (abridged — a real record has more fields):

```json
{
  "mediaUrl": "https://preview.redd.it/4u6h07bw2zfh1.jpg?width=4649&format=pjpg&auto=webp&s=3cd11155d6a6aeb2b3b03ba14561ffa1a0e3261a",
  "mediaType": "gallery",
  "width": 4649,
  "height": 6974,
  "fileExtension": "jpg",
  "mimeType": "image/jpg",
  "caption": null,
  "galleryIndex": 0,
  "mediaIndex": 0,
  "mediaCount": 20,
  "durationSeconds": null,
  "hasAudio": null,
  "dashUrl": null,
  "hlsUrl": null,
  "isPreviewImage": false,
  "postId": "1v8yjkt",
  "postTitle": "[OC] Some portraits I took recently in Bristol, UK",
  "postUrl": "https://www.reddit.com/r/pics/comments/1v8yjkt/oc_some_portraits_i_took_recently_in_bristol_uk/",
  "subreddit": "pics",
  "author": "slow_gentle_morning",
  "score": 11845,
  "upvoteRatio": 0.96,
  "numComments": 481,
  "createdAt": "2026-07-28T13:28:44.000Z",
  "over18": false,
  "source": "r/pics",
  "sourceType": "subreddit"
}
```

A Reddit-hosted video row looks like this instead:

```json
{
  "mediaUrl": "https://v.redd.it/6lfbfgzs2zfh1/CMAF_720.mp4?source=fallback",
  "mediaType": "video",
  "width": 720,
  "height": 1280,
  "durationSeconds": 16,
  "hasAudio": true,
  "bitrateKbps": 2400,
  "fileExtension": "mp4",
  "dashUrl": "https://v.redd.it/6lfbfgzs2zfh1/DASHPlaylist.mpd?v=1&f=sd",
  "hlsUrl": "https://v.redd.it/6lfbfgzs2zfh1/HLSPlaylist.m3u8?v=1&f=sd",
  "postTitle": "Cracking open a beer with your bike right after finishing the Tour de France.",
  "subreddit": "nextfuckinglevel",
  "author": "dannybluey",
  "score": 32334
}
```

Switch **One row per media file** off and you get one row per post instead, with all of its assets inside a `media` array and a `mediaCount` next to it. That row is shaped as a post rather than as a media file, so the post's own fields are named `id`, `title` and `url` (not `postId`, `postTitle` and `postUrl`), and every media field listed below appears inside the `media` entries instead of at the top level.

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `subreddits` | list of text | prefilled with `pics`, `aww` | Communities to pull media from. Names (`pics`), handles (`r/pics`) and full URLs all work. |
| `usernames` | list of text | empty | Profiles whose submitted posts are scanned for media. Accepts `spez`, `u/spez` or a profile URL. |
| `postUrls` | list of text | empty | Individual posts to pull media from. Accepts permalinks, `redd.it` short links or bare post IDs. The per-source post limit does not apply here — every post you list is read. |
| `mediaTypes` | multi-select | `image`, `gallery`, `video`, `animated` | Which kinds of media to keep. Add `embed` to also capture third-party players such as YouTube. |
| `sort` | select | `hot` | Which feed to pull: `hot`, `new`, `top`, `rising` or `controversial`. Profiles have no `rising` feed, so it falls back to `new` for them. |
| `time` | select | `all` | Time window for `top` and `controversial`: hour, day, week, month, year or all. Other sorts ignore it. |
| `maxPostsPerSource` | integer | `100` | How many posts *containing matching media* to save per subreddit or profile. The run works further back through the feed to find them, but stops digging once it has gone about five times deeper than your target — see Limits. Maximum 1000. |
| `minScore` | integer | empty | Keep only posts with at least this many upvotes. |
| `includeNsfw` | boolean | `true` | Keep posts marked 18+. Turn off to drop them. |
| `includePostsWithoutMedia` | boolean | `false` | Also save text and link posts that carry no matching media, with empty media fields. |
| `oneRecordPerAsset` | boolean | `true` | On: one row per picture/GIF/video. Off: one row per post, with a `media` array. |
| `maxConcurrency` | integer | `4` | How many of your sources are worked through at the same time. Higher is faster on long lists. 1 to 10. |
| **Browse from** | select | `us` | Country the run should use the view of. Reddit shows the same content everywhere, so most runs can leave this alone. |

At least one of `subreddits`, `usernames` or `postUrls` must be filled in.

### Output fields

| Field | Description |
| --- | --- |
| `mediaUrl` | Direct URL of the picture, GIF or video file. |
| `mediaType` | `image`, `gallery`, `video`, `animated` or `embed`. |
| `width`, `height` | Pixel dimensions of the asset, when Reddit reports them. |
| `durationSeconds` | Length of a video in seconds. |
| `hasAudio` | Whether the video has an audio track (see the note under Limits). |
| `dashUrl`, `hlsUrl` | Streaming manifests for Reddit-hosted video. |
| `bitrateKbps` | Video bitrate reported by Reddit. |
| `fileExtension` | `jpg`, `png`, `gif`, `mp4` … derived from the URL; `null` when the URL has none. |
| `mimeType` | Content type reported for gallery pictures. |
| `caption` | Caption the poster attached to a gallery picture. |
| `galleryIndex` | Position of the picture inside its gallery, starting at 0. `null` for non-gallery assets. |
| `galleryMediaId` | Reddit's own id for that gallery picture. |
| `mediaIndex`, `mediaCount` | Position of this asset among the post's saved assets, and how many there are. |
| `isPreviewImage` | `true` when the picture is the preview Reddit generated for a link post rather than an uploaded file. |
| `embedProvider`, `embedTitle`, `embedThumbnail` | Details of a third-party embed (YouTube, Streamable, …). |
| `postId`, `postFullId` | Reddit's id for the parent post. |
| `postTitle` | Title of the parent post. |
| `postUrl`, `permalink` | Absolute and relative links to the post. |
| `subreddit` | Community the post is in. |
| `author` | Username of the poster. |
| `score`, `upvoteRatio`, `numComments` | Engagement on the parent post. |
| `flair` | Link flair on the parent post. |
| `createdAt` | When the post was published (ISO 8601, UTC). |
| `over18`, `spoiler`, `isOriginalContent` | Post markers. |
| `domain`, `linkUrl` | Outbound domain and link for link posts. |
| `source`, `sourceType` | Which of your inputs produced this row, and whether it was a subreddit, a profile or a post URL. |
| `scrapedAt` | When the run collected the data. |

### Pricing

This actor is **pay per event**. There is exactly one billable event: `apify-default-dataset-item`, charged at **$0.0004 per row** — **$0.40 per 1,000 rows** — once for every row saved to the dataset. Nothing else is billed: everything the run does to find your media is free.

Because the default output is one row per media file, a post with a 20-picture gallery produces 20 billable rows ($0.008). If you would rather pay once per post, switch **One row per media file** off and every post arrives as a single row with its assets in a `media` array.

Posts that fail your filters are skipped before anything is saved, so you are never charged for content you filtered out. If a run hits its maximum-charge limit it stops cleanly and keeps everything already collected.

### FAQ

**Do I need a Reddit account?**
No. You do not need a Reddit account, a login, or any credentials — just enter what you want and run it.

**How fast is it?**
Results come back in large batches, so a 100-post subreddit is usually finished in seconds and even long watchlists complete in minutes. Transient failures are retried automatically and heavy jobs pace themselves, so big lists keep moving without any configuration from you.

**Can I run it on a schedule?**
Yes. A daily or hourly schedule with `sort: new` is the usual way to keep a media archive current. The actor holds no memory between runs, so each run returns whatever the feed currently shows — de-duplicate on `mediaUrl` or `postId` on your side if you are appending to the same store. The same post can also arrive twice inside one run if you list a subreddit and a profile (or a post link) that both contain it.

**How do galleries work?**
By default each picture in a gallery becomes its own row, numbered with `galleryIndex` starting at 0, and every row repeats the parent post's details so the rows stand alone. Turn off **One row per media file** to get the gallery back as a single row with a `media` array.

**Can I get only videos, or only images?**
Yes — set **Media types to keep** to just the buckets you want. Excluded types are dropped before anything is saved, so you are not charged for them.

**Does it include NSFW content?**
Yes by default, and it covers 18+ communities without any extra configuration. Turn off **Include 18+ posts** to drop them.

# Actor input Schema

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

Communities to pull media from. Paste names (pics), r/ handles (r/pics) or full subreddit URLs — all three work.

## `usernames` (type: `array`):

Profiles whose submitted posts should be scanned for media. Accepts spez, u/spez or a full profile URL.

## `postUrls` (type: `array`):

Individual Reddit posts to pull media from. Accepts full permalinks, redd.it short links or bare post IDs. These are fetched directly, so the per-source post limit does not apply to them.

## `mediaTypes` (type: `array`):

Which kinds of media to save. Image is a standalone picture (including the preview image Reddit generates for a link post), Gallery is one picture out of a multi-image post, Video is Reddit-hosted video, Animated is a GIF, and Embed is third-party media such as a YouTube player — embeds are links to another site, not downloadable files, so they are off by default.

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

Which listing to read for subreddits and profiles. Hot is what Reddit is pushing right now, New the most recent posts, Top the highest scoring in the chosen time range, Rising the ones gaining traction, Controversial the most divisive. Profiles do not support Rising, so it falls back to New for them.

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

Time window for the Top and Controversial sorts. Ignored by Hot, New and Rising.

## `maxPostsPerSource` (type: `integer`):

How many posts that actually contain matching media to save from each subreddit or profile. The run reads further down the listing to find them, and Reddit itself stops any single listing at about 1,000 posts, so that is the hard ceiling.

## `minScore` (type: `integer`):

Keep only posts with at least this many upvotes (score = upvotes minus downvotes). Leave empty to keep every post.

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

Keep posts marked 18+. Turn this off to drop them.

## `includePostsWithoutMedia` (type: `boolean`):

Also save text and link posts that carry no matching media, with empty media fields. Off by default, so every row you pay for has a media URL in it.

## `oneRecordPerAsset` (type: `boolean`):

On (recommended): every picture, GIF and video gets its own row, so a 20-image gallery produces 20 rows ready to feed into a downloader. Off: one row per post, with all of its media in a single media array.

## `maxConcurrency` (type: `integer`):

How many subreddits, profiles and post batches to read at the same time. Higher is faster on long lists; 4 is a good balance.

## `country` (type: `string`):

Which country's view of Reddit to use. Reddit content is the same almost everywhere, so the default suits nearly every run.

## Actor input object example

```json
{
  "subreddits": [
    "pics",
    "r/aww",
    "https://www.reddit.com/r/EarthPorn/"
  ],
  "usernames": [
    "u/spez",
    "https://www.reddit.com/user/GallowBoob/"
  ],
  "postUrls": [
    "https://www.reddit.com/r/pics/comments/1v8yjkt/oc_some_portraits_i_took_recently_in_bristol_uk/",
    "https://redd.it/1v8yiye"
  ],
  "mediaTypes": [
    "image",
    "gallery",
    "video",
    "animated"
  ],
  "sort": "hot",
  "time": "all",
  "maxPostsPerSource": 20,
  "includeNsfw": true,
  "includePostsWithoutMedia": false,
  "oneRecordPerAsset": true,
  "maxConcurrency": 4,
  "country": "us"
}
```

# Actor output Schema

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

No description

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "subreddits": [
        "pics",
        "aww"
    ],
    "mediaTypes": [
        "image",
        "gallery",
        "video",
        "animated"
    ],
    "sort": "hot",
    "time": "all",
    "maxPostsPerSource": 20,
    "maxConcurrency": 4,
    "country": "us"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/reddit-image-video-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 = {
    "subreddits": [
        "pics",
        "aww",
    ],
    "mediaTypes": [
        "image",
        "gallery",
        "video",
        "animated",
    ],
    "sort": "hot",
    "time": "all",
    "maxPostsPerSource": 20,
    "maxConcurrency": 4,
    "country": "us",
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/reddit-image-video-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 '{
  "subreddits": [
    "pics",
    "aww"
  ],
  "mediaTypes": [
    "image",
    "gallery",
    "video",
    "animated"
  ],
  "sort": "hot",
  "time": "all",
  "maxPostsPerSource": 20,
  "maxConcurrency": 4,
  "country": "us"
}' |
apify call apt_marble/reddit-image-video-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/reddit-image-video-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/dyYPTZOPJuOpnXFWx/builds/Xawy1H9XUgFVNN67w/openapi.json
