menu Chancel's Blog
rss_feed lightbulb_outline

Python Markdown解析器Mistune使用指南

warning 这篇文章距离上次更新于257天前,文中部分信息可能已失效,请自行甄别无效内容。

1. 背景

每一个建设博客的人都需要写博文,主流的博文大多是Markdown格式的,各家博客提供的Markdown编辑器也不一而同

开发一款博客程序,如何实时地将Markdown文本转换为HTML内容是非常重要的,自行开发的博客在转换Markdown文本时通常会比较关注以下3个点

  • 转换速度
  • 自定义样式丰富程度
  • 插件用的顺手(否决Markdown2)

Chancel的博客是由Python语言基于Flask框架开发的,早先使用的是Markdown2的解析器,作为一款Markdown解析器拥有非常不错的人气,使用体验也不赖,只是在使用过程中始终遇到一些奇奇怪怪的问题(不顺手)

于是决定尝试一下其他的Python Markdown的解析器,在尝试几款解析器之后基于上面第三点,选择了使用 Mistune 解析器,非常顺手

PS:其实解析器都大同小异,无论是安装方法还是使用方法都基本一致,推荐可以做个小Demo尝试以下Markdown解析器,然后再决定使用哪一个也不迟

常见的Markdown解析器列表如下

  • Misaka: A python binding for Sundown. (CPython required)
  • Hoedown: A python binding for Hoedown, successor of Misaka.
  • Discount: A python binding for Discount. (CPython required)
  • cMarkdown: Markdown for Python, accelerated by C. (CPython required)
  • Markdown: A pure markdown parser, the very first implementation.
  • Markdown2: Another pure markdown parser.
  • Mistune: The fastest markdown parser in pure Python

以上资料来自 Python中的Markdown和Markdown2有何区别?segmentfault

2. Mistune

以下安装使用基于

  • Ubuntu 1804
  • Python 3.9.1

2.1. 安装

使用Mistune只需安装以下两个pip的包

pip3 install mistune pygments

其中pygments是用于解析代码块时使用,如果你无需解析代码块则只需安装mistune

2.2. 使用

2.2.1. 简单例子

最简单的使用方法如下

import mistune

markdown = mistune.Markdown()
markdown('I am using **mistune markdown parser**')
# output: <p>I am using <strong>mistune markdown parser</strong></p>

这样就能输出默认的解析器翻译的HTML文档了

2.2.2. Options参数

Options参数用于控制解析器解析选项,主要有以下五个参数

  • escape: 默认为False,为True时,所有HTML标签都将被转义
  • hard_wrap: 为True时,将使用GFM换行功能(暂时不明白这个是什么)
  • use_xhtml: 为True时支持解析将支持XHTML语法
  • parse_block_html: 仅在块级别中HTML解析文本(div、p、h、ulli、dl、dt等)
  • parse_inline_html: 仅在行内级别中HTML解析文本(a、span、i、u、em等)

Options参数使用方法如下

>>> escape = mistune.Markdown(escape=True)
>>> escape('<p>chancel</p>')
'&lt;p&gt;chancel&lt;/p&gt;'
>>> escape = mistune.Markdown(escape=False)
>>> escape('<p>chancel</p>')
'<p>chancel</p>'

2.3. Renderer参数(渲染效果)

Renderer参数主要用于自定义渲染效果,包括代码高亮、链接点击效果、标题显示方式等等

2.3.1. 高亮代码

高亮代码的使用方法如下,解析代码语法需要使用到 pygments 依赖包

import mistune
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import html

class HighlightRenderer(mistune.Renderer):
    def block_code(self, code, lang):
        try:
            lexer = get_lexer_by_name(lang, stripall=True)
            formatter = html.HtmlFormatter()
            return highlight(code, lexer, formatter)
        except Exception:
            return '\n<pre><code>%s</code></pre>\n' % \
                mistune.escape(code)

def MarkdownConvertToHtmlWithMistune(content: str):
    renderer = HighlightRenderer()
    markdown = mistune.Markdown(renderer=renderer)
    return markdown(content)

我们使用以上代码将以下这段代码进行解析

from flask_login import LoginManager

login_manager = LoginManager()
login_manager.init_app(app)

这样解析出来之后的代码例子部分如下

<div class="highlight">
        <pre>
            <span></span>
            <span class="kn">from</span> <span class="nn">flask_login</span> <span class="kn">import</span> <span class="n">LoginManager</span>
            <span class="n">login_manager</span> <span class="o">=</span> <span class="n">LoginManager</span><span class="p">()</span>
            <span class="n">login_manager</span><span class="o">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
        </pre>
    </div>

可以看到渲染出来的代码被解析成不同的span部分,且带有不同的class,这个时候我们根据class就可以自定义自己喜欢的颜色主题

大部分代码主题都已经有人整理好并开源成仓库了 richleland/pygments-css - github.com

只需要引入上述的CSS文件,就可以渲染成相对应的主题颜色

2.3.2. 自定义HTML标题属性

我们通常需要将博文目录列出,这个时候可以使用Javascript来遍历所有标题整理成列表提供锚定位,但解析器解析出来的HTML标题不具备锚点,如下

<h1>1. 使用指南</h1>

我们通常希望是这样的

<h1 name="1. 使用指南">1. 使用指南</h1>

当然这可以使用Javascript来遍历动态修改,但这样一是在环境不佳的情况下不理想,二是对SEO不友好

所以使用Renderer参数来自定义渲染显然更为理想

类似于高亮代码的方法,我们可以简单地在HighlightRenderer类里添加一个 header 方法即可

import mistune
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import html

class HighlightRenderer(mistune.Renderer):
    def block_code(self, code, lang):
        try:
            lexer = get_lexer_by_name(lang, stripall=True)
            formatter = html.HtmlFormatter()
            return highlight(code, lexer, formatter)
        except Exception:
            return '\n<pre><code>%s</code></pre>\n' % \
                mistune.escape(code)

    def header(self, text, level, raw=None):
        return '\n<h%s id="%s">%s</h%s>\n' % (level, text, text, level)

def MarkdownConvertToHtmlWithMistune(content: str):
    renderer = HighlightRenderer()
    markdown = mistune.Markdown(renderer=renderer)
    return markdown(content)

如果还需要自定义更多渲染器内容,可以参考官方文档 Mistune - mistune 0.8.4 documenttation

3. 结束语

啊如何用花了不到2个小时,写这篇文章花了差不多多的时间,不过Mistune上手真是非常顺利,Markdown2在使用过程总是跌跌撞撞

赞一个 Mistune 的官方文档

文中提到的链接与资料来源 Mistune - mistune 0.8.4 documenttation richleland/pygments-css - github.com Python中的Markdown和Markdown2有何区别? - segmentfault.com 多个Markdown转换效率对比 - lepture.com

博文目录

[[replyMessage== null?"发表评论":"@" + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageList.data.items.length]])

[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[messageItem.m_environ.browser]] [[messageItem.m_environ.os]] [[messageItem.m_environ.device]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[subMessage.m_environ.browser]] [[subMessage.m_environ.os]] [[subMessage.m_environ.device]]