# Bulk Image Downloader: URLs & Web Pages to Files + ZIP (`ctriolab/bulk-image-downloader`) Actor

Download images in bulk from direct image URLs or from any web page (img, srcset, og:image, lazy-loaded and linked images). Filter by size and format, remove duplicates, get a shareable link per file and one ZIP. Failed images are free.

- **URL**: https://apify.com/ctriolab/bulk-image-downloader.md
- **Developed by:** [Ctrio Lab](https://apify.com/ctriolab) (community)
- **Categories:** Automation, Developer tools, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 image saveds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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: URLs & Web Pages to Files + ZIP

Download images in bulk, two ways:

1. **Direct image URLs**: paste a list of links to image files and get every file saved.
2. **Web page URLs**: give pages, and the Actor finds the images on each page and downloads them: `<img src>`, `srcset` (largest version), lazy-load attributes (`data-src`, `data-lazy-src`, `data-original`), `<picture><source>`, `og:image`, `twitter:image`, inline `background-image`, and links that point straight at image files.

Every saved image gets a **shareable file link**, one dataset row (source URL, page URL, file name, format, width, height, bytes, SHA-256, alt text), and optionally goes into **one ZIP file**. Images that fail, are filtered out, or are duplicates are **not charged**.

### How to use it

1. Paste image URLs, page URLs, or both. Leave both empty to try public-domain samples from Wikimedia Commons.
2. Optional: set filters (minimum width and height, minimum and maximum file size, formats), the number of images per page, and "Also create one ZIP file".
3. Run. Open the **Images** tab for previews, or the **Run summary** for the ZIP link and the list of skipped images with reasons.

### Features

- **Format from the file itself**: JPEG, PNG, GIF, WebP, AVIF, SVG, BMP, ICO, TIFF, HEIF. HTML error pages behind an image URL are detected and skipped.
- **Filters**: minimum width, minimum height, minimum bytes, maximum bytes (large files are stopped while downloading), formats to keep.
- **No duplicates**: each URL is downloaded once per run, and different URLs that return the same file (same SHA-256) are saved once.
- **Only new images**: for scheduled runs that watch the same pages, skip images saved by earlier runs with the same monitor name (by URL and by content).
- **ZIP**: one `images.zip` with all saved images, written to disk as the run goes (no memory spike), up to 3.5 GB.
- **Polite**: honest User-Agent with contact, robots.txt respected for web pages (on by default), retries with back-off on 429 and 5xx.
- **Limits**: max images per page, max images in total, per-request timeout, downloads at a time.

### Output

One dataset item per saved image:

```json
{
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<store>/records/00002-1280px-The_Great_Wave_off_Kanagawa.jpg",
  "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/The_Great_Wave_off_Kanagawa.jpg/1280px-The_Great_Wave_off_Kanagawa.jpg",
  "pageUrl": null,
  "source": "direct",
  "alt": null,
  "fileName": "00002-1280px-The_Great_Wave_off_Kanagawa.jpg",
  "format": "jpg",
  "contentType": "image/jpeg",
  "width": 1280,
  "height": 881,
  "sizeBytes": 433605,
  "sha256": "…",
  "finalUrl": null,
  "downloadedAt": "2026-10-29T08:00:00.000Z"
}
```

`source` tells where the image was found: `direct`, `img`, `img srcset`, `img data-src`, `picture source`, `og:image`, `twitter:image`, `background`, `link`.

The run's key-value store holds the image files, `images.zip` (if requested), and `SUMMARY` (counts, pages read, ZIP link, every skipped image with its reason).

### Pricing

You pay per saved image (one dataset item) plus the small Apify platform usage of your run. Failed downloads, filtered images, duplicates, and images skipped by "Only new images" are free. See the Pricing tab for the exact price per 1,000 images.

### What it does not do

- No crawling: only the pages you list are read, links are not followed.
- No login, cookies, or proxies, and no JavaScript rendering. Images that a page inserts only with JavaScript, or that are set only in CSS files, are not found. For those, give the direct image URLs.
- No site-specific scrapers (Instagram, Pinterest, Google Images and similar).

### Copyright

Images belong to their owners. Download only images you have the right to use, and respect the terms of the sites you download from. The Actor reads only the URLs you provide, identifies itself in the User-Agent, and respects robots.txt for web pages by default.

### FAQ

**Why was an image skipped?** Open the run summary (`SUMMARY`): every skipped URL has a reason, e.g. `HTTP 404`, `not an image`, `filtered: narrower than 200 px`, `larger than the size limit`, `duplicate content`.

**Can I get the original file instead of WebP?** The Actor asks servers for `image/*` without preferring WebP or AVIF, so most servers return the original format of the URL.

**Can I use it from code or with AI agents?** Yes, call it through the Apify API, the Apify client for Python or JavaScript, or the Apify MCP server. The dataset and the files are available through the API right after the run.

# Actor input Schema

## `imageUrls` (type: `array`):

Direct links to image files (JPG, PNG, GIF, WebP, AVIF, SVG, BMP, ICO, TIFF, HEIF), one per line. Duplicates are removed. Leave both URL fields empty to try public-domain samples from Wikimedia Commons.

## `pageUrls` (type: `array`):

Web pages to read. The Actor finds the images on each page (img src, srcset, lazy-load attributes, picture, og:image, twitter:image, inline background images and links to image files) and downloads them. Only the pages you list are read: no crawling, no login, no JavaScript rendering.

## `maxImagesPerPage` (type: `integer`):

Only the first N images found on each page are downloaded (page order, meta images first). 0 = no limit.

## `maxImages` (type: `integer`):

Stop saving after this many images. 0 = no limit.

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

Skip smaller images, e.g. icons and tracking pixels. Skipped images are not charged.

## `minHeight` (type: `integer`):

Skip shorter images. Skipped images are not charged.

## `minBytes` (type: `integer`):

Skip files smaller than this.

## `maxBytes` (type: `integer`):

Larger files are stopped while downloading and skipped (not charged). Default 25 MB.

## `formats` (type: `array`):

Leave empty to keep every image format. The format is read from the file itself, not from the URL.

## `srcsetMode` (type: `string`):

largest: download only the biggest version of each responsive image. all: download every size.

## `includeMetaImages` (type: `boolean`):

Also download the page's social preview images.

## `includeLinkedImages` (type: `boolean`):

Also download links (a href) that point straight at an image file, e.g. full-size images behind gallery thumbnails.

## `includeBackgroundImages` (type: `boolean`):

Also download images set in style="background-image: url(...)" attributes. Images set only in CSS files are not found.

## `dedupeByContent` (type: `boolean`):

Different URLs that return the same file (same SHA-256) are saved once. The same URL is always downloaded only once per run.

## `onlyNewImages` (type: `boolean`):

Skip images that an earlier run with the same monitor name already saved (by URL and by file content). Use it for scheduled runs that watch the same pages.

## `monitorName` (type: `string`):

Used with "Only new images". Runs with the same name share their memory of saved images (kept in your named key-value store "bulk-image-downloader-state").

## `createZip` (type: `boolean`):

Adds images.zip with all saved images to the run's key-value store (link in the run summary).

## `maxZipMb` (type: `integer`):

Images that do not fit are still saved one by one, just not added to the ZIP.

## `respectRobotsTxt` (type: `boolean`):

Web pages that the site's robots.txt disallows are not read. Turn off only for sites you own or have permission to download from. Direct image URLs are not checked.

## `publicLinks` (type: `boolean`):

Makes this run's key-value store readable by anyone who has its link, so fileUrl opens without an API token. Turn off to keep the files private to your account.

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

Parallel downloads.

## `requestTimeoutSecs` (type: `integer`):

Maximum time for one download. Network errors, timeouts, HTTP 429 and 5xx are retried twice.

## Actor input object example

```json
{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/1280px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"
  ],
  "pageUrls": [
    "https://commons.wikimedia.org/wiki/File:Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg"
  ],
  "maxImagesPerPage": 100,
  "maxImages": 0,
  "minWidth": 0,
  "minHeight": 0,
  "minBytes": 0,
  "maxBytes": 26214400,
  "formats": [],
  "srcsetMode": "largest",
  "includeMetaImages": true,
  "includeLinkedImages": true,
  "includeBackgroundImages": true,
  "dedupeByContent": true,
  "onlyNewImages": false,
  "monitorName": "default",
  "createZip": false,
  "maxZipMb": 1024,
  "respectRobotsTxt": true,
  "publicLinks": true,
  "maxConcurrency": 10,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

## `results` (type: `string`):

One item per saved image: file link, source URL, page URL, format, width, height, size and SHA-256.

## `files` (type: `string`):

All image files (and images.zip if requested) in the run's key-value store.

## `summary` (type: `string`):

Counts, ZIP link, pages read, and skipped images with reasons (skipped images are not charged).

# 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 = {
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/1280px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"
    ],
    "pageUrls": [
        "https://commons.wikimedia.org/wiki/File:Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ctriolab/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 = {
    "imageUrls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/1280px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"],
    "pageUrls": ["https://commons.wikimedia.org/wiki/File:Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg"],
}

# Run the Actor and wait for it to finish
run = client.actor("ctriolab/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 '{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/1280px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"
  ],
  "pageUrls": [
    "https://commons.wikimedia.org/wiki/File:Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg"
  ]
}' |
apify call ctriolab/bulk-image-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ctriolab/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/iQ5ug12h0A5Z1dJRX/builds/FqEDZF3Je6cAoVqbU/openapi.json
