在 Node 層利用 cheerio 解析網頁時,輸出的中文內容都是以 &#x 開頭的一堆像亂碼一樣的東西,嘗試過各種編碼都無效,而且神奇的是,將這一堆“亂碼”保存成網頁后,通過瀏覽器打開又可以正常顯示。這到底是什么👻??
縮減后的示例代碼如下:
const cheerio = require('cheerio');
const $ = cheerio.load('<div id="content">你好</div>')
console.log($('#content').html()) //你好
其實,上面那一堆亂碼一樣的東西,它的學名叫實體編碼 entity code。
下面引用下知乎搜到的答案。
在 HTML 中,某些字符是預留的,例如小於號「<」、大於號「>」等,瀏覽器會將它們視作標簽。如果想要在HTML中顯示這些預留字符,我們就要用到字符實體(character entities)。我們比較熟悉的字符實體有空格「 」,小於號「<」,大於號「>」等。這樣的格式比較語義化,容易記憶,但其實字符實體有其他的格式:
&name; &#dddd; &#xhhhh;
- 這三種轉義方式都稱作 character reference,第一種是 character entity reference,「&」符號后接預先定義好的 entity 名稱。
- 后兩種是 numeric character reference,數字取值為目標字符的 Unicode code point;以「&#」開頭的后接十進制數字,「&#x」開頭的后接十六進制數字。
從 HTML4 開始,numeric character reference 以 Unicode 為准,與文檔編碼無關。「你好」二字分別是 Unicode 字符 U+4F60 和 U+597D,十六進制表示的 code point 數值「4F60」和「597D」,同時也就是十進制的「20320」和「22909」。所以
在HTML中輸入
你好
你好
都會顯示為“你好”。
知道原因后,那么如何解決上述的問題呢?
方法一:使用cheerio提供的屬性
cheerio默認會對entity進行decode,我們只需要關閉該功能即可
const cheerio = require('cheerio');
const $ = cheerio.load('<div id="content">你好</div>', { decodeEntities: false })
console.log($('#content').html()) // 你好
方法二:手動decode
function decode(str) { // 一般可以先轉換為標准 unicode 格式(有需要就添加:當返回的數據呈現太多\\\u 之類的時) str = unescape(str.replace(/\\u/g, "%u")); // 再對實體符進行轉義 // 有 x 則表示是16進制,$1 就是匹配是否有 x,$2 就是匹配出的第二個括號捕獲到的內容,將 $2 以對應進制表示轉換 str = str.replace(/&#(x)?(\w+);/g, function($, $1, $2) { return String.fromCharCode(parseInt($2, $1? 16: 10)); }); return str; }
