HttpClient是Apache基金下jakarta commons項目中的一個小項目,該項目封裝了對遠程地址下載的一些功能,最新版本爲3.0。該項目地址:http://jakarta.apache.org/commons/httpclient
最近在編寫Spider的時候就用到了HttpClient。在使用過程中發現一個有趣現象:有些URL的編碼方式是utf-8,有些URL的編碼方式是gbk。他總能夠正確識別,但是有些他又不能識別(抓取回來後是亂碼)。調用的是:httpMethod.getResponseBodyAsString(); 方法。
在進行進一步分析時,發現他對在http頭信息中有charset描述的就正確正常識別。如:
HTTP/1.1 200 OK
Connection: close
Content-Type: text/html; charset=utf-8
Set-Cookie: _session_id=066875c3c0530c06c0204b96db403560; domain=javaeye.com; path=/
Vary: Accept-Encoding
Cache-Control: no-cache
Content-Encoding: gzip
Content-Length: 8512
Date: Fri, 16 Mar 2007 09:02:52 GMT
Server: lighttpd/1.4.13
而沒有charset描述信息時,就會是亂碼。再查看相關文檔時,可以指定URL的編碼方式。如:HttpClientParams.setContentCharset("gbk");,指定了編碼後,就能夠正確識別對應編碼的URL了。問題出現了,因URL編碼不一樣,Spider不可能把URL的編碼方式寫死。並且只有在抓取回來後才知道編碼是否正確。於是再仔細研究一下httpclient的源代碼,發現他使用編碼的順序是:http頭信息的charset,如果頭信息中沒有charset,則查找HttpClientParams的contentCharset,如果沒有指定編碼,則是ISO-8859-1。
- /**
- * Returns the character set from the Content-Type header.
- *
- * @param contentheader The content header.
- * @return String The character set.
- */
- protected String getContentCharSet(Header contentheader) {
- LOG.trace("enter getContentCharSet( Header contentheader )");
- String charset = null;
- if (contentheader != null) {
- HeaderElement values[] = contentheader.getElements();
- // I expect only one header element to be there
- // No more. no less
- if (values.length == 1) {
- NameValuePair param = values[0].getParameterByName("charset");
- if (param != null) {
- // If I get anything "funny"
- // UnsupportedEncondingException will result
- charset = param.getValue();
- }
- }
- }
- if (charset == null) {
- charset = getParams().getContentCharset();
- if (LOG.isDebugEnabled()) {
- LOG.debug("Default charset used: " + charset);
- }
- }
- return charset;
- }
- /**
- * Returns the default charset to be used for writing content body,
- * when no charset explicitly specified.
- * @return The charset
- */
- public String getContentCharset() {
- String charset = (String) getParameter(HTTP_CONTENT_CHARSET);
- if (charset == null) {
- LOG.warn("Default content charset not configured, using ISO-8859-1");
- charset = "ISO-8859-1";
- }
- return charset;
- }
這個該死的iso-8859-1害了多少人啊(Tomcat對提交的數據處理默認也是iso-8859-1)!!
經過仔細思考後,決定httpclient再封裝一次,思路如下:
先不設定HttpClientParams的charset;
executemethod後,再檢查http頭信息中的charset是否存在;
如果charset存在,返回httpMethod.getResponseBodyAsString(); ;
如果charset不存在,則先調用httpMethod.getResponseBodyAsString();得到html後,再分析html head的meta的charset <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">;
從meta中分析出charset後,設置到HttpClientParams的contentCharset;
再調用httpMethod.getResponseBodyAsString(),並返回該值。
經過以上思路處理後,發現抓回來的URL再也沒有亂碼了。爽!
以上步驟中,就是第四步稍微麻煩一些,不過,也可以利用第三方的html paser工具來分析meta的charset!