基於HttpClient4.5實現網路爬蟲
個人部落格站已經上線了,網址 www.llwjy.com ~歡迎各位吐槽~
-------------------------------------------------------------------------------------------------
在開始之前先打一個小小的廣告,自己建立一個QQ群:321903218,點選連結加入群【Lucene案例開發】,主要用於交流如何使用Lucene來建立站內搜尋後臺,同時還會不定期的在群內開相關的公開課,感興趣的童鞋可以加入交流。
寫在開始之前
這裡做一個簡短的說明,之前在部落格《基於HttpClient實現網路爬蟲~以百度新聞為例
獲取HttpClient
在4.5版本中,獲取HttpClient例項和之前還是有很大區別的,這裡引用了一個PoolingHttpClientConnectionManager類,我們就通過這個類來介紹如何來建立HttpClient。這個類是Http連結管理類,因此在整個工程中我們將其定義為靜態變數。
private static PoolingHttpClientConnectionManager cm;
在靜態方法中對其實現初始化的操作。
這樣就完成了PoolingHttpClientConnectionManager類的例項的初始化,下面就通過cm來獲取HttpClient。static { ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory(); LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory(); Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", plainsf) .register("https", sslsf) .build(); cm = new PoolingHttpClientConnectionManager(registry); //最大連線數 cm.setMaxTotal(maxTotal); //每個路由基礎的連線 cm.setDefaultMaxPerRoute(defaultMaxPerRoute); //上面兩句中的變數可以通過類的屬性來確定 }
private HttpClient getHttpClient() {
return HttpClients.custom().setConnectionManager(cm).build();
}
這裡就很簡單的通過一個語句就可以獲取HttpClient,這裡就有一個疑問了,在3.0中,我們設定了HttpClient的連結超時時間以及資料的讀取時間,這裡怎麼沒有設定呢?請求時間設定
在4.5中要通過RequestConfig來設定請求相關的一些引數,如下:
private RequestConfig getRequestConfig() {
return RequestConfig.custom()
.setConnectTimeout(connectTimeout)
.setSocketTimeout(readTimeout)
.build();
}
這裡只設置了前面提到的兩個時間,RequestConfig中還有另外一個時間引數,感興趣的自己可以研究下。GetMethod
在3.0中,通過GetMethod來模擬get請求,在4.5中要通過HttpGet來模擬,對於請求頭和引數也不能像之前一樣直接放到一個Map中,這裡將其拆成兩個Map,一個記錄請求的頭資訊,比如UA、cookies、Referer等,另一個記錄請求的引數。
/**
* @param url
* @param params
* @param charset 引數編碼方式
* @param headers
* @return
* @Date:2016-11-15
* @Author:lulei
* @Description: 建立get請求
*/
private HttpGet createGetMethod(String url, Map<String, String> params, String charset, Map<String, String> headers) {
HttpGet method = new HttpGet(url);
method.setConfig(getRequestConfig());
if (params != null) {
List<NameValuePair> paramList = new ArrayList<NameValuePair>();
Iterator<Entry<String, String>> iter = params.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
String key = entry.getKey();
String value = entry.getValue();
paramList.add(new BasicNameValuePair(key, value));
}
try {
String paramStr = EntityUtils.toString(new UrlEncodedFormEntity(paramList, charset));
StringBuffer sb = new StringBuffer();
sb.append(url);
if (url.indexOf("?") > 0) {
sb.append("&");
} else {
sb.append("?");
}
sb.append(paramStr);
method.setURI(new URI(sb.toString()));
} catch (Exception e) {
e.printStackTrace();
}
}
if (headers != null) {
Iterator<Entry<String, String>> iter = headers.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
String key = entry.getKey();
String value = entry.getValue();
method.setHeader(key, value);
}
}
return method;
}
由於網站開發者對URL引數的編碼方式不同,因此這裡提供了一個引數來指定引數的編碼方式,對照之前3.0的程式碼,相信這裡很容易理解。PostMethod
3.0中的get請求是GetMethod,對應4.5中的是HttpGet,同樣3.0中的post請求是PostMethod,對應4.5中的是HttpPost,對於post的請求的建立和get類似,下面直接給出程式碼。
/**
* @param url
* @param params
* @param charset 引數編碼方式
* @param headers
* @return
* @Date:2016-11-15
* @Author:lulei
* @Description: 建立post請求
*/
private HttpPost createPostMethod(String url, Map<String, String> params, String charset, Map<String, String> headers) {
HttpPost method = new HttpPost(url);
method.setConfig(getRequestConfig());
if (params != null) {
List<NameValuePair> paramList = new ArrayList<NameValuePair>();
Iterator<Entry<String, String>> iter = params.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
String key = entry.getKey();
String value = entry.getValue();
paramList.add(new BasicNameValuePair(key, value));
}
try {
method.setEntity(new UrlEncodedFormEntity(paramList, charset));
} catch (Exception e) {
e.printStackTrace();
}
}
if (headers != null) {
Iterator<Entry<String, String>> iter = headers.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
String key = entry.getKey();
String value = entry.getValue();
method.setHeader(key, value);
}
}
return method;
}
和get請求的區別主要就在請求引數的部分,這兩部分的異同可以自己慢慢對比。執行請求,獲取返回值
和3.0一樣,我們建立了Method、HttpClient,下一步就是請求資源,獲取返回值,這裡和之前3.0的程式碼幾乎差不多,先看程式碼後分析:
private boolean execute(HttpUriRequest request) {
int n = maxConnectTimes;
while (n > 0) {
try {
HttpClient httpClient = getHttpClient();
HttpResponse response = httpClient.execute(request);
responseHeaders = response.getAllHeaders();
HttpEntity entity = response.getEntity();
InputStream inputStream = entity.getContent();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, charsetName));
StringBuffer stringBuffer = new StringBuffer();
String lineString = null;
while ((lineString = bufferedReader.readLine()) != null){
stringBuffer.append(lineString);
stringBuffer.append("\n");
}
pageSourceCode = stringBuffer.toString();
InputStream in =new ByteArrayInputStream(pageSourceCode.getBytes(charsetName));
String charset = CharsetUtil.getStreamCharset(in, charsetName);
if (!charsetName.toLowerCase().equals(charset.toLowerCase())) {
pageSourceCode = new String(pageSourceCode.getBytes(charsetName), charset);
}
bufferedReader.close();
inputStream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
n--;
}
}
return false;
}
這裡還像之前一樣最多請求N次,3.0中我們通過method.getResponseBodyAsStream()來獲取伺服器返回的流,4.5中我們通過httpClient.execute(request).getEntity().getContent()來獲取伺服器返回的流,下面對於檢測流編碼就和之前的一樣,這裡就不再贅述。提供子類或其他可以訪問的方法
看到這裡,細心的你也許已經發現了,上面介紹的方法都是private,因此我們需要提供一些外部可以訪問的方法,這裡我就先提供三個方法,具體如下:
/**
* @param url
* @param params
* @param getPost
* @param charset 引數編碼方式
* @param headers
* @return
* @Date:2016-11-15
* @Author:lulei
* @Description: 訪問url
*/
public boolean execute(String url, Map<String, String> params, String getPost, String charset, Map<String, String> headers) {
if (getPost == null) {
return false;
}
getPost = getPost.toLowerCase();
if ("get".equals(getPost)) {
return executeByGet(url, params, charset, headers);
} else if ("post".equals(getPost)) {
return executeByPost(url, params, charset, headers);
}
return false;
}
/**
* @param url
* @param params
* @param charset 引數編碼方式
* @param headers
* @return
* @Date:2016-11-15
* @Author:lulei
* @Description: post請求
*/
public boolean executeByPost(String url, Map<String, String> params, String charset, Map<String, String> headers) {
HttpPost method = createPostMethod(url, params, charset, headers);
return execute(method);
}
/**
* @param url
* @param params
* @param charset 引數編碼方式
* @param headers
* @return
* @Date:2016-11-15
* @Author:lulei
* @Description: get請求
*/
public boolean executeByGet(String url, Map<String, String> params, String charset, Map<String, String> headers) {
HttpGet method = createGetMethod(url, params, charset, headers);
return execute(method);
}
這裡就不提供整體的原始碼了,對於4.5的理解還是自己動手完成後理解的比較深刻,如有問題歡迎留言交流。
-------------------------------------------------------------------------------------------------
小福利
-------------------------------------------------------------------------------------------------
個人在極客學院上《Lucene案例開發》課程已經上線了,歡迎大家吐槽~