1. 程式人生 > >基於HttpClient4.5實現網路爬蟲

基於HttpClient4.5實現網路爬蟲

個人部落格站已經上線了,網址 www.llwjy.com ~歡迎各位吐槽~

-------------------------------------------------------------------------------------------------

      在開始之前先打一個小小的廣告,自己建立一個QQ群:321903218,點選連結加入群【Lucene案例開發】,主要用於交流如何使用Lucene來建立站內搜尋後臺,同時還會不定期的在群內開相關的公開課,感興趣的童鞋可以加入交流。

寫在開始之前

      這裡做一個簡短的說明,之前在部落格《基於HttpClient實現網路爬蟲~以百度新聞為例

》介紹瞭如何基於HttpClient3.0來模擬瀏覽器請求,但從4.0版本之後,Apache就對這個包做了很大的改動,這裡就針對目前比較新的版本4.5再來介紹下如何模擬瀏覽器的請求。在這裡就不再介紹如何分析網頁,如何確定請求的型別,就直接介紹如何來模擬瀏覽器的請求。

獲取HttpClient

      在4.5版本中,獲取HttpClient例項和之前還是有很大區別的,這裡引用了一個PoolingHttpClientConnectionManager類,我們就通過這個類來介紹如何來建立HttpClient。這個類是Http連結管理類,因此在整個工程中我們將其定義為靜態變數。

private static PoolingHttpClientConnectionManager cm;

      在靜態方法中對其實現初始化的操作。

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);
	//上面兩句中的變數可以通過類的屬性來確定
}
      這樣就完成了PoolingHttpClientConnectionManager類的例項的初始化,下面就通過cm來獲取HttpClient。
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案例開發》課程已經上線了,歡迎大家吐槽~