# Web Image Downloader Scraper (`automation-lab/web-image-downloader`) Actor

Download images in bulk from direct URLs or public web pages and export original files with source URLs, MIME types, byte sizes, dimensions, alt text, filenames, and SHA-256 hashes.

- **URL**: https://apify.com/automation-lab/web-image-downloader.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/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

## Web Image Downloader Scraper

Download original image files in bulk from direct image URLs or anonymously reachable public web pages.
This **web image downloader** saves every accepted image to Apify storage and returns structured metadata for asset inventories, migrations, audits, and scheduled comparisons.

Supply one URL or a batch of URLs.
The Actor distinguishes image responses from HTML pages automatically.
For pages, it discovers standard `<img>`, `srcset`, lazy-load, `<picture>`, Open Graph, and Twitter image references.
For direct images, it saves the response without HTML parsing.

### What does Web Image Downloader do?

Web Image Downloader turns public web images into reusable files plus clean dataset records.
For each unique downloaded file it provides:

- the original input and source-page URL;
- the discovered and final redirected image URL;
- the original image bytes in the run's key-value store;
- filename, MIME type, extension, and byte size;
- width and height when the format exposes dimensions;
- page alt text when present;
- a SHA-256 content hash for exact duplicate checks;
- a timestamp and authenticated Apify file API link.

It downloads the original HTTP response bytes.
It does not resize, recompress, convert, or watermark images.

### Who is it for?

**Content migration teams** can collect image assets before moving a public site to a new CMS.

**SEO and accessibility specialists** can compare image files, dimensions, and available alt text from supplied pages.

**Developers and data engineers** can feed stable file URLs and hashes into ETL, computer-vision, deduplication, or archival workflows.

**Brand and ecommerce operators** can build a bounded inventory of anonymously accessible image assets from pages they are authorized to process.

**Researchers** can schedule the same public pages and compare SHA-256 hashes between runs to identify changed files.

### Why use this instead of a browser extension?

A browser extension is useful for a one-off manual download on one computer.
This Actor is designed for repeatable cloud automation:

- process many supplied URLs in one run;
- save files and metadata together;
- call the workflow through an API;
- schedule recurring runs;
- connect results to webhooks, datasets, and other Actors;
- enforce a predictable global image limit;
- retain source provenance for every dataset record.

It is a cloud workflow, not a web image downloader extension, Chrome extension, Android app, or APK.
No software is installed in a browser or mobile device.

### Supported input routes

#### Direct image URLs

Pass a public HTTP or HTTPS URL whose successful response has an `image/*` MIME type.
The Actor follows up to five public redirects, validates the final response, and saves its bytes.

```json
{
  "startUrls": [
    { "url": "https://upload.wikimedia.org/wikipedia/commons/6/6a/JavaScript-logo.png" }
  ],
  "maxImages": 1
}
```

#### Public page URLs

Pass an anonymously reachable server-rendered HTML page.
The Actor resolves relative image paths against the final page URL and prefers the largest advertised `srcset` candidate.

```json
{
  "startUrls": [
    { "url": "https://en.wikipedia.org/wiki/Web_scraping" }
  ],
  "maxImages": 5,
  "maxImagesPerPage": 10
}
```

You can mix direct image and page URLs in the same run.
The global `maxImages` limit applies to both routes.

### Image sources discovered on a page

The HTML parser recognizes these public, server-rendered references:

| HTML source | Behavior |
| --- | --- |
| `<img src>` | Downloads the resolved source URL. |
| `<img srcset>` | Selects the candidate with the largest width or density descriptor. |
| Lazy-load attributes | Reads `data-src`, `data-lazy-src`, `data-original`, and `data-srcset`. |
| `<picture><source>` | Reads image source sets exposed in HTML. |
| Open Graph | Reads `og:image` and `og:image:url`. |
| Twitter cards | Reads `twitter:image`. |
| `link[rel=image_src]` | Reads a declared page image. |

CSS background images, canvas pixels, dynamically rendered JavaScript state, authenticated pages, and browser-only network responses are not discovered.
Use direct image URLs when you already know the assets and need exact control.

### Getting started

1. Open the Actor in Apify Console.
2. Add at least one direct image URL or public page URL under **Image or page URLs**.
3. Choose a conservative **Maximum images** value for the first run.
4. Keep the default file-size and timeout limits unless the source requires a bounded increase.
5. Click **Start**.
6. Open **Downloaded image metadata** to inspect dataset rows.
7. Open **Downloaded image files** to browse the original binary files.
8. Save the input as an Apify Task if you want a schedule or webhook integration.

The prefilled input uses a direct PNG and a public Wikipedia page so a new user can verify both routes.

### Input parameters

| Field | Type | Default | Description |
| --- | --- | ---: | --- |
| `startUrls` | array | required | One to 1,000 public HTTP(S) direct image or HTML page URLs. |
| `maxImages` | integer | `100` | Global maximum number of unique image files saved, from 1 to 5,000. |
| `maxImagesPerPage` | integer | `20` | Maximum candidates accepted from one page, from 1 to 1,000. |
| `maxFileSizeBytes` | integer | `20971520` | Maximum accepted response size; default 20 MiB and maximum 100 MiB. |
| `requestTimeoutSecs` | integer | `30` | Per-request timeout before a bounded retry, from 5 to 120 seconds. |

Malformed URLs, credential-bearing URLs, non-HTTP protocols, localhost targets, and private network addresses are rejected.
These restrictions protect the run from accidental access to internal services.

### Output dataset

Each successfully saved image creates one record in the default dataset.
Nullable fields remain `null` when the source does not provide them or the image format does not expose them.

| Field | Meaning |
| --- | --- |
| `sourceUrl` | Original direct image or page URL from the input. |
| `pageUrl` | Final page URL where the image was discovered; `null` for direct input. |
| `imageUrl` | Resolved image candidate before image redirects. |
| `resolvedUrl` | Final image response URL after redirects. |
| `fileName` | Sanitized response or URL filename. |
| `storageKey` | Key of the original file in the run's key-value store. |
| `fileUrl` | Apify API URL for the stored file; authentication may be required. |
| `mimeType` | HTTP image MIME type. |
| `extension` | Normalized extension inferred from a known MIME type. |
| `byteSize` | Exact downloaded byte count. |
| `width`, `height` | Detected pixel dimensions when available. |
| `altText` | Trimmed HTML alt text, or `null`. |
| `sha256` | SHA-256 hash of the original bytes. |
| `downloadedAt` | ISO 8601 save timestamp. |

A real direct-PNG result has this shape:

```json
{
  "sourceUrl": "https://httpbin.org/image/png",
  "pageUrl": null,
  "imageUrl": "https://httpbin.org/image/png",
  "resolvedUrl": "https://httpbin.org/image/png",
  "fileName": "png.png",
  "storageKey": "image-541a1ef5373be3dc-png.png",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/image-541a1ef5373be3dc-png.png",
  "mimeType": "image/png",
  "extension": "png",
  "byteSize": 8090,
  "width": 100,
  "height": 100,
  "altText": null,
  "sha256": "541a1ef5373be3dc49fc542fd9a65177b664aec01c8d8608f99e6ec95577d8c1",
  "downloadedAt": "2026-01-15T12:00:00.000Z"
}
```

The file remains in the default key-value store according to your Apify storage retention settings.

### Exact duplicate handling

The Actor avoids charging or creating multiple records for the same discovered URL in one run.
It also hashes downloaded bytes and skips a later response whose SHA-256 hash matches an already saved file.

URL deduplication prevents repeated requests when a page declares the same source in multiple tags.
Hash deduplication catches identical bytes delivered through different URLs.
The first accepted source retains the dataset provenance.

### How much does it cost to download web images?

Pricing uses one `start` event per run and one `item` event per successfully saved image record.
Failed, rejected, and duplicate images do not emit an `item` charge.
Apify platform usage is billed according to the pricing shown in Console.

Current source pricing is:

| Event | FREE | BRONZE | SILVER | GOLD | PLATINUM | DIAMOND |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Run start | $0.001 one time | $0.001 | $0.001 | $0.001 | $0.001 | $0.001 |
| Downloaded image | $0.0007912 | $0.000688 | $0.00053664 | $0.0004128 | $0.0002752 | $0.00019264 |

At BRONZE rates, one downloaded image costs about **$0.001688** including the run start, 10 images cost about **$0.00788**, and 100 images cost about **$0.0698**.
The exact charge depends on the number of successfully saved unique images and your Apify tier.

### Scheduled image monitoring workflow

To detect changes on public pages:

