Java里有两种方法来进行 HTTP 网页请求:HttpURLConnection 和 HttpClient 

目前为止,这两种方法都工作的很好,基本上可以满足我们需要的 GET/POST 方法的模拟。

对于一个爬虫来说,能发送 HTTP 请求,能获取页面数据,能解析网页内容,这相当于已经完成 80% 的工作了。

只不过对于剩下的这 20% 的工作,还得花费我们另外 80% 的时间。

 

在这篇博客里,我们将介绍剩下 20% 的工作中最为重要的一项:如何在 Java 中使用 HTTP 代理,代理也是爬虫技术中的重要一项。

你如果要大规模的爬别人网页上的内容,必然会对人家的网站造成影响,如果你太拼了,就会遭人查封。要防止别人查封我们,我们要么将自己的程序分布到大量机器上去,但是对于资金和资源有限的我们来说这是很奢侈的;要么就使用代理技术,从网上捞一批代理,免费的也好收费的也好,或者购买一批廉价的 VPS 来搭建自己的代理服务器。

这里我们推荐米扑代理,国内很专业的做大数据采集的代理服务商,其覆盖国内数百个城市,提供免费和收费的,高稳定可用率99%,完全可以满足需求。

米扑代理https://proxy.mimvp.com

 

 

一、简单的 HTTP 代理

从最简单的开始,网上有很多免费代理,直接上百度搜索 “免费代理” 或者 “HTTP 代理” 就能找到很多。

虽然网上能找到大量的免费代理,但它们的安全性、稳定性、可用率普遍不高,如果你爬取的信息并没有什么特别的隐私问题,可以忽略之。如果你的爬虫涉及一些例如模拟登录之类的功能,考虑到安全性,我建议你还是不要使用网上公开的免费代理,而是搭建自己的代理服务器比较靠谱,或者购买专业的代理服务商,例如:米扑代理

1.1 HttpURLConnection 使用代理

HttpURLConnection 的 openConnection() 方法可以传入一个 Proxy 参数,如下:

URL url = new URL("https://proxy.mimvp.com");	

InetSocketAddress addr = new InetSocketAddress(proxy_ip, proxy_port);
Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);
if(proxyType.equals("socks4") || proxyType.equals("socks5")) {
	proxy = new Proxy(Proxy.Type.SOCKS, addr);
}

URLConnection conn = url.openConnection(proxy);
conn.setConnectTimeout(30 * 1000);	

注意到 Proxy 构造函数的第一个参数为枚举类型 Proxy.Type.HTTP ,如果将其修改为 Proxy.Type.SOCKS 即可以使用 SOCKS 代理。

 

1.2 HttpClient 使用代理

HttpClient 是Apache 的第三方库,需要下载引入jar包,才可使用。

HttpClient 源码和jar包下载:http://hc.apache.org/downloads.cgi

 

由于 HttpClient 非常灵活,使用 HttpClient 来连接代理有很多不同的方法

最简单的方法莫过于下面这样:

CloseableHttpClient client = HttpClients.createDefault();
HttpHost proxy = new HttpHost(proxy_ip, proxy_port);

HttpGet request = new HttpGet("https://proxy.mimvp.com");
request.addHeader("Host","proxy.mimvp.com");
request.addHeader("User-Agent","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36");

HttpResponse response = client.execute(proxy, request);
if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
	String result = EntityUtils.toString(response.getEntity());
	System.out.println(result);
}

这里要注意一点的是,虽然这里的 new HttpHost() 和上面的 new Proxy() 一样,也是可以指定协议类型的,例如:

HttpHost proxy = new HttpHost(proxy_ip, proxy_port, "HTTP");
HttpHost proxy = new HttpHost(proxy_ip, proxy_port, "HTTPS");

 

httpclient 访问 https网页,参考 stackoverflow

CloseableHttpClient httpclient = HttpClients.createDefault();
try {
    HttpHost target = new HttpHost("proxy.mimvp.com", 443, "https");
    HttpHost proxy = new HttpHost("65.253.78.123", 3128, "http");

    RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
    HttpGet request = new HttpGet("/");
    request.setConfig(config);

    CloseableHttpResponse response = httpclient.execute(target, request);
    try {
        System.out.println(response.getStatusLine());
        EntityUtils.consume(response.getEntity());
    } finally {
        response.close();
    }
} finally {
    httpclient.close();
}

 

