Sitemap Parsing and URL Discovery Strategies
Sitemaps find declared URLs, but miss orphaned pages and dynamic content entirely.

Sitemaps are the fastest way to find every page on a website. Fast and complete are not the same promise, and confusing the two is how discovery pipelines quietly fail in production. A sitemap is a declared inventory: the site owner made a list of URLs they want search engines to see and handed it over. That's useful. It's also not close to the whole story.
How the sitemap protocol works and where its hard limits create problems
A sitemap is XML with one required field, loc, and three optional ones: lastmod, changefreq, and priority. List the URLs, maybe say when they last changed, hand the file to whatever crawler asks for it.
The protocol has two hard ceilings: 50 MB uncompressed, and 50,000 URLs per file. Cross either one and nothing fails loudly. A search engine, or any parser following spec, truncates the file without an error message. What you get instead is partial coverage that looks complete right up until someone notices half the product catalog never showed up in search results.
The fix built into the protocol is the sitemap index: a file that points to other sitemap files instead of listing URLs directly. Index files can reference up to 50,000 child sitemaps, so the theoretical ceiling runs into the billions of URLs. In practice, most sites hit the 50,000-URL limit long before they'd ever touch the 50 MB size limit. So the right move is splitting sitemaps by content type or date, by products, blog posts, monthly archives, well before either ceiling is in sight. Waiting until the line gets crossed is how truncation happens in the first place.
Gzip compression cuts transfer size by 70 to 90%, which helps bandwidth. It does nothing for the 50 MB calculation, because that limit applies to the uncompressed file. Don't assume compression buys extra headroom. It doesn't.
On lastmod, changefreq, and priority: Google has said flatly that it ignores changefreq and priority. It uses lastmod, but only on sites where the field has proven accurate over time. Treat lastmod as a signal worth checking, useful for verification but too fragile for critical logic.
Here's where a lot of pipelines get tripped up. The spec says a sitemap should only list canonical URLs, only URLs returning a 200 status, nothing marked noindex. That rule gets broken constantly, and the single most common finding in sitemap audits is a declared sitemap URL that returns a 404. That's worse than having no sitemap at all. It burns a crawler's request and hands back nothing.
Finding sitemaps before parsing them: robots.txt, common paths, and HTML scanning
Before parsing anything, a pipeline has to find the sitemap. Run three methods together rather than picking one.
Start with robots.txt. Every major crawler checks it first, and the Sitemap: directive inside it is the canonical declaration, the site owner pointing straight at their file. Read this before anything else. It usually carries a Crawl-delay directive too, and that matters more than it sounds: respecting it is a basic courtesy that keeps a crawler from hammering a server harder than the site expects.
If robots.txt stays quiet on sitemaps, or names one but there might be others, probe common paths. More than 15 well-known locations are worth checking: /sitemap.xml, /wp-sitemap.xml, /sitemap_index.xml, plus assorted CMS-specific variants. Think of it as checking under the doormat after knocking on the front door.
Last resort: scan the homepage HTML for <a> and <link> tags referencing a sitemap. This catches the odd case where the first two methods find nothing, which happens more often than it should on older or custom-built sites.
Once a sitemap index turns up, expand it recursively. A tool that only reads the top-level index and stops will miss whatever's nested underneath, and on a large site that can be most of the actual content. After fetching each file, validate it: confirm it's real XML, check for a clean 200 status rather than a redirect chain that quietly lands on the homepage, and check the content-type header. A sitemap URL that redirects somewhere else entirely is a failure the status code alone won't show.
One more step before any of this moves downstream: dedupe. The same URL shows up across multiple sitemaps in an index constantly, and passing duplicates into a monitoring or ingestion pipeline just burns compute later for nothing.
What comes out of this phase is a full declared inventory, each URL tagged with its lastmod, changefreq, priority, and the specific sitemap it came from. Cheap, fast, low-bandwidth. And structurally incomplete, for reasons that have nothing to do with how well the parser was built.
What sitemap parsing misses and why a single-method pipeline fails at production scale
Even well-built, purpose-made sitemap tools fail at rates that should worry anyone relying on just one. In an August 2026 comparison of the first 20 sitemap-related tools on a major scraping marketplace, the market leader posted a 17.7% failure rate across 1,241 runs in 30 days, 220 failed outright. That's not an edge case. That's roughly one run in six.
The causes are mundane, and every one of them is avoidable with decent engineering: rate-limit responses (429s) with no retry logic, gzip files that never get decompressed, sitemap indexes pointing at child files that 404, sitemaps with hundreds of thousands of URLs getting loaded entirely into memory until something runs out of room and crashes.
But even a flawless parser only surfaces what's declared, and plenty of real content never gets declared at all.
Orphaned pages are the big one: pages that exist, that are live and reachable, but aren't linked from navigation and never made it into any sitemap. Per Ahrefs, roughly 23% of the average website's pages fall into this bucket. A sitemap-only pipeline will never see them. Not occasionally. Never, by design.
Canonical filtering creates a related gap. Non-canonical URLs get excluded from sitemaps on purpose, per spec. But non-canonical doesn't mean inactive. Those pages can still be live and serving traffic, and a pipeline that only trusts the sitemap won't account for them.
Then there's everything dynamic: content generated by JavaScript routing, infinite scroll, API-driven pagination. None of it gets statically declared anywhere, because it doesn't exist until the browser runs the code. Single-page applications are the clearest case: a static XML parser sees the shell URL, one entry point, and nothing about the dozens or hundreds of routes the app actually serves once JavaScript takes over.
Regional and personalized content adds one more layer. Sites serving different URL spaces based on geo-IP or login state can have entire sections no sitemap anywhere declares, because the sitemap was generated for one version of the site while the rest lives in a parallel space the crawler never gets shown.
None of this means sitemaps are useless, they're just the on-ramp toward the destination. Treat one as the finish line and the gaps above become blind spots nobody budgeted for.
Layering robots.txt parsing, recursive index traversal, and link extraction into a complete discovery pipeline
A production-grade pipeline runs three phases in sequence, each picking up where the last left off.
Phase 1 is the baseline. Fetch and parse robots.txt, pull every Sitemap: directive, note the Crawl-delay. Probe common paths for anything undeclared. Recursively traverse every sitemap index to whatever depth it goes, collecting every <loc> entry with its metadata. Then run a static HTML crawl from seed URLs: follow <a href> links, read canonical tags, follow rel="next" / rel="prev" pagination chains. This phase is cheap. No browser rendering, just declared and statically linked content.
Phase 2 brings in JavaScript. Any URL from Phase 1 that looks like an SPA entry point gets rendered in an actual browser, so client-side routes become visible. Parse the rendered DOM for additional links, XHR and fetch calls hitting internal APIs, and GraphQL endpoints generating URLs on demand. Interact with pagination controls, infinite scroll triggers, and nav menus that only render after JavaScript runs. This phase costs more, but it's where the SPA gap actually closes.
Phase 3 fills what's left. Cross-reference the Phase 1 and Phase 2 URL sets and look for candidate orphan paths through pattern matching. Sequential or patterned URLs discovered in Phase 1 can suggest candidate paths worth probing even if no sitemap ever mentioned them. Run geotargeted passes where regional variants matter. Then validate everything with HEAD requests: confirm 200s, flag redirects, drop 404s before any of it moves downstream.
A handful of engineering details separate a pipeline that scales from one that falls over on a big site. Streaming XML parsing keeps memory flat instead of loading a large sitemap file wholesale. Exponential backoff on 429s means a rate limit slows the run instead of killing it. Concurrency limits that respect Crawl-delay keep the crawler from getting blocked mid-discovery. And deduplication has to span all three phases together, running as a single unified pass across them.
lastmod earns its keep here too: URLs with recent lastmod values are good candidates to prioritize for re-fetching, a cheap signal compared to re-crawling an entire site just to see what changed.
What comes out the other end is a validated, deduplicated URL set with full source attribution, which sitemap, which phase, which method found it. That's the foundation everything downstream builds on.
Turning the discovered URL set into a change-monitoring feed
Run the same pipeline on a schedule, diff the current URL set against the last snapshot, and three event types fall out automatically: new, updated, removed.
New URLs are content published since the last run, and they're the top priority for ingestion into a RAG pipeline or a competitive monitoring feed. Updated lastmod values flag pages that changed, worth re-fetching to refresh embeddings or cached copies. Removed URLs mean content got taken down or reorganized, and any downstream index still pointing at them is now serving dead links.
The efficiency case is straightforward: reading XML metadata to catch changes costs a fraction of a full re-crawl, in both bandwidth and compute. But lastmod accuracy still varies by site (the same caveat from earlier applies here), so a HEAD request to confirm the change before triggering a full re-fetch is worth the extra round trip.
Monitoring pipelines tend to break in one specific, predictable way. A site migrates, the CMS changes, the CDN changes, and the sitemap regenerates at a new path while robots.txt still points at the old one. Nobody updates the pointer. The monitoring job keeps running, keeps returning "no changes," and nobody notices until someone manually checks the site and finds a pile of new content the pipeline never saw. Migrations, deploys, and platform moves are the highest-risk moments for exactly this kind of silent breakage. Checking sitemap health on deploy day beats waiting for the next scheduled audit, every time.
A few conditions are worth alerting on directly: any declared sitemap returning non-200, a URL count that drops past a set threshold (a site that normally declares 4,000 pages suddenly showing 61 has a generation bug, a signal distinct from a content purge), and lastmod dates that simply stop advancing across the board.
Structuring discovered URLs and their content for AI pipelines and RAG ingestion
The full pipeline runs: crawl the sitemap, discover URLs, fetch each page, convert to clean Markdown, chunk it, embed it. Everything downstream depends on how complete that first step was. Miss 23% of a site's pages in discovery, and that 23% doesn't exist for the model, no matter how good the embedding step is.
Format matters more than people assume. Markdown cuts token count by 60 to 80% compared to raw HTML while keeping the document's actual structure. A typical documentation page runs upward of 8,000 tokens as raw HTML and shrinks to around 1,200 tokens as clean Markdown. Same content, a fraction of the cost.
Converting HTML to Markdown isn't the whole job, though. Getting a page genuinely ready for an LLM means going further:
- Content separation. The article is not the page. Nav bars, ad slots, cookie banners, footer links need to get stripped out entirely, kept separate from the actual content instead of bundled into the same Markdown blob.
- Semantic typing. A price written as "$49.99" inside a wall of Markdown text is just a string, ambiguous to a model. A structured field with an amount and a currency code isn't. Matters a lot for anything doing product or pricing intelligence.
- Header-aware chunking. Splitting at
##or###boundaries keeps each chunk self-contained, heading and content staying together, instead of severing a header from the paragraph it's supposed to introduce.
The stakes here aren't abstract. Research into legal AI tools found that Lexis+ AI and Ask Practical Law AI produced incorrect information more than 17% of the time on legal queries. Feed a model incomplete or badly structured source content and the result isn't a cosmetic problem, it's an accuracy problem, and it shows up in exactly the kind of failure rate that study measured.
Vision-based extraction, taking screenshots and reading them, doesn't hold up at scale. By 2026, high-volume RAG pipelines lean on text-based extraction with semantic structuring instead, mostly for token efficiency and cost. That shift from raw HTML to properly structured Markdown can cut token costs by as much as 90%, and that adds up fast for any team processing URLs at real volume.
The pattern here is the same one from the start of the pipeline. Discovery, structure, and validation aren't separate concerns bolted onto a scraper. They're the actual product. An API handing back clean Markdown, HTML, or JSON through one endpoint is only as good as the URL discovery feeding it, and no amount of clever chunking downstream fixes a page that was never found in the first place.