1. Create a Task with the pages and a fixed `maxImages` value.
2. Schedule it daily, weekly, or monthly in Apify Console.
3. Export each run's dataset to your warehouse or automation tool.
4. Match records by `resolvedUrl` or another source key.
5. Compare `sha256`, `byteSize`, `width`, and `height` with the prior run.
6. Trigger your own alert when a hash appears, disappears, or changes.

The Actor supplies the repeatable snapshots and content hashes.
It does not retain cross-run state or send change alerts by itself.

### CMS migration and asset audit workflow

For a bounded public-site migration:

1. Supply the page URLs included in the migration scope.
2. Set `maxImagesPerPage` high enough for the expected page template.
3. Review `sourceUrl`, `pageUrl`, and `resolvedUrl` for provenance.
4. Download the files from the run's key-value store.
5. Use `sha256` to collapse identical assets before upload.
6. Use dimensions and byte sizes to find oversized or unexpectedly small files.
7. Review nullable `altText` separately; missing alt text is not inferred or generated.

This workflow inventories supplied pages only.
The Actor does not crawl internal links or discover an entire domain automatically.

### API usage with cURL

Start a run and wait for completion:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~web-image-downloader/runs?token=$APIFY_TOKEN&waitForFinish=300" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{"url": "https://en.wikipedia.org/wiki/Web_scraping"}],
    "maxImages": 5
  }'
```

Fetch dataset items using the `defaultDatasetId` returned by the run:

```bash
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?clean=true&format=json&token=$APIFY_TOKEN"
```

### API usage with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/web-image-downloader').call({
    startUrls: [
        { url: 'https://en.wikipedia.org/wiki/Web_scraping' },
    ],
    maxImages: 5,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map(({ fileName, fileUrl, sha256 }) => ({ fileName, fileUrl, sha256 })));
```

The image files are stored in `run.defaultKeyValueStoreId`.
Dataset `fileUrl` values point to individual records in that store.

### API usage with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/web-image-downloader').call(run_input={
    'startUrls': [
        {'url': 'https://en.wikipedia.org/wiki/Web_scraping'},
    ],
    'maxImages': 5,
})

for item in client.dataset(run['defaultDatasetId']).iterate_items():
    print(item['fileName'], item['mimeType'], item['sha256'])
```

Use the key-value store client with `run['defaultKeyValueStoreId']` when you need the binary response directly.

### Use with Apify MCP

Add this Actor to Claude Code through the Apify MCP server:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/web-image-downloader"
```

#### Claude Desktop, Cursor, and VS Code setup

Claude Desktop, Cursor, and VS Code can use this equivalent MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/web-image-downloader"
    }
  }
}
```

Example prompts:

- "Download up to five images from this public page and list file names, dimensions, and alt text."
- "Save this direct image URL and return its SHA-256 hash and Apify file link."
- "Run the web image archive Task and compare its dataset hashes with the previous run."

### Reliability and request behavior

Requests have a configurable timeout and up to three attempts for transient network, HTTP 429, and server-side 5xx failures.
Permanent client responses such as 400, 403, and 404 are not retried blindly.
Redirects are bounded and every next target is checked before the request.

Downloads run sequentially.
This intentionally favors predictable memory use and respectful source traffic over maximum request speed.
The Actor has no automatic residential proxy or browser fallback, so there is no hidden paid-proxy transfer mode.

A failed image does not stop independent URLs from being processed.
If every supplied URL fails, or candidates are found but none can be downloaded, the run fails with a non-zero status.
A valid HTML page containing no supported image references succeeds with zero records.

### Limits and unsupported pages

The Actor is designed for anonymously reachable public HTTP(S) resources.
It does not support:

- login, cookies, credentials, or private sessions;
- CAPTCHA solving or bypassing access controls;
- localhost, intranet, cloud metadata, or private IP targets;
- JavaScript-rendered images absent from initial HTML;
- CSS background-image extraction;
- `data:`, `blob:`, FTP, or local file URLs;
- automatic site crawling, sitemap expansion, or search queries;
- image conversion, OCR, classification, or alt-text generation;
- ZIP aggregation of all files into one memory-heavy archive.

Some sites block automated downloads or reject hotlinks even with the source page as referer.
Supply authorized direct URLs or choose a source that permits anonymous access.

### Responsible use and legality

Only download images that you are authorized to access and use.
Public availability does not automatically grant copyright, trademark, privacy, or redistribution rights.

Review the source website's terms, robots guidance where applicable, licenses, and applicable law.
Respect usage restrictions and use bounded limits.
Do not use the Actor to bypass authentication, technical controls, or paywalls.

You are responsible for retention, sharing, and downstream use of downloaded files.
Delete Apify storage when it is no longer needed.

### Troubleshooting

#### Why did a page return zero images?

The page may contain no supported server-rendered references.
Inspect the original HTML rather than only the rendered browser view.
Images inserted by JavaScript, CSS backgrounds, canvas, and authenticated requests are outside this Actor's scope.

#### Why did an image URL fail?

Check the run log for HTTP status, timeout, DNS, private-network, content-type, or file-size errors.
Open the URL in an anonymous session and verify it responds with `image/*` rather than HTML or a challenge page.
Increase limits only when the source and file size are known.

#### Why is `width` or `height` null?

The downloaded MIME type may be valid while its encoding is not supported by the dimension parser, or the source may omit intrinsic dimensions.
The original bytes, MIME type, byte size, and hash are still available.

#### Why does `fileUrl` require authentication?

Run storage is private by default.
Use your Apify token, the Console output link, or the Apify client for the run's key-value store.
Do not append tokens to datasets or commit them to source control.

### Related Automation Lab Actors

- [Link Extractor](https://apify.com/automation-lab/link-extractor) extracts page links when the buyer needs URL discovery rather than image-file storage.
- [Webpage Text Extractor](https://apify.com/automation-lab/webpage-text-extractor) extracts readable page text for content pipelines.
- [HTML to PDF Converter](https://apify.com/automation-lab/html-to-pdf-converter) preserves a page as a rendered document instead of separate original image files.

Combine related Actors through Tasks, schedules, webhooks, or API orchestration only when each one matches the required data type.

### FAQ

#### Does this download every image from an entire website?

No.
It processes only the direct image and page URLs supplied in `startUrls`.
It does not crawl links to other pages.

#### Can I mix direct images and pages?

Yes.
Both routes share the same `maxImages`, deduplication, storage, output, and pricing behavior.

#### Are duplicate images charged twice?

Not within one run when they have the same discovered URL or exact SHA-256 content hash.
The first successfully saved copy creates the dataset record and `item` event.

#### Does it preserve the original image quality?

Yes, for a successful response.
The Actor stores the received image bytes without conversion or recompression.
For `srcset`, it selects the largest advertised candidate rather than downloading every variant.

#### Can I schedule recurring downloads?

Yes.
Save a working input as an Apify Task, attach a schedule, and compare dataset hashes downstream.
The Actor itself does not send change alerts.

#### Can it scrape protected or logged-in pages?

No.
The supported product scope is anonymous public pages and direct image URLs.
There is no credential, session, browser, or paid-proxy input mode.

#### Where are the files?

Original image bytes are in the run's default key-value store under keys beginning with `image-`.
The default dataset contains one metadata row and a `fileUrl` for each saved file.

# Actor input Schema

## `startUrls` (type: `array`):

Direct HTTP(S) image URLs and/or anonymously reachable public web-page URLs. Private-network, authenticated, data:, and file: URLs are rejected.

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

Maximum number of unique image files to save across all supplied URLs.

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

Maximum number of image candidates accepted from each HTML page before global deduplication.

## `maxFileSizeBytes` (type: `integer`):

Reject an individual response larger than this many bytes. The default is 20 MiB.

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

Maximum duration of each page or image request before a bounded retry.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://httpbin.org/image/png"
    },
    {
      "url": "https://en.wikipedia.org/wiki/Web_scraping"
    }
  ],
  "maxImages": 10,
  "maxImagesPerPage": 20,
  "maxFileSizeBytes": 20971520,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

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

Dataset containing one typed record per successfully downloaded image.

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

Key-value store containing the original downloaded image bytes.

# 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 = {
    "startUrls": [
        {
            "url": "https://httpbin.org/image/png"
        },
        {
            "url": "https://en.wikipedia.org/wiki/Web_scraping"
        }
    ],
    "maxImages": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/web-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 = {
    "startUrls": [
        { "url": "https://httpbin.org/image/png" },
        { "url": "https://en.wikipedia.org/wiki/Web_scraping" },
    ],
    "maxImages": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/web-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 '{
  "startUrls": [
    {
      "url": "https://httpbin.org/image/png"
    },
    {
      "url": "https://en.wikipedia.org/wiki/Web_scraping"
    }
  ],
  "maxImages": 10
}' |
apify call automation-lab/web-image-downloader --silent --output-dataset

```

## MCP server setup

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