# Bulk Image Downloader - Save Every Image From Any Page (`thirdwatch/bulk-image-downloader`) Actor

Save every image from any webpage at scale. Paste page URLs or direct image links and get back the full files plus dimensions, format, and size. Automatically picks the highest-resolution version when a page offers multiple, removes duplicates, and can strip EXIF metadata for privacy.

- **URL**: https://apify.com/thirdwatch/bulk-image-downloader.md
- **Developed by:** [Thirdwatch](https://apify.com/thirdwatch) (community)
- **Categories:** SEO tools, Automation
- **Features:** Limited permissions
- **Stats:** 106 total users, 6 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bulk Image Downloader

> **Thirdwatch portfolio:** 8,800+ users across 140+ public Actors, 2M+ records delivered, >95% run success. [Explore all Thirdwatch Actors](https://apify.com/thirdwatch).

> Save every image from any webpage at scale — full files, dimensions, format, file size, and SHA-256 hash. Paste page URLs or direct image links.

### What you get

Give it a list of URLs and get back every image plus rich metadata: width, height, format, content type, byte size, and a SHA-256 hash for dedup. The actor handles both modes — point it at a webpage and it parses the HTML for `<img>`, `<picture>`, `srcset`, and Open Graph tags; or pass a direct image URL and it downloads it straight. Auto-mode picks the right path per URL by inspecting Content-Type. Highest-resolution variants from `srcset` are picked automatically. Files are saved to the run's key-value store and each dataset row carries a ready-to-use download URL.

### Bulk download images from any URL

A single tool for collecting images at scale across hundreds or thousands of pages. It processes up to 10 URLs at once, so large jobs finish quickly. Supports webpages, direct image URLs, responsive `srcset` images, and Open Graph / Twitter card images.

### Image dataset builder for ML

Built for teams assembling training corpora. Use `dedupByHash` to skip identical images across pages, `minWidth` and `minSizeBytes` to filter out tracking pixels and tiny thumbnails, and `maxImagesPerUrl` to keep first runs cheap. Every image lands in the run's key-value store with a stable `kv_key` and a direct download URL on each dataset row, so you can pipe results straight into a downstream training pipeline.

### Output fields

| Field | Description |
|-------|-------------|
| `source_url` | The input URL the image was discovered on |
| `image_url` | Absolute URL of the image file |
| `kv_store_key` | Key under which the image is stored in the run's key-value store |
| `kv_url` | Fully formed Apify API URL to download the image |
| `filename` | Suggested filename (sequence number + short hash + extension) |
| `content_type` | HTTP `Content-Type` (e.g., `image/jpeg`, `image/png`, `image/webp`) |
| `size_bytes` | File size in bytes |
| `sha256` | SHA-256 hash of the image bytes (used for dedup) |
| `width` | Image width in pixels |
| `height` | Image height in pixels |
| `format` | Decoded image format (JPEG, PNG, WEBP, GIF, etc.) |
| `from_srcset` | `true` if extracted from a responsive `srcset` |
| `downloaded_at` | ISO-8601 timestamp when the image was fetched |

### Example output

```json
{
  "source_url": "https://en.wikipedia.org/wiki/Cat",
  "image_url": "https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg",
  "kv_store_key": "000007_3a1f9b2c.jpg",
  "kv_url": "https://api.apify.com/v2/key-value-stores/abc123/records/000007_3a1f9b2c.jpg",
  "filename": "000007_3a1f9b2c.jpg",
  "content_type": "image/jpeg",
  "size_bytes": 432109,
  "sha256": "3a1f9b2c4d5e6f7081929394a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e",
  "width": 1280,
  "height": 853,
  "format": "JPEG",
  "from_srcset": true,
  "downloaded_at": "2026-05-04T10:11:12.345678+00:00"
}
```

### Input parameters

| Parameter | Required | Description |
|-----------|----------|-------------|
| `urls` | Yes | List of webpage URLs to scan for images, or direct image URLs to download. |
| `mode` | No | `auto` (default), `page`, or `direct`. Auto inspects Content-Type; page parses HTML; direct treats every URL as an image. |
| `includeSrcset` | No | Pull images from responsive `srcset` and `<picture>` elements. Default `true`. |
| `minWidth` | No | Skip images smaller than this width when dimensions are known. Default `0` (disabled). |
| `minSizeBytes` | No | Skip files smaller than this many bytes (filters tracking pixels). Default `0` (disabled). |
| `maxImagesPerUrl` | No | Cap on images downloaded per input URL. Default `100`, max `1000`. |
| `dedupByHash` | No | Skip duplicates whose SHA-256 hash matches one already downloaded. Default `true`. |
| `stripExif` | No | Re-encode JPEGs without EXIF metadata for privacy. Default `false`. |
| `proxyConfiguration` | No | Optional proxy. Most public sites do not require one. |

### Use cases

- **ML engineers**: build image datasets for training and fine-tuning by harvesting category, search, or gallery pages.
- **Site auditors**: measure image bloat across a domain — count files, total bytes, and average size per page.
- **E-commerce teams**: scrape product image catalogs for migration, redesign, or competitive research.
- **Archivists**: snapshot every image from a portfolio, gallery, or news site for offline backup.
- **Privacy-conscious publishers**: strip EXIF metadata from user-submitted images before re-publishing.

### Limitations

- `stripExif` only re-encodes JPEGs (the most common EXIF carrier). Re-encoding is light-touch but technically lossy.
- Inline `data:` URIs are skipped; only fetchable URLs are downloaded.
- Sites that gate images behind a login or short-lived signed URLs cannot be scraped.
- Maximum image count per page is capped at 1,000 to protect runtime; use `maxImagesPerUrl` to keep first runs cheap.
- The actor uses HTTP fetch only — pages that render images via JavaScript after load (some SPAs) may not expose all images.

### Compared to alternatives

- **vs. generic Apify image downloaders**: this actor returns full per-image metadata (width, height, format, hash, byte size) on the dataset row, plus a ready-to-use `kv_url` so you don't have to construct download URLs yourself. Built-in dedup, srcset handling, and EXIF stripping save a separate post-processing pass.
- **vs. running `wget` or a custom script**: hosted, retried, deduplicated, and observable in the Apify Console with no infrastructure to maintain.

### FAQ

**Where are the images stored?**
Every image is saved as a record in the run's default **key-value store**. Each dataset row carries a `kv_url` you can fetch directly, or you can browse the run's "Storage" tab in the Apify Console.

**Can I download images directly without scraping a webpage?**
Yes — set `mode: "direct"`, or leave `mode: "auto"` and pass image URLs. The actor will skip HTML parsing and download each URL as an image.

**Will it follow links to other pages?**
No. The actor only downloads images from the URLs you provide. To crawl, run a sitemap or link-extraction scraper first and feed the URLs in.

**Does it handle responsive images correctly?**
Yes. With `includeSrcset: true` (default), the highest-resolution variant from each `srcset` or `<picture>` element is selected.

**Does it dedupe across pages?**
Yes. With `dedupByHash: true` (default), an image already downloaded in this run is skipped on later pages.

**How do I keep my first run cheap?**
Lower `maxImagesPerUrl` (e.g., 10 or 20) and run on a handful of URLs first to validate output before scaling.

### Related Actors

- [Bulk SEO Data Extractor](https://apify.com/thirdwatch/seo-data-extractor) — audit on-page SEO signals, including alt-text gaps, on the same pages you download images from

Last verified: 2026-05

More scrapers at [thirdwatch.dev](https://thirdwatch.dev).

# Actor input Schema

## `urls` (type: `array`):

List of webpage URLs to scan for images, or direct image URLs to download. Mode is auto-detected by Content-Type by default.

## `mode` (type: `string`):

How to interpret each input URL. 'auto' inspects Content-Type via HEAD; 'page' always parses as HTML; 'direct' always treats URL as an image.

## `includeSrcset` (type: `boolean`):

Extract images from srcset attributes (responsive images and <picture> elements). Picks the highest-resolution variant from each set.

## `minWidth` (type: `integer`):

Skip images smaller than this width when dimensions can be determined. Set to 0 to disable.

## `minSizeBytes` (type: `integer`):

Skip files smaller than this many bytes (useful to filter out tracking pixels and tiny icons). Set to 0 to disable.

## `maxImagesPerUrl` (type: `integer`):

Cap the number of images downloaded per input URL.

## `dedupByHash` (type: `boolean`):

Skip images whose bytes match one already downloaded in this run.

## `stripExif` (type: `boolean`):

Re-encode JPEGs without EXIF metadata for privacy. Slightly slower to process.

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

Optional proxy. Most public sites do not require one.

## Actor input object example

```json
{
  "urls": [
    "https://www.apify.com"
  ],
  "mode": "auto",
  "includeSrcset": true,
  "minWidth": 0,
  "minSizeBytes": 0,
  "maxImagesPerUrl": 100,
  "dedupByHash": true,
  "stripExif": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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 = {
    "urls": [
        "https://www.apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thirdwatch/bulk-image-downloader").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 = { "urls": ["https://www.apify.com"] }

# Run the Actor and wait for it to finish
run = client.actor("thirdwatch/bulk-image-downloader").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 '{
  "urls": [
    "https://www.apify.com"
  ]
}' |
apify call thirdwatch/bulk-image-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thirdwatch/bulk-image-downloader"
        }
    }
}
```

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/uVaN7dCWAJv3Imz4C/builds/1dtVEC50xc60KsMGy/openapi.json
