Java http 和 httpclient 使用代理采集数据
Java里有两种方法来进行 HTTP 网页请求:HttpURLConnection
和 HttpClient
目前为止,这两种方法都工作的很好,基本上可以满足我们需要的 GET/POST 方法的模拟。
对于一个爬虫来说,能发送 HTTP 请求,能获取页面数据,能解析网页内容,这相当于已经完成 80% 的工作了。
只不过对于剩下的这 20% 的工作,还得花费我们另外 80% 的时间。
在这篇博客里,我们将介绍剩下 20% 的工作中最为重要的一项:如何在 Java 中使用 HTTP 代理,代理也是爬虫技术中的重要一项。
你如果要大规模的爬别人网页上的内容,必然会对人家的网站造成影响,如果你太拼了,就会遭人查封。要防止别人查封我们,我们要么将自己的程序分布到大量机器上去,但是对于资金和资源有限的我们来说这是很奢侈的;要么就使用代理技术,从网上捞一批代理,免费的也好收费的也好,或者购买一批廉价的 VPS 来搭建自己的代理服务器。
这里我们推荐米扑代理,国内很专业的做大数据采集的代理服务商,其覆盖国内数百个城市,提供免费和收费的,高稳定可用率99%,完全可以满足需求。
一、简单的 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 使用 http、https、socks5 代理爬取数据
how-to-use-socks-5-proxy-with-apache-http-client-4 (stackoverflow)
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2018-04-30 05:06:25
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!