使用Java爬虫爬取中国青年网:http://news.youth.cn/sh的社会新闻模块时,出现部分乱码问题,如下图所示:
我的爬虫使用的是自己编写的一个简单的网页编码探测器来获取网页编码的,按理说不应该出现乱码问题,下载网页的工具类代码如下:(下载工具类和字符探测工具类的两段代码不重要,不想看代码可以跳到第二部分)
一、测试工具类是否出错
/**
*Httpclient下载工具类
*/
public class WebPageDownloadUtil_HtppClient {
//获取网页源码,defaulstCharset参数仅用于获取网页字节数组
private static String getHtmlSource(HttpEntity entity, String defaultCharset)
throws IOException {
String htmlSource = null;
String charset = null;
//将网页源码转换为字节数组
InputStream in = entity.getContent();
byte[] bytes = IOUtil.convertInputStreamToBytes(in);
//尝试从header中读取charset,若未读取到,从网页字节数组中读取
if((charset=CharsetUtil.getCharsetFromHeader(entity))==null) {
charset = CharsetUtil.getCharsetFromByteArray(bytes, defaultCharset);
}
//根据读取到的charset编码王爷字节数组
htmlSource = new String(bytes, charset);
return htmlSource;
}
public static String download(String url) throws Exception {
CloseableHttpClient client = HttpClients.createDefault();
HttpGet get = new HttpGet(url);
HttpResponse resp = client.execute(get);
HttpEntity entity = resp.getEntity();
return getHtmlSource(entity, StaticValue.DEFAULT_ENCODING);
}
public staic void main(String[] args){
download("http://news.youth.cn/sh);
}
}
下载工具类中使用的编码探测工具类代码如下:
/**
* charset工具类
*/
public class CharsetUtil {
//URLConnection从header中获取charset
public static String getCharsetFromHeader(URLConnection con) {
String charset = null;
String contentType = con.getContentType();
//先从header中取
if(contentType!=null && contentType.toLowerCase().contains("charset")) {
String line = contentType.toLowerCase();
String[] ss = line.split(";");
for(String s: ss) {
if(s.contains("charset")) {
String[] chArr = s.split("=");
charset = chArr[1].trim();
}
}
}
return charset;
}
//httpclient从header中获取charset
public static String getCharsetFromHeader(HttpEntity entity) {
Charset ch = ContentType.getOrDefault(entity).getCharset();
if(ch!=null) return ch.toString();
return null;
}
//从网页字节数组中获取charset
public static String getCharsetFromByteArray(byte[] bytes, String defaultCharset)
throws IOException {
String charset = null;
//将字节数组转换为BufferedReader流
InputStreamReader isr = new InputStreamReader(new
ByteArrayInputStream(bytes),defaultCharset);
BufferedReader br = new BufferedReader(isr);
//从流中读取charset
String temp = null;
while((temp = br.readLine())!=null) {
//如果读取到了charset或者head标签已经结束,那么不再继续读取
if(temp.toLowerCase().contains("charset") ||
temp.toLowerCase().contains("</head>")) {
charset = CharsetUtil.getCharset4Line(temp);
break;
}
}
br.close();
//如果未读取到charset,返回默认charset
charset = (charset==null ? defaultCharset : charset);
return charset;
}
//从字符串中解析出charset
public static String getCharset4Line(String line) {
String charset = null;
String regex = "charset=\"?(.+?)\\s?\"?\\s?/?>";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(line);
if(matcher.find()) {
charset = matcher.group(1).trim();
}
return charset;
}
}
上面的代码先尝试从header中获取Content-Type中的编码,如果获取不到,则从网页字节数组中读取<meta>标签中的charset属性。
在下载代码中添加打印语句,输出获取到的网页编码:
而源网页中的编码同样是gb2312:
这证明我写的工具类并没有错。
二、测试jsoup解析工具是否出了问题
为了找出问题,我编写了一个测试方法,并将原网页下载到本地,以IO流读取本地文件的形式,来解析这个网页
FileInputStream fis = new FileInputStream("d:/test.html");
InputStreamReader isr = new InputStreamReader(fis,"gb2312");
BufferedReader br = new BufferedReader(isr);
String line = null;
StringBuilder sb = new StringBuilder();
while((line=br.readLine())!=null) {
sb.append(line);
}
br.close();
//解析
HtmlParserInterface parser = new HtmlParser4JsoupImpl();
List<ParserResult> resultList = parser.parseHtml(sb.toString());
resultList.forEach((k)->{
System.out.println(k);
});
在这里我分别使用了网页原本的gb2312编码和gbk编码,最终发现,使用gb2312编码会导致部分中文乱码,而是用gbk则没有这个问题。
GB2312测试
GBK测试
这就让人非常奇怪了,用网站本身使用的gb2312编码读取反而会导致乱码问题,而使用gbk却不会出现乱码。
查了一下发现有人说gb2312中并没有收录“珺”这个字,那么为什么这个网页本身却能够正常显示呢,真是好奇怪。
暂时找不到原因,只能先修改字符探测工具,碰到gb2312编码,使用gbk来读取。