逍遥游

StreamSpider爬虫之中文URL

含有中文字符的网址404问题

用浏览器访问网址的时候,浏览器会默认编码URL,例如把中文、空格改成类似%xx的形式,服务器接收到之后再解码得到原URL,这个过程对用户来说是透明的。

用HttpClient爬取网络时,发现出现很多404,其中有很多是含有乱码/中文。抽取其中一个网址,发现在浏览器中访问正常,但是程序就会出现404,查看请求头可以发现实际请求的网址是类似

1
http://tzb.hit.edu.cn/%E5%85%A8%E5%9B%BD%E6%94%BF%E5%8D%8F%E5%8A%9E%E5%85%AC%E5%8E%85/main.psp

的形式,即中文已经被编码过了。

经过反复测试,发现HttpClient访问有些中文URL正常,另外一些就是404,它们之间的区别在于编码不同,UTF-8编码的可以正常访问,而采用其他编码的由于HttpClient事先并不知道对方服务器采用了什么编码,默认按照UTF-8编码URL,对方服务器在解码时就是乱码,自然找不到资源了。

为了解决这个问题,需要对URL进行预处理,把含有URL中的中文按照编码统一处理成 %xx 的形式,这样就不用再操心编码的问题了。

注意,网址中的中文并不 都 是按照相同的编码处理的,理由如下

我们知道,一个有效的网址(可以)包括 协议、主机、端口、地址、参数(即?y=x&z=x),其中,除了参数串其他的都是由web服务器处理的,而web服务器跟网页的编码并不是一致的,所以参数串需要单独区分。查看index_gbk.php,该页是由gbk编码的,点击第一个网址,检查请求头,可以发现这两部分编码结果是不一样的,前一个”中文”是用utf8编码(?),而后一个是gbk。服务器不支持rewrite,所以没法做更多演示。

解决方案

不过,虽然JAVA里有URLEncoder类似的方法,但是它并不能满足我们的需求。因为我们只需要把中文、空格编码,但是URLEncoder会把http://也进行编码,结果已经不是有效的网址了。只好自己实现。好在,网上已经有现成的代码了Android 编码URL中文字符串,不过他没有考虑到上面说的编码问题,稍作修改。

示例代码

我采用的是第三种方案:直接遍历url,找到非字母的字符,用16进制编码。然后顺便解决一个url编码中的陈年老坑:空格被转换成”+”号。

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
public static String encodeUrl(URL url, Charset charset){
String query = url.getQuery();
if(query !=null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < query.length(); i++) {
char c = query.charAt(i);
if (c <= 127) {
sb.append(c);
} else {
byte[] b;
try {
b = String.valueOf(c).getBytes(charset);
} catch (Exception ex) {
b = new byte[0];
}
for (byte aB : b) {
int k = aB;
if (k < 0)
k += 256;
sb.append("%").append(Integer.toHexString(k).toUpperCase());
}
}
}
query = sb.toString();
}
String res = url.getProtocol()+"://";
res+=url.getHost();
if(url.getPort()!=-1){
res+=":"+url.getPort();
}
if(url.getPath()!=null){
res+=url.getPath();
}
if(query != null){
res+= "?"+query;
}
//fix url encode bug
res = res.replaceAll(" ", "%20");
return res;
}

没有对地址部分编码,因为我的环境是utf8,程序能自动转换,而且中文看起来比较顺眼

测试:

1
2
input: http://apache.local/中文utf8.php?key=中文
output: http://apache.local/中文utf8.php?key=%D6%D0%CE%C4

完整代码:Chinese URL

参考

Android 编码URL中文字符串


多说停止服务,disqus引导注册太过分,暂时不上评论系统了。有机会自己造轮子吧。邮箱:input@newnius.com