# Bulk Image Downloader — Save Images from Pages (`nexgensignal/bulk-image-downloader`) Actor

Download every image from a list of web pages or image links: JPEG, PNG, WebP, GIF, SVG and more. Filter by size and type, skip duplicates, and get each file saved with a download link plus width, height and hash. Part of NexGen Signal — data tools and official-source data products

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

## Pricing

from $33.50 / 1,000 image downloadeds

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 — Save Images from Pages

Give a list of web pages or direct image links and get **every image downloaded, checked and saved**, one row per image: where it was found, the alt text, format, width and height, file size, SHA-256, and a download link to the stored file.

The actor reads each page's `<img>` tags (including `srcset`, lazy-load `data-src` attributes and `<picture>` sources), its `og:image` and `twitter:image` tags, and links that point straight at image files. It picks the largest `srcset` candidate, skips `data:` URIs, de-duplicates by file hash across the whole run, and filters by type, minimum size and minimum dimensions.

The unit of output is one image delivered. Images that are skipped (wrong type, too small, duplicate, blocked by robots.txt, unreachable) are not billed.

### Who buys this and for what job

- **E-commerce teams** pulling product photos from supplier or catalogue pages.
- **Designers and researchers** collecting reference images from a list of articles.
- **ML teams** building image datasets with hashes for de-duplication.
- **Archivists and site owners** backing up the images on their own pages.

Buyers usually search for this as **"bulk image downloader"**, **"download all images from URL"** or **"image scraper"**.

### 📊 Sample Output

