今天我们使用 Cheerio's,尤其是 .text() 方法从 html 输入中提取文本。
但是当 html 是
<div>
By<div><h2 class="authorh2">John Smith</h2></div>
</div>
在页面上,“by”一词后面的 /div 确保有空格或换行符。 但是当应用cheerio text()时,我们得到的结果是错误的:
ByJohn smith
=> 这是错误的,因为我们需要在 By 和 john 之间有一个空格。
一般来说,是否可以以一种特殊的方式获取文本,以便任何 html 标签都被空格替换。 (我可以在之后修剪所有多个空格......)
我们希望得到 John smith 的输出
您可以使用以下正则表达式将所有 HTML 标签替换为空格:
/<\/?[a-zA-Z0-9=" ]*>/g
因此,当您用此正则表达式替换 HTML 时,它可能会产生多个空格。在这种情况下,您可以使用
replace(/\s\s+/g, ' ')
将所有空格替换为单个空格。
查看结果:
console.log(document.querySelector('div').innerHTML.replaceAll(/<\/?[a-zA-Z0-9=" ]*>/g, ' ').replace(/\s\s+/g, ' ').trim())
<div>
By<div><h2 class="authorh2">John Smith</h2></div>
</div>
您可以使用纯 JavaScript 来完成此任务。
const parent = document.querySelector('div');
console.log(parent.innerText.replace(/(\r\n|\n|\r)/gm, " "))
<div>
By<div><h2 class="authorh2">John Smith</h2></div>
</div>
一般来说,是否可以以一种特殊的方式获取文本,以便任何 html 标签都被空格替换。 (我可以在之后修剪所有多个空格......)
只需在所有标签之前和之后添加
' '
:
$("*").each(function (index) {
$(this).prepend(' ');
$(this).append(' ');
});
然后处理多个空格:
$.text().replace(/\s{2,}/g, ' ').trim();
//=> "By John Smith"
由于 cheerio 只是 NodeJS 的 jQuery 实现,您可能会发现 这些答案 也很有用。
工作示例:
const cheerio = require('cheerio');
const $ = cheerio.load(`
<div>
By<div><h2 class="authorh2">John Smith</h2></div>
</div>
`);
$("*").each(function (index) {
$(this).prepend(' ');
$(this).append(' ');
});
let raw = $.text();
//=> " By John Smith" (duplicate spaces)
let trimmed = raw.replace(/\s{2,}/g, ' ').trim();
//=> "By John Smith"
您可以使用
cheerio
代替 htmlparser2
。它允许您在解析 HTML 时每次遇到开始标记、文本或结束标记时定义回调方法。
此代码会产生您想要的输出字符串:
const htmlparser = require('htmlparser2');
let markup = `<div>
By<div><h2 class="authorh2">John Smith</h2></div>
</div>`;
var parts = [];
var parser = new htmlparser.Parser({
onopentag: function(name, attributes){
parts.push(' ');
},
ontext: function(text){
parts.push(text);
},
onclosetag: function(tagName){
// no-op
}
}, {decodeEntities: true});
parser.write(markup);
parser.end();
// Join the parts and replace all occurances of 2 or more
// spaces with a single space.
const result = parts.join('').replace(/\ {2,}/g, ' ');
console.log(result); // By John Smith
这是有关如何使用它的另一个示例:https://runkit.com/jfahrenkrug/htmlparser2-demo/1.0.0
Cheerio的text()方法主要用于抓取干净的文本。正如您已经经历过的,这与将 HTML 页面转换为纯文本略有不同。如果您只需要文本进行索引,则可以使用正则表达式替换来添加空格。对于其他一些场景,例如转换为音频,它并不总是有效,因为您需要区分空格和换行符。
我的建议是使用一个库将 HTML 转换为 Markdown。一种选择是调低。
var TurndownService = require('turndown')
var turndownService = new TurndownService()
var markdown = turndownService.turndown('<div>\nBy<div><h2>John Smith</h2></div></div>')
这将打印出:
'By\n\nJohn Smith\n----------'
最后一行是因为 H2 标题。 Markdown 更容易清理,您可能只需要删除 URL 和图像。文本显示也更容易被人类阅读。
如果您想要内容的干净文本表示,我建议使用 lynx (由古腾堡计划使用)或 pandoc。两者都可以安装,然后使用
spawn
从节点调用。与运行 puppeteer 和使用 textContent 或 insideText 相比,这些将提供更清晰的文本表示。
您还可以尝试遍历 DOM 并根据节点类型添加新行。
import "./styles.css";
import cheerio from "cheerio";
const NODE_TYPES = {
TEXT: "text",
ELEMENT: "tag"
};
const INLINE_ELEMENTS = [
"a",
"abbr",
"acronym",
"audio",
"b",
"bdi",
"bdo",
"big",
"br",
"button",
"canvas",
"cite",
"code",
"data",
"datalist",
"del",
"dfn",
"em",
"embed",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"label",
"map",
"mark",
"meter",
"noscript",
"object",
"output",
"picture",
"progress",
"q",
"ruby",
"s",
"samp",
"script",
"select",
"slot",
"small",
"span",
"strong",
"sub",
"sup",
"svg",
"template",
"textarea",
"time",
"u",
"tt",
"var",
"video",
"wbr"
];
const content = `
<div>
By March
<div>
<h2 class="authorh2">John Smith</h2>
<div>line1</div>line2
line3
<ul>
<li>test</li>
<li>test2</li>
<li>test3</li>
</ul>
</div>
</div>
`;
const isInline = (element) => INLINE_ELEMENTS.includes(element.name);
const isBlock = (element) => isInline(element) === false;
const walkTree = (node, callback, index = 0, level = 0) => {
callback(node, index, level);
for (let i = 0; i < (node.children || []).length; i++) {
walkTree(node.children[i], callback, i, ++level);
level--;
}
};
const docFragText = [];
const cheerioFn = cheerio.load(content);
const docFrag = cheerioFn("body")[0];
walkTree(docFrag, (element) => {
if (element.name === "body") {
return;
}
if (element.type === NODE_TYPES.TEXT) {
const parentElement = element.parent || {};
const previousElement = element.prev || {};
let textContent = element.data
.split("\n")
.map((nodeText, index) => (/\w/.test(nodeText) ? nodeText + "\n" : ""))
.join("");
if (textContent) {
if (isInline(parentElement) || isBlock(previousElement)) {
textContent = `${textContent}`;
} else {
textContent = `\n${textContent}`;
}
docFragText.push(textContent);
}
}
});
console.log(docFragText.join(""));
现有答案使用正则表达式或其他库,但这都不是必要的。在 Cheerio 中处理文本节点的技巧是使用
.content()
:
const cheerio = require("cheerio"); // 1.0.0-rc.12
const html = `
<div>
By<div><h2 class="authorh2">John Smith</h2></div>
</div>`;
const $ = cheerio.load(html);
console.log($("div").contents().first().text().trim()); // => By
如果您不确定文本节点将始终是第一个子节点,您可以按如下方式获取所有子节点中的第一个文本节点:
const text = $(
[...$("div").contents()].find(e => e.type === "text")
)
.text()
.trim();
console.log(text); // => By
希望不用说,但是
"John Smith"
部分是标准的Cheerio:
const name = $("div").find("h2").text().trim();
console.log(name); // => John Smith
另请参阅: