一、使用鏈接池
雖然說http協議時無鏈接的,但畢竟是基於tcp的,底層仍是須要和服務器創建鏈接的。對於須要從同一個站點抓取大量網頁的程序,應該使用鏈接池,不然每次抓取都和Web站點創建鏈接、發送請求、得到響應、釋放鏈接,一方面效率不高,另外一方面稍不當心就會疏忽了某些資源的釋放、致使站點拒絕鏈接(不少站點會拒絕同一個ip的大量鏈接、防止DOS***)。html
鏈接池的例程以下:java
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
- schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
- PoolingClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
- cm.setMaxTotal(200);
- cm.setDefaultMaxPerRoute(2);
- HttpHost googleResearch = new HttpHost("research.google.com", 80);
- HttpHost wikipediaEn = new HttpHost("en.wikipedia.org", 80);
- cm.setMaxPerRoute(new HttpRoute(googleResearch), 30);
- cm.setMaxPerRoute(new HttpRoute(wikipediaEn), 50);
SchemaRegistry的做用是註冊協議的默認端口號。PoolingClientConnectionManager是池化鏈接管理器,即鏈接池,setMaxTotal設置鏈接池的最大鏈接數,setDefaultMaxPerRoute設置每一個路由(http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d5e467)上的默認鏈接個數,setMaxPerRoute則單獨爲某個站點設置最大鏈接個數。算法
從鏈接池中獲取http client也很方面:chrome
- DefaultHttpClient client = new DefaultHttpClient(cm);
二、設置HttpClient參數
HttpClient須要設置合適的參數,才能更好地工做。默認的參數可以應付少許的抓取工做,但找到一組合適的參數每每能改善特定狀況下的抓取效果。設置參數的例程以下:apache
- DefaultHttpClient client = new DefaultHttpClient(cm);
- Integer socketTimeout = 10000;
- Integer connectionTimeout = 10000;
- final int retryTime = 3;
- client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, socketTimeout);
- client.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectionTimeout);
- client.getParams().setParameter(CoreConnectionPNames.TCP_NODELAY, false);
- client.getParams().setParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 1024 * 1024);
- HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler()
- {
- @Override
- public boolean retryRequest(IOException exception, int executionCount, HttpContext context)
- {
- if (executionCount >= retryTime)
- {
- // Do not retry if over max retry count
- return false;
- }
- if (exception instanceof InterruptedIOException)
- {
- // Timeout
- return false;
- }
- if (exception instanceof UnknownHostException)
- {
- // Unknown host
- return false;
- }
- if (exception instanceof ConnectException)
- {
- // Connection refused
- return false;
- }
- if (exception instanceof SSLException)
- {
- // SSL handshake exception
- return false;
- }
- HttpRequest request = (HttpRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
- boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
- if (idempotent)
- {
- // Retry if the request is considered idempotent
- return true;
- }
- return false;
- }
-
- };
- client.setHttpRequestRetryHandler(myRetryHandler);
五、6行分別設置了Socket最大等待時間、鏈接最大等待時間(單位都是毫秒)。socket等待時間是指從站點下載頁面和數據時,兩個數據包之間的最大時間間隔,超過這個時間間隔,httpclient就認爲鏈接出了故障。鏈接最大等待時間則是指和站點創建鏈接時的最大等待時間,超過這個時間站點不給迴應,則認爲站點沒法鏈接。第7行設置httpclient不使用NoDelay策略。若是啓用了NoDelay策略,httpclient和站點之間傳輸數據時將會盡量及時地將發送緩衝區中的數據發送出去、而不考慮網絡帶寬的利用率,這個策略適合對實時性要求高的場景。而禁用了這個策略以後,數據傳輸會採用Nagle's algorithm發送數據,該算法會充分顧及帶寬的利用率,而不是數據傳輸的實時性。第8行設置socket緩衝區的大小(單位爲字節),默認是8KB。
HttpRequestRetryHandler是負責處理請求重試的接口。在該接口的內部類中實現RetryRequest方法便可。當httpclient發送請求以後出現異常時,就會調用這個方法。在該方法中根據已執行請求的次數、請求內容、異常信息判斷是否繼續重試,若繼續重試返回true,不然返回false。
三、設置request header
設置request header也是很重要的,好比設置User-Agent能夠將抓取程序假裝成瀏覽器,騙過一些網站對爬蟲的檢查,設置Accept-Encoding爲gzip能夠建議站點以壓縮格式傳輸數據、節省帶寬等等。例程以下:
- HttpResponse response = null;
- HttpGet get = new HttpGet(url);
- get.addHeader("Accept", "text/html");
- get.addHeader("Accept-Charset", "utf-8");
- get.addHeader("Accept-Encoding", "gzip");
- get.addHeader("Accept-Language", "en-US,en");
- get.addHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.160 Safari/537.22");
- response = client.execute(get);
- HttpEntity entity = response.getEntity();
- Header header = entity.getContentEncoding();
- if (header != null)
- {
- HeaderElement[] codecs = header.getElements();
- for (int i = 0; i < codecs.length; i++)
- {
- if (codecs[i].getName().equalsIgnoreCase("gzip"))
- {
- response.setEntity(new GzipDecompressingEntity(entity));
- }
- }
- }
- return response;
須要的都設上就行了。若是須要不少不一樣的User-Agent輪流使用(同一個User-Agent對一個站點頻繁訪問容易被識別爲爬蟲而杯具),能夠去網上找,也能夠在本身的chrome瀏覽器裏看或者用抓包軟件抓。值得注意的是設置了Accept-Encoding爲gzip以後,對站點回復的內容要檢查是不是壓縮格式的,若是是,則解壓縮,如上面例程中第9行以後的代碼所示。