Echo Lab Tools › Sitemap URLs
Extract every URL a website publishes in its sitemaps, with lastmod and hreflang
The sitemap URL extractor is an Apify Actor that turns websites, domains or direct sitemap links into a deduplicated list of every URL the site publishes in its sitemaps, as JSON or CSV, with lastmod, changefreq, priority, hreflang alternates, image/video counts and news metadata.
What you get
- Sitemap discovery from robots.txt, then common paths; sitemap indexes followed with depth and loop protection
- XML, gzip (detected by magic bytes) and plain-text URL lists; one row per unique URL across the whole run
- Regex include/exclude,
lastmodAfterand same-domain filters; optional HTTP status and redirect check
Price: $0.50 per 1,000 URLs + $0.00005 per runLogin needed: noRuns on: Apify (API, schedule, MCP)
How it works
For each start point (a domain, a homepage URL or a direct .xml, .xml.gz or .txt sitemap link) the Actor:
- Reads
robots.txtand collects itsSitemap:lines. If there are none, it tries common paths such as/sitemap.xmland/sitemap_index.xmluntil one works. - Downloads each sitemap over plain HTTP. Gzip is detected from the file's magic bytes, not its name. XML is parsed iteratively and DTD/entity declarations are rejected, so a hostile file cannot blow up memory.
- Follows sitemap indexes up to
maxDepth(default 5) with loop protection, and stops atmaxSitemapsPerSitefiles (default 500). - Normalizes and filters every
<loc>, then pushes one row per URL that is unique across the whole run, so overlapping start points are not billed twice.
A SUMMARY record lists, per start point, which sitemap files were found, which failed and why, and how many URLs were saved. It reads only what the site publishes for machines; it does not crawl HTML pages.
Sample output
A row from a real run on the Apify platform (run ilYbFWXlsMAqkqnTw, build 0.1.4, 2026-11-06) with two start points, https://www.sitemaps.org and https://docs.apify.com:
{
"url": "https://www.sitemaps.org/protocol.html",
"lastmod": "2022-12-15",
"changefreq": null,
"priority": null,
"imageCount": 0,
"videoCount": 0,
"newsTitle": null,
"newsPublicationDate": null,
"alternates": [],
"site": "https://www.sitemaps.org",
"sitemapUrl": "https://www.sitemaps.org/sitemap.xml",
"extractedAt": "2026-11-06T04:16:34.585233+00:00"
}
And a row from the second site, where priority and changefreq are set but lastmod is not:
{
"url": "https://docs.apify.com/api",
"lastmod": null,
"changefreq": "weekly",
"priority": 0.5,
"imageCount": 0,
"videoCount": 0,
"newsTitle": null,
"newsPublicationDate": null,
"alternates": [],
"site": "https://docs.apify.com",
"sitemapUrl": "https://docs.apify.com/sitemap_base.xml",
"extractedAt": "2026-11-06T04:16:34.215302+00:00"
}
The run's SUMMARY record, verbatim:
[
{
"site": "https://www.sitemaps.org",
"sitemapsFound": [
"https://www.sitemaps.org/sitemap.xml"
],
"sitemapsFailed": [],
"urls": 84,
"status": "done"
},
{
"site": "https://docs.apify.com",
"sitemapsFound": [
"https://docs.apify.com/sitemap.xml",
"https://docs.apify.com/sitemap_base.xml"
],
"sitemapsFailed": [],
"urls": 100,
"status": "done"
}
]
So the tool found the sitemap index at docs.apify.com through robots.txt and followed it into sitemap_base.xml, stopping at the 100-URL cap set for this test. sitemaps.org gave 84 URLs, all with lastmod; docs.apify.com gave 100, 0 with lastmod. 184 URLs in 9 seconds at 256 MB.
Output fields
| Field | Type | Meaning |
|---|---|---|
url |
string | Published page URL |
lastmod |
string / null | Normalized to ISO 8601 when parseable, otherwise kept as published |
changefreq, priority |
string / number / null | Publisher hints |
sitemapUrl |
string | The sitemap file that first listed the URL |
site |
string | Normalized start point |
imageCount, videoCount |
integer | Image and video sitemap entries for the URL |
newsTitle, newsPublicationDate |
string / null | Google News sitemap metadata |
alternates |
array | {hreflang, href} pairs; empty if none |
statusCode, redirectUrl |
integer / string / null | Only with checkStatus |
extractedAt |
string | UTC timestamp |
How much does it cost to extract sitemap URLs?
Pricing is per result: $0.50 per 1,000 URLs ($0.0005 per URL) plus $0.00005 per run, platform usage included. Error logs and the SUMMARY record are not billed.
Worked examples: the sample run saved 184 URLs, about $0.092. A 10,000-URL site is $5.00. Watching a news site for new pages with lastmodAfter set to yesterday and 300 new URLs a day costs about $0.15 a day. maxUrls and maxUrlsPerSite cap any run. Apify's free plan includes $5 a month for Store tools (apify.com/pricing, checked 2026-11-06), which covers 10,000 URLs.
Limits you should know about
- Only what is in the sitemaps. Pages the site does not list are invisible, and a JavaScript-only site with no sitemap returns nothing. This is an inventory of what the publisher declares, not a crawl.
lastmodis the publisher's claim. Many CMSs set it to the build time for every URL, so treat it as a hint, not proof of freshness.- No memory between runs. There is no stored diff;
lastmodAfterfilters by published date, which only works if the site maintainslastmodhonestly. - Size ceilings. Files are capped at 50 MB uncompressed (with gzip-bomb protection). Around one million unique URLs per run is a planning ceiling at 256 MB, not a guarantee; split inputs or raise memory for bigger sites.
- Filters are Python regular expressions, not globs, and
sameDomainOnly(on by default) drops URLs on other registrable domains, for example a sitemap hosted on a CDN that lists another domain's pages.
API: call it from code
curl -X POST "https://api.apify.com/v2/acts/echolab~sitemap-url-extractor/run-sync-get-dataset-items?format=csv" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"startUrls": ["https://www.sitemaps.org"], "maxUrlsPerSite": 0}'
Python, new blog posts since a date:
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("echolab/sitemap-url-extractor").call(run_input={
"startUrls": ["https://example.com"],
"includePatterns": ["/blog/"],
"excludePatterns": ["/tag/", "\\?"],
"lastmodAfter": "2026-11-01",
"maxUrlsPerSite": 0, # 0 = unlimited
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
print(row["lastmod"], row["url"])
Sitemap URL extractor vs. alternatives
| Option | Good for | Trade-off |
|---|---|---|
| This Actor | Many sites or big sitemap trees, scheduled, from an API or agent; no install | Pay per URL; sitemap-only, no HTML crawl |
| SEO crawlers such as Screaming Frog | Full HTML crawl, broken links, on-page audits; can also read sitemaps | Desktop software; the free version is limited to 500 URLs per crawl |
advertools (sitemap_to_df) or your own script |
Free, flexible, good for very large one-off inventories | You run and maintain it; scheduling, retries and storage are up to you |
| Online sitemap extractor pages | One small site, once | Manual copy-paste; usually no index following, filters or API |
When not to use it: if the site has no sitemap or you need pages it does not list, use a crawler. If you need million-URL inventories every week, a self-hosted script is cheaper than paying per URL. This tool fits repeatable inventories of many sites, or new-page monitoring, that should land in a spreadsheet or pipeline without running anything yourself.
Open the sitemap URL extractor on Apify for the full input schema.
Changelog
- 2026-11-06 — verified against sitemaps.org and docs.apify.com (sitemap index) in a production run; this page published.
- 2026-11-05 — first public release on the Apify Store with pay-per-result pricing ($0.50 per 1,000 URLs).