但是遗憾的是 HttpClient 默认是不支持 SOCKS 协议的,例如设置代理如下:

HttpHost proxy = new HttpHost(proxy_ip, proxy_port, "SOCKS");

将会直接报协议不支持异常:

org.apache.http.conn.UnsupportedSchemeException: socks protocol is not supported

 

如果希望 HttpClient 支持 SOCKS 代理,可以参看这里:How to use Socks 5 proxy with Apache HTTP Client 4

通过 HttpClient 提供的 ConnectionSocketFactory 类来实现。

虽然使用这种方式很简单,只需要加个参数就可以了,但是其实看 HttpClient 的代码注释,如下:

    /**
     * Executes HTTP request using the default context.
     *
     * @param target    the target host for the request.
     *                  Implementations may accept {@code null}
     *                  if they can still determine a route, for example
     *                  to a default target or by inspecting the request.
     * @param request   the request to execute
     *
     * @return  the response to the request. This is always a final response,
     *          never an intermediate response with an 1xx status code.
     *          Whether redirects or authentication challenges will be returned
     *          or handled automatically depends on the implementation and
     *          configuration of this client.
     * @throws IOException in case of a problem or the connection was aborted
     * @throws ClientProtocolException in case of an http protocol error
     */
    HttpResponse execute(HttpHost target, HttpRequest request)
        throws IOException, ClientProtocolException;

可以看到第一个参数 target 并不是代理,它的真实作用是 执行请求的目标主机,这个解释有点模糊,什么叫做 执行请求的目标主机?代理算不算执行请求的目标主机呢?因为按常理来讲,执行请求的目标主机 应该是要请求 URL 对应的站点才对。如果不算的话,为什么这里将 target 设置成代理也能正常工作?这个我也不清楚,还需要进一步研究下 HttpClient 的源码来深入了解下。

除了上面介绍的这种方式(自己写的,不推荐使用)来使用代理之外,HttpClient 官网还提供了几个示例,我将其作为推荐写法记录在此。

第一种写法是使用 RequestConfig 类,如下:

CloseableHttpClient httpclient = HttpClients.createDefault();      
HttpGet request =newHttpGet("https://proxy.mimvp.com");
 
request.setConfig(
    RequestConfig.custom()
        .setProxy(newHttpHost("65.253.78.123", 3128, "HTTP"))
        .build()
);
         
CloseableHttpResponse response = httpclient.execute(request);

 

第二种写法是使用 RoutePlanner 类,如下:

HttpHost proxy = newHttpHost("65.253.78.123", 3128, "HTTP");
DefaultProxyRoutePlanner routePlanner = newDefaultProxyRoutePlanner(proxy);
CloseableHttpClient httpclient = HttpClients.custom().setRoutePlanner(routePlanner).build();
HttpGet request = newHttpGet("https://proxy.mimvp.com");
CloseableHttpResponse response = httpclient.execute(request);

 

 

二、使用系统代理配置

在调试 HTTP 爬虫程序时,常常需要切换代理来测试,有时候直接使用系统自带的代理配置将是一种简单的方法。

一般有下面两种方式可以设置 JVM 的代理配置:

方式1: System.setProperty

Java 中的 System 类不仅仅是用来给我们 System.out.println() 打印信息的,它其实还有很多静态方法和属性可以用。

其中System.setProperty() 就是比较常用的一个。

可以通过下面的方式来分别设置 HTTP 代理,HTTPS 代理和 SOCKS 代理:

Properties prop = System.getProperties();

// http
if(proxyType.equals("http")){
	prop.setProperty("http.proxySet", "true");
	prop.setProperty("http.proxyHost", proxy_ip);
	prop.setProperty("http.proxyPort", proxy_port);
	prop.setProperty("http.nonProxyHosts", "localhost|192.168.0.*");
}

// https
if (proxyType.equals("https")) {
	prop.setProperty("https.proxyHost", proxy_ip);
	prop.setProperty("https.proxyPort", proxy_port);
}

// socks
if(proxyType.equals("socks4") || proxyType.equals("socks5")){
    prop.setProperty("socksProxySet", "true");
    prop.setProperty("socksProxyHost", proxy_ip);
    prop.setProperty("socksProxyPort", proxy_port);
}

// ftp
if(proxyType.equals("ftp")){
    prop.setProperty("ftp.proxyHost", proxy_ip);
    prop.setProperty("ftp.proxyPort", proxy_port);
    prop.setProperty("ftp.nonProxyHosts", "localhost|192.168.0.*");
}

 

