导出html前必须先html.unescape()解码实体,再dom解析;清洗时用domparser白名单过滤标签、清除危险属性,保留段落结构并服务端二次净化。

导出 HTML 前必须先解码实体,否则 " 会变成乱码
CMS 或富文本编辑器导出的 HTML 字符串里,常混着 、<code>"、' 这类实体——它们不是标签,是编码后的字符。如果跳过解码直接丢给 BeautifulSoup 或 DOMParser,get_text() 会把 " 当原样字符输出,导致中文引号显示为 "。
正确顺序只有一条:先 html.unescape(),再进 DOM 解析。别反过来,也别省略。
-
html.unescape()必须作用于原始字符串,不能对已解析的Document或Tag对象调用 - Python 用户注意:
html.unescape()在 Python 3.4+ 内置,无需额外安装 - JS 用户对应的是
DOMParser+textContent,但要注意 Safari 对空字符串的 fallback 行为
用 DOMParser 而不是正则删标签,否则 <!-- 注释 --> 会被漏掉
写 innerHTML.replace(/]*>/g, '') 看似快,实际危险且不可靠:它匹配不了注释、CDATA、自闭合标签里的属性(比如 <img src="a>b">),更无法识别嵌套结构。真正能保留语义又安全的做法,是用 DOMParser 构建文档树,再白名单过滤。
- 只保留语义明确的标签:
['p', 'h2', 'h3', 'ul', 'ol', 'li', 'strong', 'em', 'blockquote', 'pre', 'code'] - 一律移除
style、class、所有on*属性(如onclick) -
<img>允许保留,但强制清空width/height,只留src和alt - 处理完记得对
doc.body调用normalize(),合并空文本节点
get_text() 不等于“干净纯文本”,段落结构得手动保
直接调 soup.get_text() 或 doc.body.textContent 会抹平所有换行和段落边界,<p>第一段</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill3458" title="html-ppt-to-pdf"><img
src="https://img.php.cn/upload/skill/000/000/081/178956546773641.jpg" alt="html-ppt-to-pdf" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill3458" title="html-ppt-to-pdf" class="overflowclass">html-ppt-to-pdf</a>
<p class="overflowclass">将使用 `<section class="slide">` 约定的 HTML 幻灯片转换为高保真、矢量文本 PDF(使用 Playwright + Chromium 原生 PDF 功能)。</p>
</div>
<a rel="nofollow" href="/xiazai/skill3458" title="html-ppt-to-pdf" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<p>第二段</p> 变成“第一段第二段”。下游若用于搜索或摘要,这会导致分词错乱。
- 需要保留段落:用
soup.find_all("p")或doc.body.querySelectorAll("p"),再"\n".join(p.get_text(strip=True) for p in ps) - 需要兼容列表:提取
ul/ol后,对每个li加前缀"• "或数字序号 - 别依赖
strip()清空首尾空格——零宽空格\u200b和软连字符\u00ad得显式.replace()
服务端入库前必须双重净化,前端 DOMPurify 不是保险柜
用户绕过前端直接 POST 一个带 javascript:alert(1) 的 HTML 字符串,DOMPurify 就完全失效。服务端必须用 HTMLPurifier(PHP)或 jsdom+DOMPurify(Node)再过一遍,且配置不能偷懒。
- 禁用默认宽松模式:
HTML.Allowed显式列出标签,不填div、span、font -
URI.AllowedSchemes只设['http', 'https', 'mailto'],data:和javascript:必须拦截 - 对
style属性,宁可全禁(FORBID_ATTR: ['style']),也不要信白名单——background-image: url(javascript:...)就是这么漏的 - 入库字段名别叫
content_html,改用content_sanitized,提醒后续环节这是清洗过的
清洗真正的难点不在“删什么”,而在“删完之后是否还像人写的”。比如空 <p></p> 直接删掉,可能导致 CSS p + p 选择器失效;<strong></strong> 全换成普通文本,LLM prompt 就丢了强调信号。这些细节没法靠一步脚本解决,得按下游用途反推清洗粒度。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










