HTMLLoader & HTMLStringLoader
On this page
Module: html_loader
HTMLLoader
Extends BaseFileLoader
Overview
Loads .html/.htm/.xhtml files, extracting clean text via BeautifulSoup along with page metadata (title, meta description, Open Graph tags, language) and optionally links and tables as separate documents.
Signature
class HTMLLoader(BaseFileLoader):
SUPPORTED_EXTENSIONS = [".html", ".htm", ".xhtml"]
def __init__(
self,
file_path: str,
config: Optional[HTMLLoaderConfig] = None,
encoding: str = "utf-8",
): ...
def load(self) -> List[LoadedDocument]: ...
Parameters
Returns
List[LoadedDocument] — at least one document (the main page text) plus one additional document per <table> found, if config.extract_tables is True (the default).
Raises
FileNotFoundError/ValueError— at constructionImportError—"beautifulsoup4 is required..."ifbs4isn't installedIOError—"Cannot read HTML file: ..."wrapping any exception from reading the file
Internal workflow — _parse_html(html, source)
- Parse with
BeautifulSoup(html, config.parser). - Extract page metadata from
<head>before removing any tags (_extract_page_metadata):<title>,<meta name="description"|"author"|"keywords">, Open Graph tags (og:title,og:description,og:url→ stored asog_titleetc.), and<html lang>/xml:lang. - Remove unwanted tags (default:
script,style,nav,footer,header) via.decompose(). - Extract text:
- If
config.tags_to_extractis set: concatenateget_text()from each matching tag, joined by"\n\n". - Else: extract all text from
<body>(or the whole soup if no<body>), joined by"\n".
- If
- Collapse 3+ consecutive newlines to 2.
- Build the main document's metadata, merging in the page metadata from step 2 plus
char_count. - If
config.extract_links: collect{"text", "href"}for every<a href>intometadata["links"]of the main document. - If
config.extract_tables: call_extract_tables()and append each table'sLoadedDocumentto the result list.
_extract_tables(soup, source)
For each <table> (in document order), builds rows by joining cell text (<td>/<th>) with " | ", skipping rows where every cell is empty. Skips the whole table if it produces no non-empty rows. Each table document's metadata includes element: "table" and table_index (0-indexed).
Metadata produced (main document)
title, description, author, keywords (if present in <meta>), og_title/og_description/og_url (if present), language (if lang attribute found), char_count, links (if extract_links=True), plus base file metadata.
Examples
Basic:
from zerda.document_loaders import HTMLLoader
docs = HTMLLoader("index.html").load()
print(docs[0].metadata["title"])
Extract specific content regions only:
from zerda.document_loaders import HTMLLoader, HTMLLoaderConfig
config = HTMLLoaderConfig(tags_to_extract=["article", "main"])
docs = HTMLLoader("blog_post.html", config=config).load()
Disable table extraction (treat as plain text only):
config = HTMLLoaderConfig(extract_tables=False)
docs = HTMLLoader("page.html", config=config).load()
assert len(docs) == 1
Extract links for a crawler-style pipeline:
config = HTMLLoaderConfig(extract_links=True)
docs = HTMLLoader("sitemap_page.html", config=config).load()
for link in docs[0].metadata["links"]:
print(link["href"])
Production usage — keep only article body, drop nav/footer/aside noise:
config = HTMLLoaderConfig(
tags_to_remove=["script", "style", "nav", "footer", "header", "aside", "form"],
)
docs = HTMLLoader("article.html", config=config).load()
Best practices
- Use
tags_to_extractto scope extraction to known content containers (<article>,<main>, a specificid/class wrapper if you pre-filter the HTML) when source pages have heavy chrome/boilerplate. - Leave
extract_tables=True(default) for documents where tabular data carries meaningful information — each table becomes its own retrievable unit, which is usually better for RAG than embedding it inline with surrounding prose.
Common mistakes
- Expecting
tags_to_removeto also apply whentags_to_extractis set — removal happens before extraction regardless, so it's always in effect, but if yourtags_to_extractlist includes a tag that's also slated for removal (unlikely in practice, but possible with custom configs), the removal happens first and nothing will be found for that tag. - Forgetting that table documents are separate
LoadedDocuments, not embedded in the main document's text — if you only consumedocs[0], you'll miss table content.
HTMLStringLoader
Extends BaseDocumentLoader directly (not BaseFileLoader — there is no file to validate).
Overview
Loads HTML from an in-memory string rather than a file. Useful for content fetched from APIs, scraped via another tool, or generated dynamically — anywhere you have HTML text but not (and don't want) a file on disk.
Signature
class HTMLStringLoader(BaseDocumentLoader):
def __init__(
self,
html_content: str,
source: str = "html_string",
config: Optional[HTMLLoaderConfig] = None,
): ...
def load(self) -> List[LoadedDocument]: ...
Parameters
Internal workflow
Delegates entirely to a private internal helper class _HTMLParser (a subclass of HTMLLoader that bypasses file validation by calling BaseDocumentLoader.__init__ directly and setting self.file_path = None), reusing HTMLLoader._parse_html() for all actual parsing logic. This avoids duplicating the extraction algorithm between file-based and string-based HTML loading.
Returns
Same shape as HTMLLoader.load() — main text document plus table documents if configured.
Examples
Basic:
from zerda.document_loaders import HTMLStringLoader
html = "<html><body><h1>Hello</h1><p>World</p></body></html>"
docs = HTMLStringLoader(html, source="https://example.com").load()
Processing scraped content (this is exactly how WebLoader uses it internally):
import requests
from zerda.document_loaders import HTMLStringLoader, HTMLLoaderConfig
response = requests.get("https://example.com/article")
docs = HTMLStringLoader(
response.text,
source=response.url,
config=HTMLLoaderConfig(extract_tables=True),
).load()
Thread safety
Both classes hold no shared mutable state across instances; parsing a string or file is self-contained per call. Safe to use concurrently across different instances (e.g. via DirectoryLoader or a custom thread pool).
Related APIs
HTMLLoaderConfigWebLoader— fetches a URL then delegates toHTMLStringLoaderinternallyTableExtractorLoader— a dedicated alternative for HTML table → Markdown extractionLoadedDocument
.