美文网首页我爱编程
NodeJs抓取页面html()方法乱码

NodeJs抓取页面html()方法乱码

作者: 安_6dd1 | 来源:发表于2017-03-29 14:27 被阅读0次

    如何用 Nodejs 分析一个简单页面一文中,我们爬取了博客园首页的 20 篇文章标题,输出部分拼接了一个字符串:

    var$ = cheerio.load(sres.text);varans =''; $('.titlelnk').each(function(index, item){var$item = $(item); ans += $item.html() +'

    '; });// 将内容呈现到页面res.send(ans);

    页面呈现良好:

    但是查看网页源代码,却看到这样的情景:

    什么鬼?我们让问题再清晰些,试着把爬虫代码稍做修改:

    var$ = cheerio.load(sres.text);varans = []; $('.titlelnk').each(function(index, item){var$item = $(item); ans.push($item.html()); });// 将内容呈现到页面res.send(ans);

    这输出的是什么玩意儿?

    乱码?不,是 HTML 实体编码!

    HTML 实体编码

    在 HTML 中,某些字符是预留的,比如不能使用小于号(<)和大于号(>),这是因为浏览器会误认为它们是标签。如果希望正确地显示预留字符,我们必须在 HTML 源代码中使用字符实体(character entities)。当然还另一个重要原因,有些字符在 ASCII 字符集中没有定义,因此需要使用字符实体来表示,比如中文。

    字符实体类似这样:

    &entity_name; 或者 &#entity_number;

    如需显示小于号,我们必须这样写:<或<。前者(实体名)易于记忆,而后者(实体数字)在浏览器中的支持较好。

    HTML 中常见的需要替换成字符实体的字符有 4 个,分别是<、>、&以及"。为此,我们可以简单写个 escapeHTML 函数(使得网页上可以正确显示这 4 个字符,而不会被误认为是标签):

    functionescapeHTML(text){varreplacements= {"<":"<",">":">","&":"&",""": """}; return text.replace(/[<>&"]/g,function(character){returnreplacements[character]; }); }

    更多关于 HTML 实体编码的内容可以参考HTML 字符实体

    Solution

    不仅是 "<" ">" 这样的能编码,所有字符均能编码,这也是出现 "乱码" 的原因。在文章开头的例子中,其实它把该 target 标签内的所有东西(包括中文)都给编码了。

    而最开始的代码(字符串输出)之所以没有 "乱码",完全是因为浏览器自动帮你解码了。(如果存在于 HTML 代码中,会被自动解码)

    知道了原因,我们可以从两个方向解决问题。

    首先,我们可以不对其内容进行编码。用text()方法取代html()方法:

    $('.titlelnk').each(function(index, item){var$item = $(item); ans.push($item.text()); });

    很简单并且完美地解决了这个问题。

    或者我们关闭 cheerio 中的.html()方法 转换实体编码的功能(2016-01-25 add):

    var$ = cheerio.load(sres.text, {decodeEntities:false}); $('.titlelnk').each(function(index, item){var$item = $(item);console.log($item.html()); });

    如果说不能从编码的角度解决,我们可以试着解码。

    方法一:

    创建空标签,将编码内容用 html() 方法塞入,用 text() 取出,转换过程让第三方完成(当然前提是获取了 $ 对象):

    functionhtmlDecode(str){vart = $("

    "); t.html(str);returnt.text(); }var$ = cheerio.load(sres.text);varans = []; $('.titlelnk').each(function(index, item){var$item = $(item); ans.push(htmlDecode($item.html())); });// 将内容呈现到页面res.send(ans);

    方法二:

    根据编码转换规则,用正则 decode:

    functionhtmlDecode(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){returnString.fromCharCode(parseInt($2, $1?16:10)); });returnstr; }var$ = cheerio.load(sres.text);varans = []; $('.titlelnk').each(function(index, item){var$item = $(item); ans.push(htmlDecode($item.html())); });// 将内容呈现到页面res.send(ans);

    Encode & Decode

    事情到此似乎可以告一段落,我们找到了问题的原因,也找到了解决办法。但是,HTML 实体编码,它到底是如何编码的?

    我们任意取一条标题:

    前端备忘录 — IE 的条件注释

    编码后为:

    前端备忘录 —IE的条件注释

    中文的编码结果开头都是 &#x。试着用charCodeAt()取得 "前" 字的 unicode 编码大小,然后将它转成 16 进制,正是 524d !看来和escape()相似,又是一次十六进制的转换。

    但是英文却没有被转,这点和 escape() 也神似。唯一不同的是 escape 会将空格转为%20,而 HTML 编码并没有。

    而且 HTML 编码甚至会将 自动编码成 ,这也就意味着如果要手写个 HTML 编码函数,需要将所有字符实体的映射都找出来,而且对于 &XXXX 形式的,似乎还要作个校验(确认是实体集还是普通的字符串)。

    而 HTML 解码则相对来说简单写,只需将 &#xXXX 进行转换,详细代码可以参考 Solution 一节的正则。

    事实上,HTML 编码并不一定要转成十六进制,十进制也可以。还是以 "前" 为例,它的十进制 unicode 码为 21069,完全可以用前来代替前。

    最后还有两个客户端的编码、解码函数:

    functionHtmlEncode(str){vart =document.createElement("div"); t.textContent ? t.textContent = str : t.innerText = str;returnt.innerHTML; }functionHtmlDecode(str){vart =document.createElement("div"); t.innerHTML = str;returnt.textContent || t.innerText; }

    真的是吃一堑长一智,以后碰到 "&#x" 开头的一些编码,十有八九是 HTML 的实体编码,再也不用担心了!

    学习更多

    转自:http://www.cnblogs.com/zichi/p/5135636.html

    相关文章

      网友评论

        本文标题:NodeJs抓取页面html()方法乱码

        本文链接:https://www.haomeiwen.com/subject/iwrtottx.html