一、使用链接池
虽然说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行以后的代码所示。