z3l.FastHtmlParser 1.1.2

dotnet add package z3l.FastHtmlParser --version 1.1.2
                    
NuGet\Install-Package z3l.FastHtmlParser -Version 1.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="z3l.FastHtmlParser" Version="1.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="z3l.FastHtmlParser" Version="1.1.2" />
                    
Directory.Packages.props
<PackageReference Include="z3l.FastHtmlParser" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add z3l.FastHtmlParser --version 1.1.2
                    
#r "nuget: z3l.FastHtmlParser, 1.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package z3l.FastHtmlParser@1.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=z3l.FastHtmlParser&version=1.1.2
                    
Install as a Cake Addin
#tool nuget:?package=z3l.FastHtmlParser&version=1.1.2
                    
Install as a Cake Tool

z3l.FastHtmlParser

Fast, low-allocation HTML / XML parser for scraping and data extraction on .NET 9 and .NET 10.

Website: https://romanti3172.github.io/z3l.FastHtmlParser/ · NuGet: https://www.nuget.org/packages/z3l.FastHtmlParser

  • Fast parsing. About 10x faster than HtmlAgilityPack and AngleSharp on typical pages, up to 33x on large ones, with 12–23x less memory (see the benchmarks); a 50 KB page parses in about 60 μs with a single exact-size list of tags.
  • No allocations when you match tags or attributes. Use HtmlTag / HtmlAttribute enums (about 250 values each: HTML, SVG, MathML, RSS, Atom, sitemap) or plain strings.
  • HTML5 entities. All 2125 named references, legacy names without ;, numeric references — decoded the way browsers do.
  • Text extraction. Text() gives clean text; TextLineBreaks() keeps the visible line structure (<br>, <p>, <li>, <div>...).
  • Null-safe chains. Every method accepts null and returns null / empty, so a chain never throws when something is missing.
  • Thread-safe reads. A parsed document is read-only; query it from any number of threads.

Install

dotnet add package z3l.FastHtmlParser

Quick start

using z3l.FastHtmlParser;

var doc = new HtmlParser(html, "https://example.com/catalog/");

// first match
var title = doc.Find(HtmlTag.H1).Text();
var price = doc.Find(HtmlTag.Span, cls: "price").Text();

// chains never throw: a missing tag gives null at the end
var phone = doc.Find(HtmlTag.Div, id: "contacts")
               .Find(HtmlTag.A, cls: "phone")
               .AttributeHrefTelephone();

// all matches; links are made absolute against the page URL
foreach (var a in doc.Find(HtmlTag.Div, cls: "gallery").FindAll(HtmlTag.A))
{
    Console.WriteLine(a.AttributeHref());
}

Finding tags

Find returns the first match, FindAll all matches (lazily), Is checks one tag. They work on the whole document or inside a tag (tag.Find(...) searches its descendants).

doc.Find(HtmlTag.Table);
doc.Find(HtmlTag.Li, cls: "item");                        // one of the space-separated classes
doc.Find(HtmlTag.Div, id: "main");
doc.Find(HtmlTag.Dt, text: "Price");                      // visible text: tags removed, entities decoded
doc.Find("custom-element");                               // names that are not in HtmlTag

// any combination of criteria
doc.Find(new TagQuery(HtmlTag.Meta) { Attribute = HtmlAttribute.Property, AttributeValue = "og:title" });
doc.FindAll(new TagQuery(HtmlTag.Img) { AttributeName = "data-lazy", AttributeValue = "true" });
doc.Find(new TagQuery { IdStartsWith = "product-" });     // any tag
tag.Is(new TagQuery(HtmlTag.Li) { Class = "item", TextStartsWith = "Price" });

TagQuery criteria: Class, Id, IdStartsWith, Attribute / AttributeName + AttributeValue, Text, TextStartsWith, Body, BodyStartsWith, Source. Null criteria are not checked. A query is a struct: searching allocates nothing, and the name is compared first with one integer comparison.

using z3l.FastHtmlParser.Shortcuts;

doc.FindByNameClass(HtmlTag.Span, "price");
doc.FindByNameText(HtmlTag.Dt, "Price").NextSiblingByName(HtmlTag.Dd).Text();
doc.FindAllByNameAttribute(HtmlTag.Img, HtmlAttribute.DataSrc, "...");
doc.FindByNameItemprop(HtmlTag.Span, "price");
tag.IsNameClass(HtmlTag.Div, "card");
tag.ParentByName(HtmlTag.Table);
tag.AttributeTitle();

Every shortcut is a one-line call of the core API above; they live in their own namespace, so the core API stays small unless you import them.

Reading a tag

