Developer FIELD NOTES · 4 MIN READ
Taking HTML Apart: Extracting Tags, Classes, and Attributes
How to inventory an HTML page: tag counts, classes and IDs, attribute values, why void elements make the counts unequal, and why not regex.
View-source shows you everything at once, which is another way of saying it shows you nothing in particular. When you want to know what a page is actually made of — which tags appear, how many, what classes the stylesheets are aiming at, which IDs exist — you want the parts list, not the document. Tag extraction turns markup into an inventory: counts, names, and the attribute values that bind the HTML to its CSS and scripts.
What an inventory counts
Feed the extractor a small document and it reports several tallies at once — opening tags, closing tags, comments, and whether a doctype is present. For this input:
<!DOCTYPE html>
<!-- header nav -->
<html>
<body class="page dark">
<div id="main" class="wrap">
<p>Hi</p><p data-x="1">Yo</p>
<img src="a.png" alt="A">
</div>
</body>
</html>
the numbers come back as 6 opening tags, 5 closing tags, 1 comment, and a doctype — with a per-tag breakdown: html 1, body 1, div 1, p 2, img 1. Each opening tag also comes with its raw source text and character position, so <body class="page dark"> is reported verbatim and can be located in the original file.
The count that is allowed to be unequal
Six opening tags, five closing tags, and the document is fine. The <img> element is void: HTML defines it as having no content and no closing tag, and the same goes for br, input, meta, link, hr, and friends. So an opening-versus-closing mismatch is not automatically an error — it is expected whenever void elements appear. What the mismatch is good for is spotting real sloppiness: a document with twelve <div>s and eleven </div>s has a structural bug, and the per-tag counts expose exactly which element is unbalanced.
Classes, IDs, and the attributes that matter
Classes and IDs are where HTML meets CSS and JavaScript, so the extractor treats them specially. A class attribute is split on whitespace — class="page dark" contributes two entries, page and dark — and the tool reports both the unique list (here page, dark, wrap) and how many times each appears, which is a quick way to see which style hooks a page leans on. IDs come back deduplicated, since they are supposed to be unique anyway. Beyond those, common attributes are collected with their values: src has a.png, alt has A, href, title, name, type, value, rel, and target fill out the set. Attribute values are HTML-decoded, so title="Tom & Jerry" is reported as Tom & Jerry — the way entities work is covered in How HTML Entities Work.
Why not just run a regex
The temptation is always "it's just find <(\w+)". It fails on the first realistic page: a quoted attribute value can contain a >, comments can contain complete fake tags, and <p data-x="1"> must yield p with attributes, not a tag named p-data-x. Regular expressions match flat text and cannot balance nesting — How Regular Expressions Work explains why nested structures are their hard limit. A small purpose-built scanner that walks the markup, skips comments, and tracks quoted attribute values gets all of these right because it is doing one narrow job.
Inventory, not validator
The tool answers "what is here", not "is this correct". Unbalanced counts, duplicate IDs, and classes nothing styles will all show up in the report — but as observations, not verdicts. That is the right contract for the common tasks: auditing which CSS hooks exist before refactoring styles, checking that a CMS-generated page emits the elements you expect, counting images for an accessibility pass, or diffing two pages' structure by comparing their inventories.
Try it yourself
Paste the sample document above into the HTML Tag Extractor and verify the counts — then paste a page from a real project and see how fast the class list grows. To see the opposite operation, the HTML to Text converter strips the tags and keeps the words, and the HTML Minifier shows what the same markup looks like once every removable byte is gone.