From html2text

html2text latest releasehtml2text supported Pythonshtml2text licensehtml2text monthly downloadshtml2text total downloadshtml2text GitHub starshtml2text last commit

html2text converts HTML to Markdown. It subclasses the standard library HTMLParser and streams tokens through a large, well-worn option surface: list markers, emphasis and strong markers, link styles (inline or reference), image handling, table rendering, line wrapping at body_width, Unicode folding via its UNIFIABLE table, and a google_doc mode that reads the inline CSS a Google Docs HTML export carries. It ships a command-line converter alongside the library and is a long-standing default for turning arbitrary web HTML into readable Markdown in scrapers, mail-to-text pipelines, and documentation tooling.

turbohtml covers the same conversion through to_markdown(), a single fully typed method on the parsed WHATWG tree. It walks the tree in C instead of driving a Python HTMLParser, and groups the flat html2text options into named config dataclasses so each concept has one name.

turbohtml vs html2text

Dimension

turbohtml

html2text

Scope

Full WHATWG HTML parser and tree with Markdown, plain-text, and HTML serialization

HTML-to-Markdown conversion only

Feature breadth

Same Markdown option surface (lists, inline marks, links, images, tables, wrapping, code, Google Docs mode) plus text extraction, selectors, and sanitization on the shared tree

Broad, mature Markdown-conversion options including google_doc and CLI flags

Performance

C tree walk, roughly 50x faster on the benchmark below

Pure-Python HTMLParser subclass

Typing

Fully type annotated, config via frozen dataclasses

Untyped public API

Dependencies

Compiled C extension (prebuilt wheels)

Pure Python, no compiled dependency

Maintenance

Actively developed

Long-standing, widely deployed, stable

Feature overlap

Port these html2text calls 1:1 onto to_markdown() with a Markdown config:

  • html2text.html2text(text) -> turbohtml.parse(text).to_markdown().

  • List markers, emphasis/strong markers, and quote characters (ul_item_mark, emphasis_mark, strong_mark, open_quote, close_quote).

  • Link handling: ignore, skip-internal, inline vs reference style, and baseurl prefixing.

  • Image handling: markdown, alt-only, ignore, or raw HTML, plus a default alt string.

  • Table rendering: markdown, strip, or raw HTML, with column padding.

  • Line wrapping at a width, for list items and links.

  • Unicode folding (unicode_snob), code marking and block style, single-line-break spacing, aggressive escaping.

  • The google_doc inline-CSS mode and its list-indent step.

What turbohtml adds

  • One parsed tree serves Markdown, layout-aware plain text (to_text()), and HTML (serialize()); html2text is Markdown-only.

  • Convert any subtree by calling to_markdown() on a selected element (doc.find("article").to_markdown()) instead of pre-slicing the HTML string.

  • Full spec-compliant WHATWG parsing feeds the conversion, so malformed markup is repaired the same way a browser does before it is rendered to Markdown.

  • Grouped, frozen-dataclass config (Markdown.Links, Markdown.Images, Markdown.Tables …) is fully typed and discoverable, versus html2text’s flat set of untyped instance attributes.

  • A shell entry point: python -m turbohtml to-markdown (installed as the turbohtml console script) reads a file or stdin, covering html2text’s command-line converter.

What html2text has that turbohtml does not

  • Pure-Python install with no compiled extension. html2text runs anywhere a Python interpreter does; turbohtml needs a prebuilt wheel (or a C toolchain to build from source) for the target platform.

  • baseurl in html2text and Markdown.Links(base_url=...) both prefix rather than fully resolve; neither does RFC-3986 resolution, so there is no gap here, but do not expect turbohtml to add it.

Performance

to Markdown

turbohtml

html2text

HTML to Markdown — article (2 KiB)

2.84 µs

595 µs (210x)

HTML to Markdown — list (4 KiB)

4.47 µs

1.25 ms (281x)

HTML to Markdown — table (4 KiB)

7.25 µs

1.1 ms (152x)

HTML to Markdown — configured (4 KiB)

15.3 µs

1.23 ms (80.5x)

Google Docs export to Markdown — google_doc (4 KiB)

10.7 µs

596 µs (56.0x)

How to migrate

Swap the import and the call:

# html2text
import html2text

html2text.html2text(text)

# turbohtml
import turbohtml

turbohtml.parse(text).to_markdown()
print(parse("<h1>Title</h1><p>Some <b>bold</b> text.</p>").to_markdown())
# Title

Some **bold** text.

The html2text options map onto the grouped Markdown config fields with one name per concept:

html2text

turbohtml to_markdown()

ul_item_mark

Markdown.Lists(bullets=...)

emphasis_mark, strong_mark

Markdown.Inline(emphasis=..., strong=...)

ignore_emphasis

Markdown.Inline(ignore_emphasis=...)

ignore_links

Markdown.Links(ignore=...)

skip_internal_links

Markdown.Links(skip_internal=...)

inline_links

Markdown.Links(style=...) ("inline"/"reference")

ignore_images, images_to_alt, images_as_html, images_with_size

Markdown.Images(mode=...) ("markdown"/"alt"/"ignore"/"html")

default_image_alt

Markdown.Images(default_alt=...)

ignore_tables, bypass_tables

Markdown.Tables(mode=...) ("markdown"/"strip"/"html")

pad_tables

Markdown.Tables(pad=...)

body_width, wrap_list_items, wrap_links

Markdown.Wrapping(width=..., list_items=..., links=...)

unicode_snob (and the UNIFIABLE table)

Markdown.Document(transliterate=...)

mark_code

Markdown.Code(mark=...)

backquote_code_style

Markdown.Code(block_style=...) ("fenced"/"indented")

single_line_break

Markdown.Document(block_spacing="single")

baseurl

Markdown.Links(base_url=...)

open_quote, close_quote

Markdown.Inline(quote_open=..., quote_close=...)

escape_snob

Markdown.Escaping(mode="all")

google_doc

Markdown.GoogleDoc(enabled=...) (or the Markdown.google_doc() preset)

google_list_indent

Markdown.GoogleDoc(list_indent=...)

hide_strikethrough

Markdown.Inline(hide_strikethrough=...)

Markdown.google_doc() reads the inline-CSS styling a Google Docs HTML export carries: a font-weight of bold or 700900 becomes strong, font-style:italic becomes emphasis, a Courier New/Consolas font-family becomes an inline code span, list-style-type picks the list marker, and each Markdown.GoogleDoc(list_indent=...) pixels of margin-left add one list-nesting level. With Markdown.Inline(hide_strikethrough=True) a text-decoration:line-through drops the struck text.

from turbohtml import Markdown

export = '<p><span style="font-weight:700">Quarterly</span> revenue</p>'
print(parse(export).to_markdown(Markdown.google_doc()))
**Quarterly** revenue

Gotchas and pitfalls

  • to_markdown() is a method on any node, so convert a subtree by calling it on the element you selected (doc.find("article").to_markdown()) instead of slicing the HTML string first.

  • Layout-aware plain text (the inscriptis role, to_text()) is a separate method; for the unstructured concatenation read text.

  • base_url does simple prefixing rather than full RFC-3986 URL resolution, matching html2text’s baseurl.

  • html2text wraps prose at body_width (78) by default; turbohtml wraps only when you set Markdown.Wrapping(width=...). Set it explicitly to reproduce wrapped output, or leave it unset for unwrapped Markdown.