编码问题:爬取非英语网站

3
我想要将网页内容作为字符串获取,我发现了这个问题,其中涉及如何编写基本网络爬虫的解决方法,该解决方法声称(并似乎)处理了编码问题,但是提供的代码仅适用于美国/英文网站,无法正确处理其他语言。

下面是一个完整的Java类,演示了我的意思:

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class I18NScraper
{
    static
    {
        System.setProperty("http.agent", "");
    }

    public static final String IE8_USER_AGENT = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; WOW64; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; InfoPath.2)";

  //https://dev59.com/T3M_5IYBdhLWcg3wcCnc
    private static final Pattern CHARSET_PATTERN = Pattern.compile("text/html;\\s+charset=([^\\s]+)\\s*");
    public static String getPageContentsFromURL(String page) throws UnsupportedEncodingException, MalformedURLException, IOException {
        Reader r = null;
        try {
            URL url = new URL(page);
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestProperty("User-Agent", IE8_USER_AGENT);

            Matcher m = CHARSET_PATTERN.matcher(con.getContentType());
            /* If Content-Type doesn't match this pre-conception, choose default and 
             * hope for the best. */
            String charset = m.matches() ? m.group(1) : "ISO-8859-1";
            r = new InputStreamReader(con.getInputStream(),charset);
            StringBuilder buf = new StringBuilder();
            while (true) {
              int ch = r.read();
              if (ch < 0)
                break;
              buf.append((char) ch);
            }
            return buf.toString();
        } finally {
            if(r != null){
                r.close();
            }
        }
    }

    private static final Pattern TITLE_PATTERN = Pattern.compile("<title>([^<]*)</title>");
    public static String getDesc(String page){
        Matcher m = TITLE_PATTERN.matcher(page);
        if(m.find())
            return m.group(1);
        return page.contains("<title>")+"";
    }

    public static void main(String[] args) throws UnsupportedEncodingException, MalformedURLException, IOException{
        System.out.println(getDesc(getPageContentsFromURL("http://yandex.ru/yandsearch?text=%D0%A0%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2&lr=223")));
    }
}

输出结果如下:

???????????&nbsp;&mdash; ??????: ??????? 360&nbsp;???&nbsp;???????

尽管它应该是这样的:
Результатов&nbsp;&mdash; Яндекс: Нашлось 360&nbsp;млн&nbsp;ответов

你能帮我理解我做错了什么吗?尝试强制使用UTF-8并没有帮助,尽管它是源代码和HTTP标头中列出的字符集。


你试过使用Apache Http Client 4.x吗?我发现它更加舒适和稳定。应该可以处理大部分编码问题,但是Joel在下面提到的<meta>元素的处理仍然取决于你自己,不过EntityUtils可以帮你解决很多问题。 - Philipp Reichart
你得到的是'?'而不是U+FFFD,这说明了一些问题。可能存在对ISO-8859-1的隐式解释。许多标准库的部分默认使用此编码。 - wberry
你如何知道解码出现了问题,而不是调试输出的编码有误?在返回字符串之前,你应该打印字符的数字值并检查它们。 - erickson
看起来这是一个特定于操作系统的问题。在我的 Mac 上运行,它输出 ????,但在我的 Linux 机器上正常运行。前几个字符是 10 1056 1077 1079 1091 1083 1100 1090 1072 1090 1086 1074 - 不确定如何解释这些,但它们实际上不是问号。 - dimo414
3个回答

2
确定正确的字符集编码可能有些棘手。
您需要结合以下两点来完成:
a)HTML META Content-Type 标签:
<META http-equiv="Content-Type" content="text/html; charset=EUC-JP">
b) HTTP响应头:
Content-Type: text/html; charset=utf-8

c) 从字节中检测字符集的启发式方法(请参见此问题

使用这三种方法的原因是:

  1. (a) 和 (b) 可能会丢失
  2. META Content-Type 可能错误(请参见此问题

如果 (a) 和 (b) 都没有,该怎么办?

在这种情况下,您需要使用一些启发式方法来确定正确的编码 - 请参见此问题

我认为以下顺序最可靠地识别 HTML 页面的字符集编码:

  1. 使用 HTTP 响应头 Content-Type(如果存在)
  2. 对响应内容字节使用编码检测器
  3. 使用 HTML META Content-Type

但您可以选择交换第二步和第三步。


1
你遇到的问题是你的 Mac 编码不支持 Cyrillic 字符集。我不确定在 Oracle JVM 上是否也是这样,但当 Apple 生产自己的 JVM 时,Java 的默认字符编码为 MacRoman。
当你启动程序时,请指定 file.encoding 系统属性以将字符编码设置为 UTF-8(这是 Mac OS X 默认使用的)。请注意,您必须在启动时设置它:java -Dfile.encoding=UTF-8 ...;如果您通过编程方式设置它(使用调用 System.setProperty()),那么就太晚了,设置将被忽略。
每当 Java 需要将字符编码为字节时,例如当它将文本转换为字节以写入标准输出或错误流时,它将使用默认值,除非您明确指定其他值。如果默认编码无法编码特定字符,则会替换为适当的替换字符。
如果编码可以处理Unicode替换字符U+FFFD(�),那么就使用它。否则,问号(?)是常用的替换字符。

我在我的iMac上进行了测试,在Java版本“1.6.0_26”上,默认编码仍然是“MacRoman”。即使我的LANG设置为“en_US.UTF-8”,这仍然是正确的。 - erickson
添加该系统属性标志后,输出如下内容: 时间戳: 2021年3月10日 14:53:298 UTC - dimo414

0

Apache Tika 包含了你想要的实现。许多人用它来做这个。你也可以看看 Apache Nutch。另一方面,那样的话,你就不必自己实现爬虫了。


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