Cheerio - 获取带有替换为空格的 html 标签的文本

Posted

技术标签:

【中文标题】Cheerio - 获取带有替换为空格的 html 标签的文本【英文标题】:Cheerio - Get text with html tags replaced by white spaces 【发布时间】:2019-11-19 21:24:13 【问题描述】:

今天我们使用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 示例无效,因为包含 John Smith 的 div 都是结束标签。 肯定与真正的问题无关。谢谢,纠正错误 在我看来,您只是没有应用正确的选择器。取你已经用过的,加上`h2`,分别获取header的内容。 @Mathieu 你一定要用cheerio吗? 【参考方案1】:

您可以使用以下正则表达式将所有 HTML 标记替换为空格:

/<\/?[a-zA-Z0-9=" ]*>/g

所以当你用这个正则表达式替换你的 HTML 时,它可能会产生多个空格。在这种情况下,您可以使用replace(/\s\s+/g, ' ') 将所有空格替换为单个空格。

查看结果:

console.log(document.querySelector('div').innerHTML.replaceAll(/&lt;\/?[a-zA-Z0-9=" ]*&gt;/g, ' ').replace(/\s\s+/g, ' ').trim())
<div>
  By<div><h2 class="authorh2">John Smith</h2></div>
</div>

【讨论】:

【参考方案2】:

您可以使用纯 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>

【讨论】:

【参考方案3】:

一般来说,是否有可能以一种特殊的方式获取文本,以便将任何 html 标记替换为空白。 (我可以在之后修剪所有多个空格...)

只需在所有标签前后添加' '即可:

$("*").each(function (index) 
    $(this).prepend(' ');
    $(this).append(' ');
);

然后处理多个空格:

$.text().replace(/\s2,/g, ' ').trim();
//=> "By John Smith"

由于 cheerio 只是 NodeJS 的 jQuery 实现,您可能会发现 these answers 也很有用。


工作示例:

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(/\s2,/g, ' ').trim();
//=> "By John Smith"

【讨论】:

【参考方案4】:

您可以使用htmlparser2,而不是cheerio。它允许您在解析 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

【讨论】:

【参考方案5】:

Cheerio 的 text() 方法主要用于从抓取中获取干净的文本。正如您已经体验过的那样,这与将 HTML 页面转换为纯文本有点不同。如果您只需要文本进行索引,则使用正则表达式替换添加空格将起作用。对于其他一些场景,例如转换为音频,它并不总是有效,因为您需要区分空格和换行。

我的建议是使用一个库来将 HTML 转换为 markdown。一种选择是turndown。

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 和图像。文字显示也更容易被人类阅读。

【讨论】:

【参考方案6】:

如果您想要内容的清晰文本表示,我建议使用lynx(古腾堡项目使用)或pandoc。两者都可以安装,然后使用spawn 从节点调用。这些将提供比运行 puppeteer 和使用 textContent 或 innerText 更清晰的文本表示。

您也可以尝试遍历 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 - 获取带有替换为空格的 html 标签的文本的主要内容,如果未能解决你的问题,请参考以下文章

Cheerio 获取元素外部 html

Cheerio 从 .text() 获取包含中断和 H 标记的内容

使用cheerio在没有孩子的父母中获取文本

PHP把空格、换行符、中文逗号等替换成英文逗号的正则表达式

正则表达式替换以删除 html 标记之间的空格

选择带有cheerio属性的元素