Bulk Image Downloader: Crawl Sites, Dedupe Photos, Export ZIP
Devansh Tiwari Β· @devanshtiwari365
Turn any page or list of image links into deduplicated image files with one metadata row per image.
About
Bulk Image Downloader: Crawl Sites, Dedupe Photos, Metadata Export
Download every image from a public page, or from a list of image links, and get one clean row per image: source page, size, dimensions, format, SHA-256 and how the image was found. Duplicates are skipped, tiny tracking pixels can be filtered out, and each failed image keeps a row that says why.
Start a 7-day free trial and run a small list first. No card is needed for the trial.
πΌοΈ What you get
- Images from pages and direct links. Paste webpages, image links, or a mix. The Actor reads img tags, srcset and picture sources, Open Graph and Twitter Card images, and lazy-load attributes.
- A bounded crawl. Follow links from a starting page to the depth and page limits you set, and keep it on the right sections with URL patterns.
- Duplicates removed. Each image body gets a SHA-256 fingerprint, so the same photo found on many pages is saved once.
- Filters. Minimum width, height and file size, file extensions, MIME types and a per-link image cap.
- Cleaning and conversion. Strip EXIF metadata from JPEGs and convert WebP to PNG or PNG to JPG.
- Full source tracking. Every row says whether the image came from srcset, a picture tag, an Open Graph tag, a data attribute or a direct link.
- A status row for every attempt. Failed images keep their row with the HTTP status and error.
- Your format. Download the metadata as Excel, CSV or JSON, or pull it through the API.
π₯ Who it is for
- AI engineers: "I need every image deduped and tagged with how it was found, not a folder of pictures."
- Developers with a catalog scraper: "I hand over a list of image links and get the files and a metadata table back."
- E-commerce operators: "I crawl competitor product pages on a schedule and compare image fingerprints to catch photo swaps."
- Researchers and archivists: "I pull every image from a story or site in one run."
π How it works
- Add your URLs. Paste webpages, direct image links, or both.
- Set your options. Choose depth, size filters, de-duplication and any cleaning.
- Run it and download. Read the metadata table, and fetch the image files from the run's storage.
π₯ Input
| Field | What it does |
|---|---|
| URLs | Webpages or direct image links, mixed freely. |
| Mode | Auto-detect, force page parsing, or force direct download. |
| Crawl depth and max pages | How far to follow links from each starting page. |
| URL patterns | Which links the crawl may follow. |
| Min width, height, size | Skip small images and tracking pixels. |
| Extensions and MIME types | Keep only the formats you want. |
| Max images per URL, max URLs | Caps that keep a run bounded. |
| De-duplicate by hash | Skip images already seen. |
| Strip EXIF, convert format | Clean JPEGs, convert WebP or PNG. |
| File name pattern | Name files with the slug, hash, extension, index or source. |
π Output
One row per image attempt.
| Field | What it holds |
|---|---|
source_url, image_url | The page and the image link |
filename, kv_store_key | The saved file name and its key in the run's storage |
status, error, http_status | Whether it worked and why not |
content_type, format, size_bytes | What the file is |
width, height | Pixel dimensions |
sha256, is_duplicate | Fingerprint and duplicate flag |
from_srcset, from_og_tag, from_direct_url and similar | How the image was found |
π° Pricing
$19 a month after a 7-day free trial. Compute is billed from your prepaid credits during the trial and after it.
π‘ Good to know
- Only public pages and image links are read. The Actor does not log in.
- It does not run JavaScript, so pages that build their images with scripts need their image links pasted in directly.
- Image files are saved as generic binary. Use the extension in each row's file name.
- The Actor is independent and not affiliated with any site it reads.
FAQ
- Does it run JavaScript on the page?
- No. It reads the page's HTML, including srcset and lazy-load attributes, Open Graph and Twitter Card images. Pages that build their image list with JavaScript return little or nothing, so for those, collect the image links first and paste them in as direct links.
- How are duplicate images handled?
- Each image body gets a SHA-256 fingerprint. With de-duplication on, an image whose fingerprint was already seen is flagged as a duplicate and not saved again, so the same photo found on ten pages is stored once.
- Can it crawl a whole website?
- It follows links from your starting page up to the depth and page limits you set, and a URL pattern list keeps it on the sections you want. By default it stays on the pages you supply. Per-link caps (1,000 images per URL, 10,000 URLs per run) keep a run bounded.
- What does each result row tell me?
- One row per image: the page it came from, the image link, how it was found (srcset, picture tag, Open Graph, direct link and others), HTTP status, size in bytes, width, height, format, SHA-256 and whether it succeeded. A failed image keeps its row with the error, so you can see what failed and why.
- Where do the image files go?
- Each image is saved to the run's storage under a key that starts with IMG- and ends with the file name, and the row names that key. Files are saved as generic binary, so give each file the extension shown in its row's file name when you download it.
Input
Preview of the input form. Rent the Actor to fill it in and start runs from the console.
Up to 10,000 URLs per run. Use mode=direct for a list of image URLs (no HTML parsing). Instead of pasting URLs you can also point the Actor at a CSV/text file, a public Google Sheet, or an Apify dataset below β all are merged together.
urlsList of URLs. Each can be a webpage (the Actor discovers all images on the page via HTML/srcset/og:image/twitter:image) or a direct image URL. Mixed lists allowed.
urlsFromCsvUrlPublic URL of a .csv or .txt file listing URLs. A plain text file is read one URL per line; a CSV uses the column named in 'URL column' (or auto-detects a url/image/link column). Merged into the URLs above.
urlsFromGoogleSheetUrlLink to a Google Sheet shared with 'Anyone with the link'. Read as CSV (no login or key). Uses the 'URL column' or auto-detects it. Merged into the URLs above.
urlsFromDatasetIdAn Apify dataset ID or dataset URL from your account. Each item's 'URL column' (or an auto-detected url/image/link field) is used. Handy for chaining after one of our *-scraper Actors. Merged into the URLs above.
urlFieldColumn name (CSV / Google Sheet) or field name (dataset) that holds the URL. Leave empty to auto-detect a url/image/link column.
Choose the shortest path to a useful output.
presetStart with a focused configuration. Explicit fields override the preset.
How the Actor interprets each URL in your list.
mode'auto' detects by URL extension, 'page' forces HTML parsing, 'direct' treats every URL as an image.
includeSrcsetDiscover images from , , and lazy-loaded data-src/data-srcset attributes. Picks the highest-resolution variant by default.
includeOgTagsDiscover Open Graph and Twitter Card images from page tags. Often the best cover image.
minWidthSkip images narrower than this. 0 = no minimum. Filters tracking pixels and thumbnails.
minHeightSkip images shorter than this. 0 = no minimum.
minSizeBytesSkip images smaller than this. Filters tracking pixels and 1x1 placeholders. 0 = no minimum.
maxImagesPerUrlSafety cap on images discovered per source URL. Prevents runaway runs on heavy pages.
maxUrlsCap on total URLs processed.
Bounded crawling controls.
crawlDepthFollow page links up to this depth. 0 processes only the supplied pages.
maxPagesHard cap on fetched HTML pages per input URL.
urlGlobsOptional URL patterns such as https://example.com/products/* for bounded page crawling.
includeExtensionsOptional allow-list such as jpg, png, webp. Leave empty to keep all discovered image URLs.
includeMimeTypesOptional allow-list such as image/jpeg or image/png. Applied after the response headers are known.
maxSizeBytesOptional upper bound for downloaded image bytes.
How images are downloaded and post-processed.
dedupByHashCompute SHA-256 of each image body and skip duplicates. Reports is_duplicate=true on the dataset.
stripExifRe-encode JPEGs without EXIF (camera model, GPS, timestamps). Privacy-friendly. Slightly lossy.
convertFormatOptional format conversion. webp-to-png is the most common (broader compatibility).
filenamePatternTemplated filename. Tokens: {slug} (sanitized URL slug), {hash} (SHA-256 prefix), {ext} (extension), {idx} (index), {source} (source domain).
outputFormatAlways emits the structured dataset. Optionally also: ZIP archive (single or per-URL), KV store binaries, S3 upload, or webhook on completion. Valid values: dataset, kv-store, zip, zipPerUrl, s3, webhook.
s3BucketRequired when outputFormat includes 's3'. Use AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION env vars (set via Apify Secrets).
webhookUrlPOST a completion event with the run summary to this URL on run completion.
Performance tuning for power users.
maxConcurrencyMax parallel image downloads. Default 10.
downloadTimeoutMsPer-image fetch timeout in milliseconds.
imageCheckMaxRetriesRetries for failed image downloads (5xx, timeouts, transient network errors).
proxyConfigurationOPTIONAL. Default is no proxy. Set residential proxy if source sites are hotlink-protected or geo-fenced.
failFastStop the run on the first error. Default false (continue and report per-image errors).
debugLoggingPrint per-image tracing tags (FETCH, PARSE, HASH, WRITE) to run logs.
API
Bytewells speaks the Apify API. Existing integrations keep working: point apify-client at https://bytewells.com/api and use your Bytewells API token (Console β Settings β API access). The Actor ID is devanshtiwari365/bulk-image-downloader (or devanshtiwari365~bulk-image-downloader in URLs).
JavaScript Β· apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({
token: process.env.BYTEWELLS_TOKEN,
baseUrl: 'https://bytewells.com/api',
});
const input = {
"urls": [
{
"url": "https://apify.com"
},
{
"url": "https://picsum.photos/800/600.jpg"
}
],
"mode": "auto",
"includeSrcset": true,
"includeOgTags": true,
"minWidth": 0,
"minHeight": 0,
"minSizeBytes": 0,
"maxImagesPerUrl": 1000,
"maxUrls": 10000,
"crawlDepth": 0,
"maxPages": 1,
"urlGlobs": [],
"includeExtensions": [],
"includeMimeTypes": [],
"dedupByHash": true,
"stripExif": false,
"convertFormat": "none",
"filenamePattern": "{slug}-{hash}.{ext}",
"outputFormat": [
"dataset",
"kv-store"
],
"maxConcurrency": 10,
"downloadTimeoutMs": 15000,
"imageCheckMaxRetries": 3,
"proxyConfiguration": {
"useApifyProxy": false
},
"failFast": false,
"debugLogging": false
};
const run = await client.actor('devanshtiwari365/bulk-image-downloader').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);Python Β· apify-client
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["BYTEWELLS_TOKEN"], api_url="https://bytewells.com/api")
run_input = {
"urls": [
{
"url": "https://apify.com"
},
{
"url": "https://picsum.photos/800/600.jpg"
}
],
"mode": "auto",
"includeSrcset": True,
"includeOgTags": True,
"minWidth": 0,
"minHeight": 0,
"minSizeBytes": 0,
"maxImagesPerUrl": 1000,
"maxUrls": 10000,
"crawlDepth": 0,
"maxPages": 1,
"urlGlobs": [],
"includeExtensions": [],
"includeMimeTypes": [],
"dedupByHash": True,
"stripExif": False,
"convertFormat": "none",
"filenamePattern": "{slug}-{hash}.{ext}",
"outputFormat": [
"dataset",
"kv-store"
],
"maxConcurrency": 10,
"downloadTimeoutMs": 15000,
"imageCheckMaxRetries": 3,
"proxyConfiguration": {
"useApifyProxy": False
},
"failFast": False,
"debugLogging": False
}
run = client.actor("devanshtiwari365/bulk-image-downloader").call(run_input=run_input)
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)HTTP Β· curl
curl -X POST "https://bytewells.com/api/v2/acts/devanshtiwari365~bulk-image-downloader/runs" \
-H "Authorization: Bearer $BYTEWELLS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls":[{"url":"https://apify.com"},{"url":"https://picsum.photos/800/600.jpg"}],"mode":"auto","includeSrcset":true,"includeOgTags":true,"minWidth":0,"minHeight":0,"minSizeBytes":0,"maxImagesPerUrl":1000,"maxUrls":10000,"crawlDepth":0,"maxPages":1,"urlGlobs":[],"includeExtensions":[],"includeMimeTypes":[],"dedupByHash":true,"stripExif":false,"convertFormat":"none","filenamePattern":"{slug}-{hash}.{ext}","outputFormat":["dataset","kv-store"],"maxConcurrency":10,"downloadTimeoutMs":15000,"imageCheckMaxRetries":3,"proxyConfiguration":{"useApifyProxy":false},"failFast":false,"debugLogging":false}'Runs are billed as compute from your credit balance. Paid Actors need an active rental or trial; otherwise the API answers 402 rental-required.
Related
More Actors from the same publisher.

