What is the Document Object Model?
The Document Object Model is the in-memory representation a browser builds from an HTML document. Every element becomes a node in a tree, with parents, children and siblings, and the tree exposes an interface that JavaScript can read and modify while the page is open.
The word doing the work in that definition is modify. The DOM is not a copy of the HTML that arrived. It is a live structure that begins as that HTML and then changes.
The gap that catches every scraper once
This is the single most common source of confusion in web scraping, and it is worth being explicit about.
Open developer tools and inspect an element. What you see is the current DOM, after every script has run, every fetch has resolved and every framework has rendered. Copy a selector from there, put it in a script that requests the same URL, and get nothing back.
Nothing is broken. Your script received the original HTML, which on a client-rendered site may be a near-empty shell with a <div id="root"> and a bundle of JavaScript. The content you inspected was constructed afterwards, in the browser, and never existed in the response your parser read.
To see what your scraper sees, view source rather than inspect, or disable JavaScript and reload. If the content vanishes, it is built client-side.
When you need the rendered DOM
You need a rendered DOM when the content only exists after scripts run, when it appears after an interaction such as a click or a scroll, or when it arrives from a network call the page makes after loading.
You do not need it when the content is already in the initial HTML, which is still true of a great many pages, including plenty that feel dynamic. Server-side rendered frameworks put the content in the source precisely so that crawlers can read it.
Checking before reaching for a browser is worth the ten seconds. Rendering is the most expensive thing you can do per page, and doing it by reflex is the usual reason a large crawl costs more than it should.
A third option people miss
If a page loads its data from an internal JSON endpoint, you can often request that endpoint directly. The network tab shows what the page calls. The response is already structured, no rendering is needed, and the endpoint's shape changes far less often than the markup does. When it is available, this is usually the best of the three routes.
Where Zenrows fits
JavaScript rendering runs the page in a headless browser and returns the rendered DOM, so your selectors match what developer tools showed you. Where the content arrives late, wait_for holds until a specific selector exists rather than guessing at a fixed delay, and JSON response captures the XHR and fetch calls the page made, which is how you find that internal endpoint in the first place.
Go deeper on the blog
In the docs
Last updated: Aug 16, 2026