tag.Name;              // "div"
tag.Text();            // visible text, entities decoded
tag.TextLineBreaks();  // text with line breaks: <br>, block elements → new line, <p> → paragraph
tag.Body();            // inner HTML
tag.FullSource();      // outer HTML
tag.AttributeHref();   // absolute URL
tag.AttributeByName(HtmlAttribute.Content);   // decoded value
tag.AttributeBody(HtmlAttribute.Content);     // value as written in the source
tag.Parent();
tag.Parent(HtmlTag.Table);                            // nearest enclosing <table>
tag.Parent(new TagQuery(HtmlTag.Div) { Class = "card" });
tag.Children();                                       // direct children
foreach (var descendant in tag) { }                   // all tags inside
tag.NextSibling();
tag.NextSibling(HtmlTag.Dd);                          // <dt>Price</dt><dd>100</dd>
tag.PreviousSibling();
tag.Next(HtmlTag.Div);                                // first <div> after this tag

Wherever a TagQuery is expected, a plain HtmlTag or a tag name works too: it means "a tag with this name" (tag.NextSibling(HtmlTag.Dd), tag.NextSibling("dd"), tag.Parent("my-widget")).

Example: a definition list.

var price = doc.Find(HtmlTag.Dt, text: "Price").NextSibling(HtmlTag.Dd).Text();

Text helpers for raw HTML

HtmlText.Decode("Q&amp;A &copy; 2026");            // "Q&A © 2026"
HtmlText.ToText("<b>Hello</b>   world");           // "Hello world"
HtmlText.ToTextWithLineBreaks("<p>One</p><p>Two</p>");

Parsing rules

The parser builds a flat list of tags, not a full DOM, and follows the HTML rules that matter for scraping:

  • `` are single HtmlTag.Comment tags; markup inside them is ignored.
  • The content of script, style, textarea, title, iframe, xmp, noembed, noframes is text, not markup.
  • Void elements (img, br, meta, link, input, hr, ...) and <!doctype> never contain other tags.
  • Text(), IsText and TextLineBreaks() return the visible text: comments and the content of script, style, noscript, template, noframes, noembed are skipped (Text() of a <script> itself still gives its code).
  • Optional end tags work as in HTML5: a new <li> ends an open <li>, <td>/<tr> end open cells and rows, <dt>/<dd> and <option> end their open siblings, a block element (<div>, <ul>, <table>, ...) ends an open <p>.
  • Tags left open are ended by the end tag of an enclosing element (<li>one<li>two</ul>) or by the end of the document, so a truncated page keeps its text.
  • < starts a tag only before a letter, /, ! or ?: in a < b it is text. Stray end tags (x</span>) are not text.
  • Quoted attribute values may contain < and >.
  • A document that starts with <?xml (RSS, Atom, sitemap, XHTML) is parsed by XML rules: an element is closed only by its end tag or />, so an RSS <link> keeps its content.
  • Attribute values are returned decoded, as a browser reads them (title="It&#39;s" → It's), and Is* / Find* compare decoded values. AttributeBody returns the value exactly as written. Links (AttributeHref, AttributeSrc) are not decoded, only resolved against the page URL with &amp;, %3f, %3d unescaped.

On the test pages and on a set of broken-markup snippets (left-out end tags, stray end tags, truncated pages) the parser finds the same elements, texts, attribute values and parents as AngleSharp, apart from the trees a browser builds implicitly (an inserted <tbody>, an ignored second <body>).

Tests and benchmarks

dotnet test
dotnet run -c Release --project benchmarks/z3l.FastHtmlParser.Benchmarks -- --filter '*'

The tests run on saved pages of quotes.toscrape.com, books.toscrape.com and webscraper.io/test-sites and compare every page with AngleSharp. The benchmarks compare parsing and a complete scraping task with AngleSharp and HtmlAgilityPack; see benchmarks/README.md.

books.toscrape.com, 51 KB: parse + extract 20 books Mean Allocated
z3l.FastHtmlParser 71 μs 45 KB
HtmlAgilityPack 862 μs (12x) 1,038 KB (23x)
AngleSharp 1,160 μs (16x) 707 KB (16x)

License

Free for individuals and organizations with annual revenue below USD 1,000,000 and fewer than 100 employees, including commercial use. Organizations with annual revenue of USD 1,000,000 or more, or with 100 or more employees, need a commercial license: a one-time payment of USD 1,000 per organization, perpetual, all versions. See LICENSE.md for the full terms and pricing to buy a commercial license.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.2 0 11/10/2026
1.1.1 54 11/8/2026
1.1.0 59 11/7/2026
1.0.0 58 11/7/2026
0.1.0 54 11/7/2026