1、使用连接池
虽说http协议时无连接的,但毕竟是基于tcp的,底层还是需要和服务器建立连接的。对于需要从同一个站点抓取大量网页的程序,应该使用连接池,否则每次抓取都和Web站点建立连接、发送请求、获得响应、释放连接,一方面效率不高,另一方面稍不小心就会疏忽了某些资源的释放、导致站点拒绝连接(很多站点会拒绝同一个ip的大量连接、防止DOS攻击)。
连接池的例程如下:
- 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也很方面:
- DefaultHttpClient client = new DefaultHttpClient(cm);
2、设置HttpClient参数
HttpClient需要设置合适的参数,才能更好地工作。默认的参数能够应付少量的抓取工作,但找到一组合适的参数往往能改善特定情况下的抓取效果。设置参数的例程如下:
- 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);
5、6行分别设置了Socket最大等待时间、连接最大等待时间(单位都是毫秒)。socket等待时间是指从站点下载页面和数据时,两个数据包之间的最大时间间隔,超过这个时间间隔,httpclient就认为连接出了故障。连接最大等待时间则是指和站点建立连接时的最大等待时间,超过这个时间站点不给回应,则认为站点无法连接。第7行设置httpclient不使用NoDelay策略。如果启用了NoDelay策略,httpclient和站点之间传输数据时将会尽可能及时地将发送缓冲区中的数据发送出去、而不考虑网络带宽的利用率,这个策略适合对实时性要求高的场景。而禁用了这个策略之后,数据传输会采用Nagle's algorithm发送数据,该算法会充分顾及带宽的利用率,而不是数据传输的实时性。第8行设置socket缓冲区的大小(单位为字节),默认是8KB。
HttpRequestRetryHandler是负责处理请求重试的接口。在该接口的内部类中实现RetryRequest方法即可。当httpclient发送请求之后出现异常时,就会调用这个方法。在该方法中根据已执行请求的次数、请求内容、异常信息判断是否继续重试,若继续重试返回true,否则返回false。
3、设置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;
各个header的含义参考http://kb.cnblogs.com/page/92320/
需要的都设上就好了。如果需要很多不同的User-Agent轮流使用(同一个User-Agent对一个站点频繁访问容易被识别为爬虫而杯具),可以去网上找,也可以在自己的chrome浏览器里看或者用抓包软件抓。值得注意的是设置了Accept-Encoding为gzip之后,对站点回复的内容要检查是否是压缩格式的,如果是,则解压缩,如上面例程中第9行之后的代码所示。