![Table of downloaded images: image URL, format, width, height, bytes and alt text.](https://api.apify.com/v2/key-value-stores/IXCaMKjxSmUTLHhmq/records/bulk-image-downloader.png)

| image_url | format | width | height | bytes |
|---|---|---|---|---|
| https://upload.wikimedia.org/wikipedia/commons/6/61/Smeaton%27s_Lighthouse00.jpg?utm_source=en.wikipedia.org\&utm_campaign=parser\&utm_content=thumbnail_unscaled | jpeg | 321 | 503 | 39304 |
| https://upload.wikimedia.org/wikipedia/commons/a/ab/Fresnel_lighthouse_lens_diagram.png?utm_source=en.wikipedia.org\&utm_campaign=parser\&utm_content=thumbnail_unscaled | png | 492 | 578 | 90202 |
| https://thumb.wikimedia.org/wikipedia/commons/thumb/f/f5/NJ_LBI_Lighthouse_04.JPG/500px-NJ_LBI_Lighthouse_04.JPG?utm_source=en.wikipedia.org\&utm_campaign=parser\&utm_content=thumbnail | jpeg | 500 | 375 | 34802 |
| https://thumb.wikimedia.org/wikipedia/commons/thumb/2/27/Torre_de_H%C3%A9rcules_2023.jpg/500px-Torre_de_H%C3%A9rcules_2023.jpg?utm_source=en.wikipedia.org\&utm_campaign=parser\&utm_content=thumbnail | jpeg | 500 | 750 | 81015 |
| https://thumb.wikimedia.org/wikipedia/commons/thumb/7/71/Sumburgh_Lighthouse_Lamp.jpg/500px-Sumburgh_Lighthouse_Lamp.jpg?utm_source=en.wikipedia.org\&utm_campaign=parser\&utm_content=thumbnail | jpeg | 500 | 894 | 71134 |

Rows from live run `5dVND5wPhxqehWR2b` on Apify (JPEG and PNG only, at least 200 px wide and 5,000 bytes, from the Wikipedia article on lighthouses). Notice that where a page offers several sizes in `srcset`, only the largest is downloaded.

### ✅ What you get

Every field of each record:

- `record_id`, `sha256` — file hash (record_id is its first 24 characters): the same file always gets the same id.
- `source_page`, `image_url`, `final_url` — the page it was found on, the image link as found, and where it was served from after redirects.
- `alt_text`, `found_in` — the alt text or title the page gives it, and where it was found (img src, img srcset, picture source, meta og:image, link to image, direct link).
- `content_type`, `format`, `bytes`, `width`, `height` — type, format (jpeg, png, gif, webp, svg, avif, bmp, ico, tiff), size and pixel dimensions (empty for SVG).
- `file_key`, `file_url` — key and link of the saved file in the run's key-value store (empty when saveImages is off).
- `downloaded_at` — when it was downloaded (UTC).

### ⚙️ Sample inputs

**Images from one article (the Store example)**

```json
{
  "urls": ["https://en.wikipedia.org/wiki/Lighthouse"],
  "maxImages": 10
}
```

The first 10 images in page order.

**Large photos only, details without files**

```json
{
  "urls": ["https://en.wikipedia.org/wiki/Lighthouse"],
  "imageTypes": ["jpeg", "png"],
  "minWidth": 200,
  "minBytes": 5000,
  "saveImages": false,
  "maxImages": 100
}
```

Skips icons and logos; gives size, dimensions and hash for each image.

**Direct image links**

```json
{
  "urls": ["https://thumb.wikimedia.org/wikipedia/commons/thumb/2/27/Torre_de_H%C3%A9rcules_2023.jpg/500px-Torre_de_H%C3%A9rcules_2023.jpg"],
  "maxImages": 5
}
```

Each link is downloaded as it is.

Input fields:

| Field | Meaning |
|---|---|
| `urls` | pages or direct image links (up to 5,000). |
| `imageTypes` | jpeg, png, gif, webp, svg, avif, bmp, ico, tiff. Empty = all. |
| `saveImages` | store each file with a link (default true). |
| `minWidth`, `minHeight`, `minBytes` | skip smaller images (0 = no limit). |
| `maxImageMb` | skip files bigger than this (default 25). |
| `maxImagesPerPage` | image links tried per page, in page order (default 100). |
| `maxImages` | stop after this many delivered images (default 1,000). Each delivered image is billed. |

### 🧾 JSON sample record

```json
{
  "record_id": "405faa992abf5258a9dd2d4c",
  "source_page": "https://en.wikipedia.org/wiki/Lighthouse",
  "image_url": "https://upload.wikimedia.org/wikipedia/commons/6/61/Smeaton%27s_Lighthouse00.jpg?utm_source=en.wikipedia.org&utm_campaign=parser&utm_content=thumbnail_unscaled",
  "final_url": "https://upload.wikimedia.org/wikipedia/commons/6/61/Smeaton%27s_Lighthouse00.jpg?utm_source=en.wikipedia.org&utm_campaign=parser&utm_content=thumbnail_unscaled",
  "alt_text": null,
  "found_in": "img srcset",
  "content_type": "image/jpeg",
  "format": "jpeg",
  "bytes": 39304,
  "width": 321,
  "height": 503,
  "sha256": "405faa992abf5258a9dd2d4c7ad5934b4691af214a94752ff99d4f74e0475fed",
  "file_key": "img-405faa992abf5258a9dd2d4c.jpg",
  "file_url": "https://api.apify.com/v2/key-value-stores/yhqh2NgL36BnSDpU2/records/img-405faa992abf5258a9dd2d4c.jpg",
  "downloaded_at": "2026-10-17T04:16:56Z"
}
```

### 🔧 How it works

Each page is downloaded once and parsed; each image is then downloaded (8 at a time) to measure it and hash it. Pillow reads the dimensions. A file already delivered in the run (same SHA-256) is skipped, so the same logo on 50 pages is billed once.

Every run reads each host's robots.txt first: a path it disallows is skipped, listed in the run receipt and never charged. Only public internet addresses are fetched (no private, loopback or cloud-metadata addresses, checked again on every redirect). No logins, no CAPTCHA solving, no proxies.

Saved files go to the run's own key-value store. If your Apify account keeps storages private, add your API token to the link (`?token=…`) or open the store from the run in Apify Console.

Delivery is pushed first and charged after. The run receipt (key-value store record `RUN_RECEIPT`) lists every page with its outcome and number of images found and delivered, the robots.txt result per host, the reasons images were skipped with counts, and the delivered and charged counts.

### 💰 Pricing example

Per image delivered, no start fee, no monthly charge:

| Apify plan | Price per image |
|---|---|
| Free | $0.05 |
| Bronze | $0.045 |
| Silver | $0.04 |
| Gold, Platinum, Diamond | $0.0335 |

- 10 images: $0.50 on the Free plan, $0.335 on Gold.
- 1,000 images: $50.00 on Free, $33.50 on Gold.
- 10,000 images: $500.00 on Free, $335.00 on Gold.

**Coverage and size.** Proof runs on 17 Oct 2026: the Wikipedia article on lighthouses gave 94 image links; a 40-image run over two articles finished in about 6 seconds. Works on any public page that puts its images in the HTML; images added only by JavaScript after load are not seen.

### ⚖️ Legal & ToS

Images belong to their owners. You choose the links. Make sure you have the right to download and use the content you point it at; the actor fetches only what you give it (and, where you turn it on, what those pages link to).

Every run reads each host's robots.txt first: a path it disallows is skipped, listed in the run receipt and never charged. Only public internet addresses are fetched (no private, loopback or cloud-metadata addresses, checked again on every redirect). No logins, no CAPTCHA solving, no proxies.

### ❓ FAQ

**Does it run JavaScript?**
No. It reads the HTML the server sends, including srcset and lazy-load attributes. Images inserted later by scripts are not found.

**Can I download only the files, not the details?**
Every delivered image has a row; the file itself is in the key-value store.

**Why was an image skipped?**
RUN_RECEIPT counts the reasons: type not selected, smaller than your limits, duplicate, over maxImageMb, blocked by robots.txt, or the link was not an image.

### 🆘 Troubleshooting

- **file_url answers 403** — Your account keeps storages private: add `?token=YOUR_TOKEN` to the link or open the store from the run.
- **Fewer images than on the page** — Check maxImagesPerPage and the skipped counts in RUN_RECEIPT.
- **SOURCE UNAVAILABLE** — None of your links could be reached after retries. Nothing was charged.

### 🔗 Related Actors

- [Brand Logo & Favicon Lookup](https://apify.com/nexgensignal/brand-logo-lookup-api) — a company's logo from its domain
- [Website Tech Stack Detector](https://apify.com/nexgensignal/website-tech-stack-api) — what a site is built with
- [Website Performance Audit](https://apify.com/nexgensignal/website-performance-audit-api) — Lighthouse scores and web vitals per page

🏢 **About NexGen Signal** — data tools and official-source data products on Apify. Browse the catalog: [apify.com/nexgensignal](https://apify.com/nexgensignal)

### ⭐ Found this useful?

If this actor saved you time, a rating on its Apify Store page helps other buyers find it.

Part of NexGen Signal — data tools and official-source data products.

# Actor input Schema

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

Web pages to collect images from, or direct image links. Up to 5,000 per run.

## `imageTypes` (type: `array`):

Empty = all types.

## `saveImages` (type: `boolean`):

Store each file in the run's key-value store and give a download link. Off = details only (size, type, dimensions, hash); the file is still downloaded to measure it.

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

Skip images narrower than this. 0 = no limit.

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

Skip images shorter than this. 0 = no limit.

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

Skip tiny files such as tracking pixels and icons. 0 = no limit.

## `maxImageMb` (type: `integer`):

Skip files bigger than this.

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

Most image links to try from one page, in page order.

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

Stop after this many images are delivered. Each delivered image is billed; skipped ones are not.

## Actor input object example

```json
{
  "urls": [
    "https://en.wikipedia.org/wiki/Lighthouse"
  ],
  "imageTypes": [],
  "saveImages": true,
  "minWidth": 0,
  "minHeight": 0,
  "minBytes": 0,
  "maxImageMb": 25,
  "maxImagesPerPage": 100,
  "maxImages": 10
}
```

# Actor output Schema

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

The delivered (and billed) records.

## `receipt` (type: `string`):

Per-link outcome, robots result, delivered and charged counts.

# 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://en.wikipedia.org/wiki/Lighthouse"
    ],
    "maxImages": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("nexgensignal/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://en.wikipedia.org/wiki/Lighthouse"],
    "maxImages": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("nexgensignal/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://en.wikipedia.org/wiki/Lighthouse"
  ],
  "maxImages": 10
}' |
apify call nexgensignal/bulk-image-downloader --silent --output-dataset

```

## MCP server setup

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