鱼喃

听!布鲁布鲁,大鱼又在那叨叨了

告别中文乱码,自动检测网页编码的JAVA爬虫

麻烦的中文编码GB2312

写了个爬虫系统,结果运行没一会儿就发现队列里出现了乱码的url。检查发现网页是用gb2312编码的,我读取的时候是用utf8,所以中文就乱码了。原因找到了,开始fix。

一般而言,指定网页编码的地方有三处:

  • http header头部的Content-Type (Content-Type:text/html; charset=UTF-8)
  • 源码 meta charset (<meta charset="UTF-8"/>)
  • 源码 http-equiv (<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />)

其中 http header是服务器指定的,优先级最高,剩下两个一般不会同时出现,顺序无所谓。

策略是,先把网页下载下来,然后依次找上述三个位置,找到编码就用该编码将字符串以该编码转换成utf8,但是实际测试发现,不管怎么转都是乱码,最好的情况下确实转成了中文,但是跟原来完全不是同一个字啊。例如 “校内办公――电子地图” 变成了 “校锟节办公锟斤拷锟斤拷锟斤拷锟接碉拷图”。

网上找到的关于java编码的问题都是说需要在读取的时候指定编码,不然就会产生中文乱码。然而,我这是爬虫,在下载之前我并不知道它用的是什么编码啊。

思来想去,无论什么编码,英文字符显示都是正常的,可以用字节流先以字节的形式把网页存下来;然后转成字符串获得编码;最后再利用正确的编码对原字节数组进行编码转换。

示例代码

核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private void parse(HttpEntity entity){
/* 先尝试从响应头部里找编码 */
ContentType contentType = ContentType.get(entity);
Charset cs = contentType.getCharset();
if (cs != null) {
charset = cs.toString();
logger.info("Detect charset in header:"+charset);
}

try {
/* 先以字节流的形式把源码读到内存 */
InputStream is = entity.getContent();
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
byte[] temp = new byte[1024];
int size;
while ((size = is.read(temp)) != -1) {
os.write(temp, 0, size);
}

/* 如果header里没有指定,从meta里找 */
if (charset == null) {
Document doc = Jsoup.parse(os.toString());
Elements metaTags = doc.getElementsByTag("meta");
for (Element metaTag : metaTags) {
String content = metaTag.attr("content");
String http_equiv = metaTag.attr("http-equiv");
charset = metaTag.attr("charset");
if (!charset.isEmpty()) {
logger.info("Detect charset in meta:"+charset);
break;
}
if (http_equiv.toLowerCase().equals("content-type")) {
charset = content.substring(content.toLowerCase().indexOf("charset") + "charset=".length());
logger.info("Detect charset in http-equiv:"+charset);
break;
}
}
/* 如果都没有找到,默认使用UTF-8 */
if (charset == null || charset.isEmpty())
charset = "utf-8";
}
/* 用指定的编码来解析 */
html = new String(os.toByteArray(), charset);
}catch (Exception ex){
errMsg = ex.getMessage();
}
}

完整代码见:CRSpider.java

参考

HTML5标准学习 – 编码

Java获取网页编码

Cpdetector识别网页编码 解决Java爬虫乱码问题

java中文乱码解决之道(五)—–java是如何编码解码的

HttpClient 4.0的使用详解

java中GBK编码格式转成UTF8,用一段方法实现怎么做?