Google Lens OCR: Image and PDF to Text with Word Boxes
Devansh Tiwari Β· @devanshtiwari365
Turn images and PDF pages into text with line and word boxes, language detection and optional translation. One result row per image, failures kept visible.
AIDeveloper toolsAutomation$19/mo7-day free trial
Exhibitor List Scraper: Trade Show & Messe Aussteller Leads
Devansh Tiwari Β· @devanshtiwari365
Scrape trade show and Messe exhibitor lists: company, website, email, phone, booth and contacts. 148 shows, 30 platforms, one format. Excel, CSV, JSON.
Lead generationMarketingAutomation$19/mo7-day free trial
Amazon KDP Book Niche Analyzer: Self Publishing Niche Research
Devansh Tiwari Β· @devanshtiwari365
Amazon KDP niche research: real Best Sellers Rank, prices, ratings and competition for each Kindle book per keyword, plus an evidence-based opportunity score.
E-commerceMarketingSEO tools$19/mo7-day free trial
Mercari US Scraper: Active & Sold Listings, Price & Seller Data
Devansh Tiwari Β· @devanshtiwari365
Scrape Mercari US active and sold listings by keyword or link: USD price, condition, brand, category, likes and seller sales, plus a sold-price summary.
E-commerceMarketingLead generation$19/mo7-day free trial
StreetEasy Scraper: NYC Rentals, Sales and Price Drop Monitor
Devansh Tiwari Β· @devanshtiwari365
StreetEasy NYC rentals and sales as clean rows with gross and net rent, fees and price history, or a monitor that returns only new listings and price drops.
E-commerceReal estate$19/mo7-day free trial