Библиотека markdown: из Markdown в HTML на Python
Python Markdown - это библиотека markdown, которая превращает текст в разметке Markdown в готовый HTML. Ее используют в генераторах документации, блогах на Flask и Django, при сборке отчетов. В статье показаны установка, базовая конвертация, подключение расширений для таблиц, кода и оглавления, работа с файлами и защита от опасного HTML в пользовательском тексте.

Содержание статьи
Установка и первая конвертация
Библиотека не входит в стандартную поставку Python, ее ставят через pip. Пакет называется Markdown, а импортируется как markdown.
pip install markdownОсновная функция markdown.markdown() принимает строку и возвращает строку с HTML. Обертку html и body она не добавляет, результат готов для вставки в шаблон страницы.
import markdown
text = "# Заголовок\n\nЭто *текст* в формате **Markdown**"
html = markdown.markdown(text)
print(html)
# <h1>Заголовок</h1>
# <p>Это <em>текст</em> в формате <strong>Markdown</strong></p>Для быстрой проверки файла есть запуск из командной строки: python -m markdown notes.md > notes.html.
Какой синтаксис понимает библиотека
Без расширений Python-Markdown следует исходному описанию Markdown от Джона Грубера. В таблице показано, во что превращаются основные элементы.
| Markdown | HTML |
|---|---|
# Текст ... ###### Текст | h1 ... h6 |
*курсив*, **жирный** | em, strong |
[текст](https://site.ru) | a href |
 | img |
- пункт или 1. пункт | ul / ol с li |
> цитата | blockquote |
`код` | code |
| строка с отступом 4 пробела | pre с code |
--- | hr |
Есть отличия от GitHub-диалекта. Вложенные списки требуют отступа ровно в 4 пробела, двух пробелов недостаточно. Перенос строки внутри абзаца не создает тег br, для этого нужно расширение nl2br. Таблицы и блоки кода в тройных обратных кавычках тоже работают только с расширениями.
Подключение расширений
Расширения передаются списком в параметре extensions. Встроенные расширения указывают коротким именем, сторонние ставят отдельно через pip.
| Расширение | Что добавляет |
|---|---|
tables | таблицы с разделителем | |
fenced_code | блоки кода в тройных обратных кавычках |
codehilite | подсветку кода через Pygments |
toc | id у заголовков и оглавление |
footnotes | сноски |
nl2br | br на месте одиночного переноса |
sane_lists | более предсказуемое поведение списков |
extra | набор: tables, fenced_code, footnotes, abbr, attr_list, def_list, md_in_html |
Таблицы и блоки кода
Расширение extra сразу включает самые востребованные возможности, поэтому его часто подключают по умолчанию.
import markdown
text = """
| Метод | Описание |
|-------|----------|
| get | чтение |
```python
print("hi")
```
"""
html = markdown.markdown(text, extensions=["extra"])
print(html)Для цветной подсветки добавьте codehilite и установите пакет Pygments. Библиотека разметит код классами, а цвета подключаются отдельным CSS-файлом, который генерирует сам Pygments.
Оглавление через toc
Расширение toc добавляет заголовкам атрибуты id и собирает оглавление. Его можно вставить в текст маркером [TOC] или взять из атрибута объекта после конвертации. Для кириллицы удобно задать функцию slugify с поддержкой Unicode.
import markdown
from markdown.extensions.toc import slugify_unicode
md = markdown.Markdown(extensions=["toc"],
extension_configs={"toc": {"slugify": slugify_unicode}})
html = md.convert("## Установка\n\n## Настройка")
print(html) # <h2 id="установка">Установка</h2> ...
print(md.toc) # <div class="toc"><ul>...</ul></div>Класс Markdown и работа с файлами
Функция markdown.markdown() при каждом вызове заново создает парсер. Если нужно обработать много документов, выгоднее один раз создать объект markdown.Markdown и вызывать convert(). Между документами вызывайте reset(), чтобы сбросить состояние расширений, например сноски и оглавление предыдущего текста.
from pathlib import Path
import markdown
md = markdown.Markdown(extensions=["extra", "toc"])
for src in Path("docs").glob("*.md"):
body = md.convert(src.read_text(encoding="utf-8"))
src.with_suffix(".html").write_text(body, encoding="utf-8")
md.reset()Указывайте кодировку явно: в Windows без encoding="utf-8" русский текст прочитается неправильно. Подробнее о режимах открытия и кодировках рассказано в статье работа с текстовым файлом. Для одиночных файлов есть функция markdown.markdownFromFile(input=..., output=...).
Безопасность при выводе пользовательского текста
Python-Markdown пропускает встроенный HTML без изменений. Если пользователь напишет в комментарии тег script, он попадет на страницу как есть. Библиотека не занимается очисткой, поэтому для текста из форм результат нужно пропускать через санитайзер с белым списком тегов, например пакет nh3 или bleach.
import markdown
import nh3
user_text = "**Привет** <script>alert(1)</script>"
safe_html = nh3.clean(markdown.markdown(user_text))
print(safe_html) # <p><strong>Привет</strong> </p>Во Flask готовый HTML передают в шаблон через фильтр |safe или обертку Markup, иначе Jinja экранирует теги и пользователь увидит их текстом. Делать это можно только с уже очищенной строкой. Как устроены маршруты и шаблоны, описано в статье основы Flask.
Альтернативные библиотеки
Кроме Python-Markdown, для конвертации используют еще два популярных пакета. Выбор зависит от того, какой диалект Markdown нужен и важна ли скорость.
markdown-it-pyследует спецификации CommonMark, поэтому результат совпадает с большинством современных редакторов.mistuneориентирован на скорость и подходит для обработки большого потока текстов.markdown(Python-Markdown) выигрывает числом готовых расширений и используется в MkDocs.
Описание всех параметров и встроенных расширений есть в документации Python-Markdown.
Частые вопросы
Ответы на вопросы, которые возникают при первом использовании библиотеки.
Почему не работает таблица или блок кода в тройных кавычках?
Эти элементы не входят в базовый синтаксис. Подключите расширения tables и fenced_code или сразу extra.
Почему вложенный список выводится одним уровнем?
Python-Markdown требует отступ в 4 пробела для вложенного пункта. С отступом в 2 пробела элементы окажутся в одном списке.
Как превратить HTML обратно в Markdown?
Библиотека работает только в одну сторону. Для обратного преобразования используют отдельные пакеты, например markdownify или html2text.
Как получить чистый текст без разметки?
Сконвертируйте Markdown в HTML, а затем извлеките текст парсером, например через BeautifulSoup(html, "html.parser").get_text().