在HTML文档中突出显示术语
我们有一个词汇表,里面有多达2000个词条(每个词条可能由一个、两个或三个单词组成,这些单词之间可以用空格或连字符分开)。
现在,我们想找到一种方法,在一个较长的HTML文档中(最多100KB的HTML标记)突出显示所有的词条,以便生成一个包含高亮词条的静态HTML页面。
要实现这个功能,我们面临的挑战是:词汇表的词条数量很大,而且HTML文档也很长……那么,如何在Python中设计一个高效的解决方案呢?
目前我在考虑使用lxml来解析HTML文档,遍历所有的文本节点,然后将每个文本节点的内容与所有词汇表中的词条进行匹配。
在客户端(浏览器)实时高亮显示是不行的,因为IE会对长时间运行的脚本发出警告,导致脚本超时……所以这种方法不适合生产环境。
有没有更好的主意呢?
3 个回答
你可以先查看一下词汇表里的每一个术语,然后对每个术语使用正则表达式(regex)在HTML中查找所有出现的地方。接着,把找到的每个地方都替换成这个术语,并用一个带有“highlighted”类的标签包裹起来,这样就可以通过样式设置背景颜色来突出显示了。
我觉得用客户端的JavaScript来高亮显示是最好的选择。这种方法可以节省服务器的处理时间和带宽,更重要的是,它能保持HTML的整洁,方便那些不需要多余标记的人使用,比如在打印或转换成其他格式时。
为了避免超时,可以把任务分成小块,然后一个一个地处理,使用setTimeout来实现。下面是这种方法的一个例子:
function hilite(terms, chunkSize) {
// prepare stuff
var terms = new RegExp("\\b(" + terms.join("|") + ")\\b", "gi");
// collect all text nodes in the document
var textNodes = [];
$("body").find("*").contents().each(function() {
if (this.nodeType == 3)
textNodes.push(this)
});
// process N text nodes at a time, surround terms with text "markers"
function step() {
for (var i = 0; i < chunkSize; i++) {
if (!textNodes.length)
return done();
var node = textNodes.shift();
node.nodeValue = node.nodeValue.replace(terms, "\x1e$&\x1f");
}
setTimeout(step, 100);
}
// when done, replace "markers" with html
function done() {
$("body").html($("body").html().
replace(/\x1e/g, "<b>").
replace(/\x1f/g, "</b>")
);
}
// let's go
step()
}
使用方法如下:
$(function() {
hilite(["highlight", "these", "words"], 100)
})
如果你有任何问题,随时问我。
你可以使用一个解析器来递归地遍历你的树形结构,并只替换那些由文本组成的标签。
在这个过程中,你需要考虑几个问题:
- 不是所有的文本都需要被替换(比如,内联的JavaScript)。
- 文档中的某些元素可能不需要解析(比如,标题等)。
下面是一个简单的示例,虽然这个示例并不适合实际生产环境,但可以帮助你理解如何实现这个功能:
html = """The HTML you need to parse"""
import BeautifulSoup
IGNORE_TAGS = ['script', 'style']
def parse_content(item, replace_what, replace_with, ignore_tags = IGNORE_TAGS):
for content in item.contents:
if isinstance(content, BeautifulSoup.NavigableString):
content.replaceWith(content.replace(replace_what, replace_with, ignore_tags))
else:
if content.name not in ignore_tags:
parse_content(content, replace_what, replace_with, ignore_tags)
return item
soup = BeautifulSoup.BeautifulSoup(html)
body = soup.html.body
replaced_content = parse_content(body, 'a', 'b')
这个示例应该会把所有的“a”替换成“b”,但会保留以下内容:
- 在内联JavaScript或CSS中的内容(虽然内联的JS或CSS通常不应该出现在文档的主体中)。
- 像img、a等标签中的引用。
- 标签本身。
当然,根据你的词汇表,你还需要确保不会只替换一个单词的一部分;为此,使用正则表达式会比使用content.replace更合适。