如何用自定义 <comment> 元素替换 HTML 注释

Posted

技术标签:

【中文标题】如何用自定义 <comment> 元素替换 HTML 注释【英文标题】:How to replace HTML comments with custom <comment> elements 【发布时间】:2015-04-19 17:58:03 【问题描述】:

我正在使用 Python 中的 BeautifulSoup 将大量 html 文件大量转换为 XML。

示例 HTML 文件如下所示:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- this is an HTML comment -->
<!-- this is another HTML comment -->
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        ...
        <!-- here is a comment inside the head tag -->
    </head>
    <body>
        ...
        <!-- Comment inside body tag -->
        <!-- Another comment inside body tag -->
        <!-- There could be many comments in each file and scattered, not just 1 in the head and three in the body. This is just a sample. -->
    </body>
</html>
<!-- This comment is the last line of the file -->

我想出了如何找到文档类型并将其替换为标签&lt;doctype&gt;...&lt;/doctype&gt;,但评论让我很沮丧。我想用&lt;comment&gt;...&lt;/comment&gt; 替换HTML cmets。在这个 HTML 示例中,我能够替换前两个 HTML cmets,但 html 标记内的任何内容和结束 html 标记之后的最后一个注释我都没有。

这是我的代码:

file = open ("sample.html", "r")
soup = BeautifulSoup(file, "xml")

for child in soup.children:

    # This takes care of the first two HTML comments
    if isinstance(child, bs4.Comment):
        child.replace_with("<comment>" + child.strip() + "</comment>")

    # This should find all nested HTML comments and replace.
    # It looks like it works but the changes are not finalized
    if isinstance(child, bs4.Tag):
        re.sub("(<!--)|(&lt;!--)", "<comment>", child.text, flags=re.MULTILINE)
        re.sub("(-->)|(--&gr;)", "</comment>", child.text, flags=re.MULTILINE)

# The HTML comments should have been replaced but nothing changed.
print (soup.prettify(formatter=None))

这是我第一次使用 BeautifulSoup。如何使用 BeautifulSoup 查找并替换所有带有 &lt;comment&gt; 标签的 HTML cmets?

我可以通过pickle 将其转换为字节流,对其进行序列化,应用正则表达式,然后将其反序列化回BeautifulSoup 对象吗?这会起作用还是只会导致更多问题?

我尝试在子标记对象上使用pickle,但反序列化失败并出现TypeError: __new__() missing 1 required positional argument: 'name'

然后我尝试通过child.text 仅腌制标签的文本,但由于AttributeError: can't set attribute,反序列化失败。基本上,child.text 是只读的,这就解释了为什么正则表达式不起作用。所以,我不知道如何修改文本。

【问题讨论】:

应用所有更改后不会导致格式错误的xml 文件吗? 我不知道,但Chilkat 有一个(不是免费的)HTML-to-XML 转换 python 库,可以将所有 HTML cmets 转换为 &lt;comment&gt;,并且 XML 文件看起来不错。 【参考方案1】:

你有几个问题:

    您不能修改child.text。它是一个只读属性,只是在幕后调用get_text(),其结果是一个全新的字符串,未连接到您的文档。

    re.sub() 不会就地修改任何内容。你的线路

    re.sub("(<!--)|(&lt;!--)", "<comment>", child.text, flags=re.MULTILINE)
    

    应该是

    child.text = re.sub("(<!--)|(&lt;!--)", "<comment>", child.text, flags=re.MULTILINE)
    

    ...但由于第 1 点,这无论如何都行不通。

    尝试通过用正则表达式替换文档中的文本块来修改文档是使用 BeautifulSoup 的错误方法。相反,您需要找到节点并将其替换为其他节点。

这是一个可行的解决方案:

import bs4

with open("example.html") as f:
    soup = bs4.BeautifulSoup(f)

for comment in soup.find_all(text=lambda e: isinstance(e, bs4.Comment)):
    tag = bs4.Tag(name="comment")
    tag.string = comment.strip()
    comment.replace_with(tag)

这段代码首先遍历调用find_all() 的结果,利用我们可以将pass a function 作为text 参数这一事实。在 BeautifulSoup 中,CommentNavigableString 的子类,因此我们将其视为字符串进行搜索,而 lambda ... 只是例如的简写

def is_comment(e):
    return isinstance(e, bs4.Comment)

soup.find_all(text=is_comment)

然后,我们创建一个新的Tag,命名为适当的名称,将其内容设置为原始评论的剥离内容,并将评论替换为我们刚刚创建的标签。

结果如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<comment>this is an HTML comment</comment>
<comment>this is another HTML comment</comment>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
        ...
        <comment>here is a comment inside the head tag</comment>
</head>
<body>
        ...
        <comment>Comment inside body tag</comment>
<comment>Another comment inside body tag</comment>
<comment>There could be many comments in each file and scattered, not just 1 in the head and three in the body. This is just a sample.</comment>
</body>
</html>
<comment>This comment is the last line of the file</comment>

【讨论】:

非常感谢您的帮助。我现在正在阅读这个。如果可以,请您解释或详细说明python代码中的每一行,以for开头。再说一次,我对 Beautiful Soup 还是很陌生。先感谢您!我对 lamda 的用法一点也不熟悉。 @user3621633 我已经添加了对代码现在功能的解释。 这成功了!谢谢你。我唯一要添加的是我需要 XML 格式的。所以,我想我必须在写入输出文件之前将 BeautifulSoup 与 xml 一起应用。如果我第一次使用 xml,HTML cmets 会以 HTML 实体(即 <)的形式出现,我不确定 BeautifulSoup 是否可以使用这些实体。

以上是关于如何用自定义 <comment> 元素替换 HTML 注释的主要内容,如果未能解决你的问题,请参考以下文章

如何用正则表达式去掉html标签

如何用文本图片和复选框过滤自定义listView?

如何用行上的条件定义总和?

如何用 <br /> 元素替换字符串中的所有换行符?

如何用js获取id 元素内容

如何用 `(X)` 之类的括号替换 `<sup>X</sup>` 之类的 HTML 元素?