Blog / Guides
HTML to Plain Text Conversion: Methods, Tools, and Code
By URLtoText Team · · 9 min read
To convert HTML to plain text, parse the HTML with a real parser, remove script and style elements, and extract the text nodes with line breaks after block elements. In Python that's BeautifulSoup's get_text() or the html2text library. In JavaScript it's DOMParser with textContent, or the html-to-text npm package. On the command line, lynx -dump or pandoc do it in one line.
This guide walks through each HTML to plain text conversion method with working code, then covers the pitfalls that trip up most converters.
| Method | Best for | Keeps structure | Handles JavaScript pages |
|---|---|---|---|
BeautifulSoup get_text() |
Python scripts, full control | With some work | No |
html2text (Python) |
Readable text or Markdown | Yes | No |
DOMParser (browser JS) |
Front-end apps | No | Only the HTML you give it |
html-to-text (Node.js) |
Emails, server-side apps | Yes | No |
lynx, pandoc |
Shell scripts, batch jobs | Yes | No |
| URL to text API | Live web pages at scale | Yes | Yes |
Why not just strip the tags with a regex?
A regex like <[^>]+> looks tempting, but it breaks on real HTML. It leaves the contents of <script> and <style> in your output, doesn't decode entities like &, mangles attributes that contain >, and runs paragraphs together. Use a parser.
Python: BeautifulSoup
BeautifulSoup is the standard choice for parsing HTML in Python. Install it with pip install beautifulsoup4.
The one-liner is soup.get_text(), but it has two problems. Without a separator, words from adjacent elements run together. With separator="\n", every inline tag (like a link in the middle of a sentence) gets its own line. This function avoids both:
import re
from bs4 import BeautifulSoup
BLOCK_TAGS = ["p", "div", "br", "li", "tr", "h1", "h2", "h3", "h4", "h5", "h6",
"section", "article", "header", "footer", "blockquote", "pre"]
def html_to_text(html: str) -> str:
soup = BeautifulSoup(html, "html.parser")
# 1. Remove elements whose text is never meant for readers.
for tag in soup(["script", "style", "noscript", "template"]):
tag.decompose()
# 2. Put a line break after block-level elements so paragraphs stay apart.
for tag in soup.find_all(BLOCK_TAGS):
tag.append("\n")
# 3. Extract text without adding separators inside inline tags like <a>.
text = soup.get_text()
# 4. Normalize whitespace: collapse runs of spaces, trim lines, drop blank runs.
text = text.replace("\xa0", " ")
lines = [re.sub(r"[ \t]+", " ", line).strip() for line in text.splitlines()]
return "\n".join(line for line in lines if line)
html = """
<html><head><style>p { color: red; }</style><script>var x = 1;</script></head>
<body>
<h1>Price & availability</h1>
<p>In stock now. <a href="/buy">Buy it</a> today.</p>
<ul><li>Red</li><li>Blue</li></ul>
</body></html>
"""
print(html_to_text(html))
Output:
Price & availability
In stock now. Buy it today.
Red
Blue
The parser decodes entities (& becomes &), and the script and style contents are gone.
Python: html2text
If you want readable output with headings, lists, and links preserved, html2text converts HTML to Markdown-style text. Install it with pip install html2text.
import html2text
html = """
<h1>Price & availability</h1>
<p>In stock now. <a href="https://example.com/buy">Buy it</a> today.</p>
<ul><li>Red</li><li>Blue</li></ul>
"""
converter = html2text.HTML2Text()
converter.body_width = 0 # don't hard-wrap long lines
print(converter.handle(html)) # Markdown with links
converter.ignore_links = True
converter.ignore_emphasis = True
print(converter.handle(html)) # closer to plain text
The first call prints # Price & availability, the paragraph with [Buy it](https://example.com/buy), and a bulleted list. With ignore_links and ignore_emphasis set, the link becomes plain "Buy it". The # and * markers remain, which most people find easier to read than flat text.
JavaScript in the browser: DOMParser
In the browser, let the built-in parser do the work. DOMParser doesn't run scripts in the HTML it parses, so it's safe for untrusted input.
function htmlToText(html) {
const doc = new DOMParser().parseFromString(html, "text/html");
doc.querySelectorAll("script, style, noscript, template").forEach((el) => el.remove());
return doc.body.textContent.replace(/\s+/g, " ").trim();
}
console.log(htmlToText("<p>Fish & chips</p><script>alert(1)</script>"));
// "Fish & chips"
textContent returns all text with no line breaks between paragraphs, so this version collapses everything into one line. If you need paragraph breaks, innerText on an element that's on screen respects layout, but on a parsed document that isn't rendered it behaves like textContent. For structured output, use a library.
JavaScript in Node.js: html-to-text
The html-to-text package handles paragraphs, lists, links, and tables. Install it with npm install html-to-text.
const { convert } = require("html-to-text");
const html = `
<h1>Price & availability</h1>
<p>In stock now. <a href="https://example.com/buy">Buy it</a> today.</p>
<table><tr><th>Size</th><th>Price</th></tr><tr><td>S</td><td>$10</td></tr></table>
`;
const text = convert(html, {
wordwrap: false,
selectors: [
{ selector: "a", options: { ignoreHref: true } },
{ selector: "img", format: "skip" },
{ selector: "table", format: "dataTable" },
],
});
console.log(text);
Output:
PRICE & AVAILABILITY
In stock now. Buy it today.
SIZE PRICE
S $10
By default it uppercases headings and wraps lines at 80 characters. wordwrap: false turns wrapping off. Without the dataTable format, table cells run together, which is a common surprise.
Command line: lynx, w3m, and pandoc
Text-mode browsers render HTML the way a person would read it:
# Render a file or URL as text, without the numbered link list at the end
lynx -dump -nolist page.html > page.txt
lynx -dump -nolist https://example.com > example.txt
# w3m works the same way
w3m -dump page.html > page.txt
Pandoc converts between document formats, including HTML to plain text and HTML to Markdown:
pandoc page.html -t plain -o page.txt
pandoc page.html -t gfm -o page.md # GitHub-flavored Markdown
To batch convert a folder of HTML files:
for f in *.html; do pandoc "$f" -t plain -o "${f%.html}.txt"; done
Online HTML to plain text converters
If you only need to convert one snippet, an online HTML to text converter is fastest: paste the HTML, copy the result. If you have a URL rather than raw HTML, URLtoText fetches the page and returns plain text, Markdown, or HTML. It can render JavaScript first, which matters for pages that build their content in the browser. All the methods above only see the HTML you give them, so they return little or nothing for those pages.
Common pitfalls in HTML to text conversion
Whitespace. HTML ignores most line breaks and indentation in the source, so naive extraction gives you random gaps or one huge line. Add breaks after block elements, collapse runs of spaces, and convert non-breaking spaces ( , \xa0) to normal spaces.
Entities. &, <, ’, and friends need decoding. Parsers do this automatically. Regex stripping doesn't. In Python, html.unescape() decodes entities in a string if you're not using a parser.
Scripts, styles, and hidden content. Remove script, style, noscript, and template before extracting. Also watch for elements hidden with CSS or the hidden attribute, such as mobile menus and modal text. Parsers can't see CSS, so they include that text.
Tables. Cells run together unless you add separators. Use a library with table support, add a tab or | between cells, or convert to Markdown so the table stays readable.
Links. Decide whether you need URLs. Keep them for research or citations (Buy it (https://example.com/buy)), drop them for reading or word counts.
Boilerplate. A full web page includes navigation, cookie banners, and footers. Converting the whole page brings all of that along. Target the main content first, for example soup.select_one("article") in BeautifulSoup, then convert just that element.
Encoding. If you see ’ instead of an apostrophe, the bytes were decoded with the wrong character set. Let requests or your parser detect the encoding, or set it explicitly.
HTML to Markdown: often the better choice
Plain text throws away structure: which line was a heading, which items formed a list, where a link pointed. Markdown keeps all of that in a lightweight format that's still easy to read. It's usually the better target when:
- The text is going into ChatGPT, Claude, or another LLM. Headings and lists help the model follow the document, and Markdown uses far fewer tokens than raw HTML.
- You're migrating content into a static site generator, wiki, or docs tool.
- You want readable diffs when tracking changes to a page over time.
Good HTML to Markdown practices:
- Strip the page down to the main content before converting.
- Use a converter that handles tables, such as pandoc with
-t gfm. - Turn relative links and image paths into absolute URLs so they still work outside the site.
- Check code blocks and nested lists, which are the most common conversion errors.
In Python, html2text (shown above) outputs Markdown by default, and the markdownify package is another popular option.
Converting live web pages at scale
Everything above assumes you already have the HTML. For live web pages, you also have to fetch them, render JavaScript, and deal with sites that block scripts. The URLtoText API handles that and returns text or Markdown in one request:
import requests
response = requests.post(
"https://urltotext.com/api/v1/urltotext/",
headers={"Authorization": "Token YOUR_API_TOKEN"},
json={
"url": "https://example.com",
"output_format": "markdown", # or "text" or "html"
"render_javascript": True,
"extract_css_selector": "article",
},
timeout=120,
)
response.raise_for_status()
print(response.json()["data"]["content"])
The extract_css_selector option keeps only the matching part of the page, which takes care of the boilerplate problem. For a broader look at fetching pages with code, see our Python web scraping guide.
Frequently asked questions
What is the best way to convert HTML to plain text in Python?
Use BeautifulSoup to parse the HTML, remove script and style tags, and call get_text(), adding line breaks after block elements as shown above. If you want headings, lists, and links preserved, use html2text instead.
How do I convert HTML to plain text online?
Paste your HTML into an online HTML to text converter. If you have a page URL, a URL to text tool like URLtoText fetches the page and converts it for you, including pages that need JavaScript.
How do I batch convert HTML files to text?
Use pandoc or lynx in a shell loop, for example for f in *.html; do pandoc "$f" -t plain -o "${f%.html}.txt"; done. In Python, loop over the files and pass each one to your conversion function.
Should I convert HTML to plain text or Markdown?
Choose plain text for search indexing, word counts, and text messages. Choose Markdown when structure matters, such as feeding content to an AI model or moving it into another site.
Why is my converted text missing content?
The page probably loads its content with JavaScript, so the raw HTML you downloaded is mostly empty. Render the page in a headless browser first, or use a tool with a JavaScript rendering option.