← All posts

What Is HTML Parsing? A Plain-English Guide to Parsing HTML

Quick answer: HTML parsing is the process of reading a raw HTML string and building a structured, navigable tree of nodes (elements, attributes, and text) that your code can query and traverse. Instead of hunting through a wall of angle brackets with string matching, you ask the tree questions like "give me every link inside the main article" using CSS selectors or XPath. Every browser does this, and every scraper does a smaller version of it.

The core idea: from a string to a tree

When a server sends you a page, you get one long string of characters. It looks like this:

<article>
  <h1>Coffee Methods</h1>
  <ul class="methods">
    <li data-id="1">Pour over</li>
    <li data-id="2">French press</li>
  </ul>
</article>

To a human that reads as a nested structure. To a computer it is just text until something imposes structure on it. A parser is the thing that imposes that structure. It scans the characters, recognizes tags, matches opening tags to closing tags, handles nesting, and produces a tree where <article> is a parent node, <h1> and <ul> are its children, and each <li> is a child of the <ul>.

Once that tree exists, questions become easy. "What is the text of the first heading?" is a single lookup. "Which list items have a data-id attribute?" is a filter over nodes. None of that requires you to think about angle brackets anymore.

Why not just use regular expressions?

This is the question everyone asks, so let me answer it directly: regex is the wrong tool for parsing HTML, and it will hurt you.

Regular expressions describe flat patterns in text. HTML is a nested, recursive structure with optional closing tags, attributes in any order, comments, CDATA, and forgiving browser rules that let malformed markup still render. A regex that grabs <a href="..."> breaks the moment someone writes <a class="x" href='...'> or nests a tag you did not anticipate. You end up patching the pattern forever.

A real parser already knows the rules of HTML, including the messy real-world ones. Use regex for small, well-defined extractions inside a single text node (a phone number, a date). Use a parser for anything that involves the document structure.

Approach Good for Breaks on
Regex / string matching Simple patterns inside one string Nesting, attribute order, malformed markup
HTML parser Structure, selectors, traversal Almost nothing (parsers are lenient by design)

DOM vs SAX: two ways to parse

There are two broad parsing models, and knowing the difference helps you pick a tool.

DOM parsing builds the entire tree in memory, then hands it to you. You can walk up, down, and sideways, query anything, and revisit nodes freely. This is what you want for scraping and most extraction work because the whole document is available at once. The cost is memory: the full tree lives in RAM.

SAX parsing (and the broader family of streaming or event-based parsers) does not build a tree. It reads the document top to bottom and fires events as it goes: "start element li", "text node", "end element li". You react to those events. It uses almost no memory and handles enormous documents, but you cannot look backward or query the tree because there is no tree.

Model Memory Random access Best for
DOM Whole tree in RAM Yes Scraping, extraction, most tasks
SAX / streaming Tiny No Huge files, one-pass processing

For day-to-day web scraping, you almost always want DOM-style parsing. Reach for streaming only when documents are too large to hold in memory.

Selectors: how you query the tree

Once you have a tree, you need a query language to find nodes. The two standards are CSS selectors and XPath.

CSS selectors are the same syntax you use to style pages. article ul.methods li reads as "every li inside a ul with class methods inside an article." They are compact and easy to read for most jobs. Our CSS selectors for web scraping guide covers the full set.

XPath is a more powerful path language that can walk in directions CSS cannot, including selecting a parent from a child or matching on text content. When a page has no clean classes or IDs to anchor on, XPath earns its keep. The XPath cheat sheet has the patterns worth memorizing.

A quick comparison of the same query in both:

CSS:   article ul.methods li[data-id]
XPath: //article//ul[@class='methods']/li[@data-id]

Both select the list items with a data-id attribute. Use CSS for readability, XPath when you need its extra reach.

Common parsers by language

Every language has mature parsers. Here are the ones people actually use.

Language Parser Selector support
Python BeautifulSoup, lxml, selectolax CSS, XPath (lxml)
JavaScript Cheerio, jsdom, parse5 CSS
Ruby Nokogiri CSS, XPath
Go goquery, net/html CSS
Java jsoup CSS
PHP DOMDocument, Symfony DomCrawler CSS, XPath

For Python specifically, our BeautifulSoup tutorial is the fastest way to get productive, and the wider Python web scraping libraries roundup compares the options.

A working example

Here is HTML parsing end to end in Python. We fetch a page, parse it into a tree, and query the tree with a CSS selector.

import requests
from bs4 import BeautifulSoup

html = requests.get("https://example.com").text

# Parse the raw string into a navigable tree.
soup = BeautifulSoup(html, "html.parser")

# Query the tree with CSS selectors, not string matching.
for item in soup.select("article ul.methods li"):
    print(item.get("data-id"), item.get_text(strip=True))

The important line is BeautifulSoup(html, "html.parser"). That is where the string becomes a tree. Everything after it is a query against structure, which is exactly what makes parsing better than string hacking.

Where parsing gets hard

Two things break naive parsing.

First, JavaScript-rendered pages. If the content you want is injected by client-side scripts, the HTML you download will not contain it. The tree you parse is missing the data. You need a real browser to run the scripts first, then parse the resulting DOM.

Second, inconsistent markup at scale. A parser handles messy HTML fine, but selectors you wrote for one page layout break when a site ships a redesign or serves regional variants. Maintaining hundreds of selectors across many sites is where scraping projects quietly rot.

This is the gap link.sc fills. You send a URL and it handles rendering (running JavaScript in a real browser pool), parsing, and cleanup server-side, then returns clean Markdown or JSON. You skip the parser-plus-selector maintenance entirely:

curl -X POST https://api.link.sc/v1/fetch \
  -H "x-api-key: lsc_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "format": "markdown"}'

The response contains a content field with the parsed, readable text. See the docs for the JSON output mode when you need structured fields instead of prose.

Ethics note

Parse responsibly. Read a site's robots.txt, respect rate limits, only collect public data, and never use parsing to get around authentication or paywalls. HTML parsing is a neutral tool: the responsibility for using it well is yours.

The short version

HTML parsing converts raw markup into a tree you can query with CSS selectors or XPath. Use a real parser, not regex. Choose DOM-style parsing for scraping and streaming for giant files. Every language has a solid parser, and when rendering and selector maintenance get painful, a managed fetch API removes that work for you.


Want parsed, LLM-ready content without maintaining a single selector? Try link.sc free.