问题来源:用httpclient抓取数据过程中出现了中文乱码,即输入的时候一堆问号。
问题:有没一种方法可以高效判定抓取来的页面的编码方式,直接存成Java String 中?
问题解决过程:
因为功能简单,所以只要写出自己的发送get请求并获取response中的文本中的方法就足够了。
代码比较简单,Google一下到处都有,这里不再赘述。方法中遇到的问题主要是如何获取页面中编码。
分析UTF-8和GB2312的区别,具体的编码方式自行wikipedia,而和问题相关的内容就是:GB2312每个字符有两个字节表示,UTF-8可以有一,二,三个字节。并且两种编码方式有部分重叠,这样,本来设想的逐个字节读取过程中判断是哪种编码方式的方案也破灭了。
分析人文角度:网页是给人看的,如果人都不能识别,那么网页也就没什么价值了。而且如果人不能识别网页中内容,那么网页的设计者肯定会想方法使网页能够让人识别,更确切地说是让浏览器识别。那解决方法有了:看浏览器怎么识别编码的。
HTML文档编码方式有两种,在headers中有content-type字段,规定编码方式,在HTML正文中有meta 也可以规定编码方式。所以要看浏览器是怎么根据这些内容编码的。测试内容以及结果如下:页面的内容选用ANSI编码,而在headers中指定charset=utf8,无论meta中charset指定什么,浏览器不能正常识别。headers中不指定,而meta中charset为gb2312时,页面正常显示,charset为utf8时,页面乱码,当在headers和meta中都不指定时,无论页面内容采用ANSI还是utf8,页面都能正常显示。
实验结论:浏览器判定页面编码方式顺序为:①headers;②meta中charset③根据自己算法,如果优先级高的方式指定了编码,则不再考虑低级的方式,而关于第三中方式,有以下的链接说明
http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E9%9B%86%E6%8E%A2%E6%B5%8B
所以就目前方式来说问题解决方式为
一。根据返回请求的headers判断编码,如果有,则结束;若没有,进入二。
二。读取前m(个人取的是4096,没依据,凭喜好)个字节,看有没有charset,如果有,结束,如果没有,视情况进入第三步。
三。 如果对精度有很严格要求,自己钻研字符集探测算法。
补上代码:
StringBuilder sb = new StringBuilder();
byte[] bytes = new byte[4096];
int size = 0;
InputStream is = null;
try {
URL url = new URL(webSite);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
is = conn.getInputStream();
while (size < 4096) {
size += is.read(bytes, size, 4096 - size);
}
String meta = new String(bytes, 0, size, "GB2312");
String headers = conn.getContentType();
String charset = "";
Pattern p = Pattern.compile("charset=\"?(gb2312|utf-8)\"?", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(headers);
if (m.find()) {
charset = m.group(1);
}
if (charset == "" || charset == null) {
m = p.matcher(meta);
if (m.find())
charset = m.group(1);
}
charset.replaceAll("\"", "");
if (charset == "" || charset == null) {
System.out.println(webSite + "is not available");
return "";
}
sb.append(new String(bytes, 0, size, charset));
while ((size = is.read(bytes)) > 0) {
String str = new String(bytes, 0, size, charset);
sb.append(str);
}
conn.disconnect();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();