这里有三点要说明:

1)系统默认先使用 HTTP/HTTPS 代理,如果既设置了 HTTP/HTTPS 代理,又设置了 SOCKS 代理,SOCKS 代理会起不到作用

2)由于历史原因,注意 socksProxyHost 和 socksProxyPort 中间没有小数点

3)HTTP 和 HTTPS 代理可以合起来缩写,如下:

// 支持同时代理 http和https 请求
if (proxyType.equals("http") || proxyType.equals("https")) {
	prop.setProperty("proxyHost", proxy_ip);
	prop.setProperty("proxyPort", proxy_port);
}


 

方式2: JVM 命令行参数

可以使用 System.setProperty() 方法来设置系统代理,也可以直接将这些参数通过 JVM 的命令行参数来指定。

如果你使用的是 Eclipse ,可以按下面的步骤来设置:

1)按顺序打开:Window -> Preferences -> Java -> Installed JREs -> Edit

2)在 Default VM arguments 中填写参数: -DproxyHost=65.253.78.123 -DproxyPort=3128

 

 

方式3: 使用系统代理

上面两种方法都可以设置系统,下面要怎么在程序中自动使用系统代理呢?

对于 HttpURLConnection 类来说,程序不用做任何变动,它会默认使用系统代理。

但是 HttpClient 默认是不使用系统代理的,如果想让它默认使用系统代理,可以通过 SystemDefaultRoutePlanner 和 ProxySelector 来设置。示例代码如下:

SystemDefaultRoutePlanner routePlanner = newSystemDefaultRoutePlanner(ProxySelector.getDefault());
CloseableHttpClient httpclient = HttpClients.custom().setRoutePlanner(routePlanner).build();

HttpGet request = newHttpGet("https://proxy.mimvp.com");    
CloseableHttpResponse response = httpclient.execute(request);

 

 

补充知识:Java 中 HttpClient 使用

一、使用Apache的HttpClient发送GET和POST请求的步骤如下: 

1. 使用帮助类HttpClients创建CloseableHttpClient对象

2. 基于要发送的HTTP请求类型创建HttpGet或者HttpPost实例

3. 使用addHeader方法添加请求头部,诸如User-Agent, Accept-Encoding等参数

4. 对于POST请求,创建NameValuePair列表,并添加所有的表单参数,然后把它填充进HttpPost实体

5. 通过执行此HttpGet或者HttpPost,请求获取CloseableHttpResponse实例 

6. 从此CloseableHttpResponse实例中获取状态码、错误信息,以及响应页面等等

7. 最后关闭HttpClient资源

 

二、使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可:

1. 创建HttpClient对象,HttpClients.createDefault()

2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象,HttpPost httpPost = new HttpPost(url)

3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。List<NameValuePair> valuePairs = new LinkedList<NameValuePair>();valuePairs.add(new BasicNameValuePair(entry.getKey(),entry.getValue()));httpPost.setEntity(formEntity)

4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse

5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容

6. 释放连接。无论执行方法是否成功,都必须释放连接

 

HttpClient 实例:

// 通过API实时获取米扑代理
public static void spider_proxy() {
	String proxy_url = "https://proxyapi.mimvp.com/api/fetchsecret.php?orderid=868435221231212345&http_type=3";

	try {
		@SuppressWarnings({ "resource", "deprecation" })		
		HttpClient client = new DefaultHttpClient();					// 舍弃的用法
		
		CloseableHttpClient client2 = HttpClients.createDefault();	// 推荐的用法
		HttpGet request = new HttpGet(proxy_url);
		request.addHeader("Host","proxy.mimvp.com");
		request.addHeader("User-Agent","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36");
			
		HttpResponse response = client2.execute(request);
		if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
			String result = EntityUtils.toString(response.getEntity());

			String[] proxy_list;
			proxy_list = result.split("\n");
			for (String proxy : proxy_list) {
				System.out.println(proxy);
			}
		}
		client2.close();
	} catch (Exception e) {
		System.out.println(e.toString());
	}
}

 

 

参考推荐

Java 中 HttpClient 使用

Java 和 HTTP 的使用代理

Java HttpClient 使用 http、https、socks5 代理爬取数据

how-to-use-socks-5-proxy-with-apache-http-client-4 (stackoverflow)