Website Image Extractor — Alt Text, Size & SEO Audit avatar

Website Image Extractor — Alt Text, Size & SEO Audit

Pricing

$0.80 / 1,000 image reads

Go to Apify Store
Website Image Extractor — Alt Text, Size & SEO Audit

Website Image Extractor — Alt Text, Size & SEO Audit

List every image on a web page or site with its real pixel size and file size, alt text and SEO/accessibility flags. Reads only the first bytes of each image: nothing is downloaded or stored. Pay per image, blocked pages are free.

Pricing

$0.80 / 1,000 image reads

Rating

0.0

(0)

Developer

Francesco Antonio Russo

Francesco Antonio Russo

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Categories

Share

Give it a web page (or a whole site) and get one record per image: the real pixel size and file size, the alt text, where the image sits on the page, and a list of SEO, accessibility and performance problems. It reads only the first bytes of each image to learn its size: no image is downloaded, stored or returned, so there is nothing to unzip, nothing to pay storage for, and no copy of anyone's pictures.

Use it to audit alt text before a launch, find the 3 MB hero image that slows a page, list every image of a competitor's product page with its real dimensions, feed an image inventory into a spreadsheet or a CMS migration, or check that a site declares width/height (layout shift) on its images.

  • Plain HTTP, no browser. Fast, cheap, 256 MB of memory. The flip side: images that a page only draws with JavaScript are not seen (see Limits).
  • Pay per image read: $0.0008 per image ($0.80 per 1,000). Pages that block us, images that cannot be read and pages that need JavaScript are free.
  • Polite and honest. It identifies itself as ImageMetadataBot, obeys robots.txt (for the page and for the host of every image), spaces its requests and never tries to get past a bot wall.

What you get

FieldMeaning
pageUrl, imageUrl, finalUrlThe page, the image as the page declares it (absolute URL), and the image URL after redirects
sourceimg · picture · srcset (no src, the largest candidate) · lazy-attr:data-src (also data-lazy-src, data-original, data-lazy, data-url) · css-background · og:image · twitter:image
altThe alt text as written. null = no alt attribute at all, "" = a decorative image (the difference matters for accessibility)
declaredWidth, declaredHeightThe width / height attributes of the tag, in pixels
naturalWidth, naturalHeightThe real size, read from the file header (JPEG: EXIF rotation applied, as browsers do)
format, contentTypeFormat from the file's magic bytes (jpeg, png, gif, webp, avif, heic, svg, ico, bmp) and the type the server sent
fileSizeBytes, sizeSourceFile size, and how it was learned: range (Content-Range) · content-length · body (a short body counted) · unknown
srcsetEvery srcset and <picture><source> candidate with its descriptor (800w, 2x) and MIME type
loading, fetchpriority, region, domIndex, occurrencesloading and fetchpriority attributes; nearest header/nav/main/aside/footer; position on the page; how many times the same URL appears on it
statusok (read) · broken (404/410) · unmeasured (could not be read: free)
unmeasuredReason, httpStatusWhy an image was not measured (robots_txt, blocked, rate_limited, timeout, http_error, private_address, no_dimensions…) and the HTTP status
issuesThe flags below
charged, checkedAtWhether this record is a billed event; when it was checked

