NodeJS. 如何处理含有特殊字符编码的情况?

7

我遇到了字符编码的困难。我正在尝试抓取以下网址:

http://www.google.com/movies?near=Montreal&date=0

我的代码看起来像这样:

var http = require('http');
var url = require('url');
var Iconv  = require('iconv').Iconv;

var location = 'montreal';

var googleMovies = url.parse("http://www.google.com/movies?near=" + location);

var req = http.request(googleMovies, function(response) {
    var str = '';
    response.on('data', function(chunk) {
        str += chunk;
    });
    response.on('end', function() {

        var iconv = new Iconv('latin1', 'UTF-8');
        str = iconv.convert(str).toString();

        console.log(str);
    });
});
req.end()

我最初尝试时没有使用:

    var iconv = new Iconv('latin1', 'UTF-8');
    str = iconv.convert(str).toString();

但这会导致出现�字符。

我已在此页面上测试了上述源代码:

http://nlp.fi.muni.cz/projects/chared/

它似乎将其检测为Latin1编码,但可能存在问题。

3个回答

10

这些�字符来自以下拼接操作:

response.on('data', function(chunk) {
    str += chunk;
});

这会将每个chunk转换为一个String,使用默认的utf8编码。在此时,任何在Buffer中不是有效UTF-8序列的内容都将丢失并由�替换。

convert()之后,您应该保留chunk作为Buffer。它们可以被收集到一个Array中,并与Buffer.concat()组合。

var chunks = [];

response.on('data', function (chunk) {
    chunks.push(chunk);
});

response.on('end', function () {
    var iconv = new Iconv('latin1', 'UTF-8');
    var str = iconv.convert(Buffer.concat(chunks)).toString();
    console.log(str);
});

2
如果您将User-Agent设置为桌面浏览器,HTML中的元标签和响应头中的Content-Type将设置charset为UTF-8而不是latin1。例如:
var dest = url.parse('http://www.google.com/movies?near=montreal');
dest.headers = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36',
};

http.get(dest, function(response) {
  var str = '';

  response.on('data', function(chunk) {
    str += chunk;
  }).on('end', function() {
    console.log(str);
  }).setEncoding('utf8');
});

这个解决方案也是可行的。看起来编码取决于用户代理(这正常吗?)。我更喜欢@Jonathan Lonowski的解决方案,因为它更好地解释了字符集转换,而你的解决方案则专注于修复特定服务的源输出。 - Tomasz Rakowski
1
这种情况比应该出现的要普遍得多,但很多后端都会检查“User-Agent”,并相应地改变行为(例如旅游网站根据您的操作系统更改价格,网站仅发送已知与浏览器兼容的资源等)。 - mscdex

1
一个 Buffer 的默认编码是 UTF-8,这是一种可变宽度编码系统。ASCII 范围之后的字符使用多个字节进行编码。如果你收到了 latin1 特定的字符(代码点 > 127),它们将具有第一个位设置,UTF-8 解码器将把它们视为多字节字符,最终导致未映射的代码点(显示为�)。 iconv 有一个流解码器,你可以将响应流传输到该解码器中。
http.request(googleMovies, function (response) {
    var iconv = new Iconv('latin1', 'UTF-8');
    response.pipe(iconv).pipe(process.stdout);
    //or response.pipe(iconv).on('data', console.log);
}).end();

你的解决方案看起来很优雅,我认为它应该可以工作,但出于某种原因它没有。你能测试一下看看它是否能够工作吗? - Tomasz Rakowski
我正在end事件中记录接收到的参数,而不是在data事件中。将其管道传输到process.stdout也应该可以工作。 - c.P.u1
谢谢,现在它工作了,它将数据倾倒到标准输出。你有办法将其导入到一个可以进一步操作的字符串中吗?我需要一个'str'变量来进一步处理它。关于“导入到字符串”的所有解决方案都与@Jonathan Lonowski的示例类似。 - Tomasz Rakowski
你需要使用相同的方法:监听 data,拼接字符串并在 end 上使用它。只是这一次你需要将监听器附加到 iconv 上。 - c.P.u1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接