Example record (real run, https://wordpress.org/news/)

{
"pageUrl": "https://wordpress.org/news/",
"imageUrl": "https://wordpress.org/news/files/2024/01/sunita-rai-featured-img.jpg",
"finalUrl": "https://wordpress.org/news/files/2024/01/sunita-rai-featured-img.jpg",
"source": "img",
"alt": "Sunita Rai at WordCamp Asia",
"title": null,
"declaredWidth": 1920,
"declaredHeight": 1200,
"naturalWidth": 1920,
"naturalHeight": 1200,
"format": "jpeg",
"contentType": "image/jpeg",
"fileSizeBytes": 1457137,
"sizeSource": "range",
"srcset": [
{ "url": "https://i0.wp.com/wordpress.org/news/files/2024/01/sunita-rai-featured-img.jpg?w=1920&ssl=1", "descriptor": "1920w" },
{ "url": "https://i0.wp.com/wordpress.org/news/files/2024/01/sunita-rai-featured-img.jpg?resize=300%2C188&ssl=1", "descriptor": "300w" }
],
"loading": null,
"fetchpriority": "high",
"region": "main",
"domIndex": 0,
"occurrences": 1,
"status": "ok",
"unmeasuredReason": null,
"httpStatus": 206,
"issues": ["TOO_HEAVY", "LEGACY_FORMAT"],
"charged": true,
"checkedAt": "2026-10-15T10:37:41.796Z"
}

(The real record lists five srcset candidates; two are shown. 1.4 MB is the real size of that file: the check read 32 KB of it.)

The flags in issues

FlagWhen it is raisedWhy you care
ALT_MISSINGAn <img>-based image with no alt attribute, not marked decorative (role="presentation", aria-hidden="true")Accessibility and image SEO
ALT_EMPTYalt=""Correct for decorative images; listed so you can check each one really is decorative
ALT_TOO_LONGAlt text over 125 charactersScreen readers cut or skip it
ALT_IS_FILENAMEAlt is a file name: with an extension (hero.jpg), a camera name (IMG_1234, DSC0042) or a technical name with digits, hyphens or underscores (hero-banner-2). A plain word that is also a fair description ("Atalanta" for atalanta.png, "PayPal" for paypal.svg) is not flaggedIt describes nothing
ALT_EMPTY_IN_LINKalt="" on an image that is the only content of a linkThe link has no accessible name
TOO_HEAVYFile larger than heavyThresholdKb (default 300 KB)Page weight
DIMENSIONS_NOT_DECLAREDNo width and height (nor inline width+height or aspect-ratio)Layout shift (CLS)
DECLARED_MISMATCH(not for SVG) The declared width/height ratio differs from the real one by more than 5 %Distorted or shifting image (unless a stylesheet crops it on purpose, e.g. object-fit: cover: stylesheets are not read)
OVERSIZED_FOR_DISPLAY(not for SVG) Real width more than 2x the declared width (declared sides under 10 px, like width="3" height="2", are read as an aspect ratio, not a size)Bytes the visitor never sees
LEGACY_FORMATJPEG or PNG over 100 KB, with no WebP/AVIF alternative in the page's srcset or <picture>Smaller formats are available
BROKEN_IMAGEThe image answers 404 or 410Broken image on the page
MIXED_CONTENTAn http:// image loaded by an https:// pageBrowsers block or warn
NOT_AN_IMAGEThe URL answers 200 but the bytes are not an image (usually an HTML error page)A "soft 404"

Size flags (TOO_HEAVY, DECLARED_MISMATCH, OVERSIZED_FOR_DISPLAY, LEGACY_FORMAT) need a measured image (an SVG is drawn at any size, so it has no DECLARED_MISMATCH or OVERSIZED_FOR_DISPLAY); alt and dimension flags work from the HTML alone. CSS backgrounds and og:image/twitter:image get size flags but no alt flags (they have no alt attribute to check).

Run summary

Next to the dataset, the key-value store holds a SUMMARY record (free): every page read with its status, the number of images found, delivered and charged, the issues per page, PAGE_LIKELY_CSR flags and warnings, plus totals and a count per issue type. Use it to see at a glance why a page gave nothing.

Input

FieldDefaultWhat it does
startUrlsWikipedia "Lighthouse"Pages to read. A bare domain (example.com) gets https://
crawlSameDomainoffAlso read pages linked from yours, on the same host (www. and the bare domain count as one). At most 30 new links per page. Never leaves the site
maxCrawlDepth1Link levels to follow (1-3)
maxPages20Pages read in total, start pages included (1-1,000). The start pages you list are always read first (in your order, up to maxPages; any beyond it are listed in SUMMARY.pages as stopped, with a warning). The pages the crawl finds share what is left: sites take turns, equal parts, and a part a site does not use goes to the others. Every page left out for lack of allowance is counted in SUMMARY.warnings
maxImagesPerPage200Images processed per page, in document order (1-500). The rest are neither read nor charged
measureImagesonRead each image's header. Off = HTML facts only (alt, declared size, source) and no size flags
includeCssBackgroundsonbackground / background-image in style attributes and <style> blocks
includeMetaImagesonog:image and twitter:image. A tag whose value is not a URL (content="https:") is skipped with a page warning, free
heavyThresholdKb300TOO_HEAVY threshold (1 KB = 1,024 bytes)
minSidePx0Skip icons and tracking pixels: images whose longest side is below this are not delivered and not charged
dedupeAcrossPagesonAn image used on several pages (logo, icons) is delivered once, with the first page that uses it; the later pages say how many images they left out (imagesFound still counts them)

Repeats of one URL on the same page always collapse into one record (occurrences says how many; the alt/dimension problems of each occurrence are kept).

What you pay, and what you don't

$0.0008 per image record that was read (event image): $0.80 per 1,000 images. A record is billed when its status is ok or broken.

RunImages billedCost
One article page (Wikipedia "Lighthouse", real run)39$0.031
A shop product page (Allbirds, real run)34$0.027
5 pages of a site, crawling (wordpress.org, real run)76$0.061
1,000 images1,000$0.80
50 pages x 40 images2,000$1.60

Free, always:

  • a page that answers 401 / 403 / 429 or shows a challenge page ("blocked"), 404/410, a server error, a timeout, a file that is not HTML;
  • a page that robots.txt forbids;
  • a page that only draws its pictures with JavaScript (PAGE_LIKELY_CSR): there is nothing in the HTML to report, so nothing is billed;
  • an image that could not be measured (status: "unmeasured"): forbidden by robots.txt, hot-link protected (403), rate limited (429), server error, timeout, unreachable host, or an image whose pixel size could not be read (no_dimensions: an unusual format, an SVG with no size, a header the Actor could not reach). Such an image is still delivered, with the alt text and every HTML-based flag, at no charge. (A measured image is never billed without its dimensions; the one exception is a broken image, a 404/410, which is billed as a verified finding and has no dimensions to give);
  • images skipped by minSidePx, repeats removed by deduplication, images beyond maxImagesPerPage, the SUMMARY record.

Your spending limit is exact. If you set a maximum cost per run, the Actor checks the budget before every record and stops when it is spent. It never charges past it; SUMMARY.stopReason says spending limit reached and heldByLimit counts what was found but not delivered. maxPages x maxImagesPerPage is the other natural cap.

FAQ

Does it download the images? No. For each image it asks the server for the first 32 KB (Range: bytes=0-32767) and reads the size from the header. If the server ignores Range it reads at most 64 KB (256 KB for a JPEG whose header sits behind a large EXIF block, or an AVIF/HEIC whose size box comes late) and hangs up. The bytes are examined in memory and thrown away; no file is stored or returned.

Can I use the images I find? The Actor reports facts about images. You are responsible for the right to use any image it lists: most images are protected by copyright and licence terms, whatever the technical possibility of fetching them.

Why is alt sometimes null and sometimes ""? null means the tag has no alt attribute (a problem for screen readers and SEO); "" means the author deliberately marked the image as decorative (usually correct). Images from CSS backgrounds and meta tags have no alt attribute at all and are null by nature.

Which image does a srcset record show? The one in src. With no src, the largest srcset candidate. All candidates, including <picture> sources, are listed in srcset.

A page returned nothing. Why? Open SUMMARY: each page has a status and a reason. blocked = the site answered 401/403/429 or a challenge (this Actor does not try to get past bot protection: no proxies, no CAPTCHA solving, one honest User-Agent). robots_disallowed = the page's robots.txt forbids the bot. PAGE_LIKELY_CSR = the HTML has no images and looks like a JavaScript app. All of these are free.

Why is a crawl slow on some sites? If a host's robots.txt has a Crawl-delay, requests to that host are spaced accordingly (up to 10 seconds). Image CDNs that ask for 3 seconds make 40 images take two minutes. That is the polite behaviour; it costs you almost nothing in compute.

Why did og:image show a different size than I expected? The record reports the real file, not the og:image:width the page claims.

Does it follow links to other sites? No. With crawlSameDomain it stays on the host of each start URL (www. and the bare domain are one host). Subdomains are different hosts.

Can it read internal addresses? No. Only http/https on ports 80 and 443; private, loopback, link-local and cloud-metadata addresses are refused, including after redirects and for image URLs.

Limits, stated plainly

  • JavaScript-drawn images are not seen. Plain HTTP reads the HTML the server sends. On four of five sites we compared, a real browser added only 0-13 % more images, mostly trackers; on a single-page app the HTML may have none. Such pages are flagged PAGE_LIKELY_CSR, free.
  • Content inside <noscript> is ignored (it is a fallback for images already present, and often a tracking pixel). External stylesheets and scripts are not fetched, so CSS backgrounds are only those in style attributes and <style> blocks of the page.
  • HTML is read up to 5 MB per page. At most 5 redirects are followed.
  • A page is read once per run. Pages behind a login, or that need cookies, are not supported.
  • The first measurement of an image is what is reported; CDNs that choose a format by browser (WebP/AVIF) are asked with a browser-like Accept header, so you see what a modern browser receives.
  • Concurrency is 4 requests per host and 10 in total.

Run it

Locally: put your input in <storage>/key_value_stores/default/INPUT.json, then CRAWLEE_STORAGE_DIR=$PWD/<storage> node src/main.js. Tests: npm test.

Fetch only the images with problems through the dataset API, for example the heavy ones with no alt text:

.../datasets/<id>/items?format=csv&fields=pageUrl,imageUrl,naturalWidth,naturalHeight,fileSizeBytes,alt,issues

and filter on the issues column in your spreadsheet, n8n or Make flow.