在上一篇博客《模擬 HTTP 請(qǐng)求》中,我們分別介紹了兩種方法來進(jìn)行 HTTP 的模擬請(qǐng)求:HttpURLConnection
和 HttpClient
,到目前為止這兩種方法都工作的很好,基本上可以實(shí)現(xiàn)我們需要的 GET/POST 方法的模擬。對(duì)于一個(gè)爬蟲來說,能發(fā)送 HTTP 請(qǐng)求,能獲取頁面數(shù)據(jù),能解析網(wǎng)頁內(nèi)容,這相當(dāng)于已經(jīng)完成 80% 的工作了。只不過對(duì)于剩下的這 20% 的工作,還得花費(fèi)我們另外 80% 的時(shí)間 :-)
在這篇博客里,我們將介紹剩下 20% 的工作中最為重要的一項(xiàng):如何在 Java 中使用 HTTP 代理,代理也是爬蟲技術(shù)中的重要一項(xiàng)。你如果要大規(guī)模的爬別人網(wǎng)頁上的內(nèi)容,必然會(huì)對(duì)人家的網(wǎng)站造成影響,如果你太拼了,就會(huì)遭人查封。要防止別人查封我 們,我們要么將自己的程序分布到大量機(jī)器上去,但是對(duì)于資金和資源有限的我們來說這是很奢侈的;要么就使用代理技術(shù),從網(wǎng)上撈一批代理,免費(fèi)的也好收費(fèi)的 也好,或者購買一批廉價(jià)的 VPS 來搭建自己的代理服務(wù)器。關(guān)于如何搭建自己的代理服務(wù)器,后面有時(shí)間的話我再寫一篇關(guān)于這個(gè)話題的博客。現(xiàn)在有了一大批代理服務(wù)器之后,就可以使用我們這 篇博客所介紹的技術(shù)了。
一、簡單的 HTTP 代理
我們先從最簡單的開始,網(wǎng)上有很多免費(fèi)代理,直接上百度搜索 “免費(fèi)代理” 或者 “HTTP 代理” 就能找到很多(雖然網(wǎng)上能找到大量的免費(fèi)代理,但它們的安全性已經(jīng)有很多文章討論過了,也有專門的文章對(duì)此進(jìn)行調(diào)研的,譬如這篇文章,我在這里就不多作說明,如果你的爬蟲爬取的信息并沒有什么特別的隱私問題,可以忽略之,如果你的爬蟲涉及一些例如模擬登錄之類的功能,考慮到安全性,我建議你還是不要使用網(wǎng)上公開的免費(fèi)代理,而是搭建自己的代理服務(wù)器比較靠譜)。
1.1 HttpURLConnection 使用代理
HttpURLConnection 的 openConnection()
方法可以傳入一個(gè) Proxy 參數(shù),如下:
1 2 3 | Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress( "127.0.0.1" , 9876 )); URL obj = new URL(url); HttpURLConnection con = (HttpURLConnection) obj.openConnection(proxy); |
OK 了,就這么簡單!
不僅如此,我們注意到 Proxy 構(gòu)造函數(shù)的第一個(gè)參數(shù)為枚舉類型 Proxy.Type.HTTP
,那么很顯然,如果將其修改為 Proxy.Type.SOCKS
即可以使用 SOCKS 代理。
1.2 HttpClient 使用代理
由于 HttpClient
非常靈活,使用 HttpClient 來連接代理有很多不同的方法。最簡單的方法莫過于下面這樣:
1 2 3 4 | HttpHost proxy = new HttpHost( "127.0.0.1" , 9876 , "HTTP" ); CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet request = new HttpGet(url); CloseableHttpResponse response = httpclient.execute(proxy, request); |
和上一篇中使用 HttpClient 發(fā)送請(qǐng)求的代碼幾乎一樣,只是 httpclient.execute()
方法多加了一個(gè)參數(shù),第一參數(shù)為 HttpHost
類型,我們這里設(shè)置成我們的代理即可。
這里要注意一點(diǎn)的是,雖然這里的 new HttpHost()
和上面的 new Proxy()
一樣,也是可以指定協(xié)議類型的,但是遺憾的是 HttpClient 默認(rèn)是不支持 SOCKS 協(xié)議的,如果我們使用下面的代碼:
1 | HttpHost proxy = new HttpHost( "127.0.0.1" , 1080 , "SOCKS" ); |
將會(huì)直接報(bào)協(xié)議不支持異常:
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 類來實(shí)現(xiàn)。
雖然使用這種方式很簡單,只需要加個(gè)參數(shù)就可以了,但是其實(shí)看 HttpClient 的代碼注釋,如下:
1 2 3 4 5 6 7 | /* * @param target the target host for the request. * Implementations may accept <code>null</code> * if they can still determine a route, for example * to a default target or by inspecting the request. * @param request the request to execute */ |
可以看到第一個(gè)參數(shù) target 并不是代理,它的真實(shí)作用是 執(zhí)行請(qǐng)求的目標(biāo)主機(jī),這個(gè)解釋有點(diǎn)模糊,什么叫做 執(zhí)行請(qǐng)求的目標(biāo)主機(jī)?代理算不算執(zhí)行請(qǐng)求的目標(biāo)主機(jī)呢?因?yàn)榘闯@韥碇v,執(zhí)行請(qǐng)求的目標(biāo)主機(jī) 應(yīng)該是要請(qǐng)求 URL 對(duì)應(yīng)的站點(diǎn)才對(duì)。如果不算的話,為什么這里將 target 設(shè)置成代理也能正常工作?這個(gè)我也不清楚,還需要進(jìn)一步研究下 HttpClient 的源碼來深入了解下。
除了上面介紹的這種方式(自己寫的,不推薦使用)來使用代理之外,HttpClient 官網(wǎng)還提供了幾個(gè)示例,我將其作為推薦寫法記錄在此。
第一種寫法是使用 RequestConfig 類,如下:
1 2 3 4 5 6 7 8 9 10 | CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet request = new HttpGet(url); request.setConfig( RequestConfig.custom() .setProxy( new HttpHost( "45.32.21.237" , 8888 , "HTTP" )) .build() ); CloseableHttpResponse response = httpclient.execute(request); |
第二種寫法是使用 RoutePlanner 類,如下:
1 2 3 4 5 6 7 | HttpHost proxy = new HttpHost( "127.0.0.1" , 9876 , "HTTP" ); DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); CloseableHttpClient httpclient = HttpClients.custom() .setRoutePlanner(routePlanner) .build(); HttpGet request = new HttpGet(url); CloseableHttpResponse response = httpclient.execute(request); |
二、使用系統(tǒng)代理配置
我們?cè)谡{(diào)試 HTTP 爬蟲程序時(shí),常常需要切換代理來測試,有時(shí)候直接使用系統(tǒng)自帶的代理配置將是一種簡單的方法。以前在做 .Net 項(xiàng)目時(shí),程序默認(rèn)使用 Internet 網(wǎng)絡(luò)設(shè)置中配的代理,遺憾的是,我這里說的系統(tǒng)代理配置指的 JVM 系統(tǒng),而不是操作系統(tǒng),我還沒找到簡單的方法來讓 Java 程序直接使用 Windows 系統(tǒng)下的代理配置。
盡管如此,系統(tǒng)代理使用起來還是很簡單的。一般有下面兩種方式可以設(shè)置 JVM 的代理配置:
2.1 System.setProperty
Java 中的 System
類不僅僅是用來給我們 System.out.println()
打印信息的,它其實(shí)還有很多靜態(tài)方法和屬性可以用。其中 System.setProperty()
就是比較常用的一個(gè)。
可以通過下面的方式來分別設(shè)置 HTTP 代理,HTTPS 代理和 SOCKS 代理:
1 2 3 4 5 6 7 8 9 10 11 12 | // HTTP 代理,只能代理 HTTP 請(qǐng)求 System.setProperty( "http.proxyHost" , "127.0.0.1" ); System.setProperty( "http.proxyPort" , "9876" ); // HTTPS 代理,只能代理 HTTPS 請(qǐng)求 System.setProperty( "https.proxyHost" , "127.0.0.1" ); System.setProperty( "https.proxyPort" , "9876" ); // SOCKS 代理,支持 HTTP 和 HTTPS 請(qǐng)求 // 注意:如果設(shè)置了 SOCKS 代理就不要設(shè) HTTP/HTTPS 代理 System.setProperty( "socksProxyHost" , "127.0.0.1" ); System.setProperty( "socksProxyPort" , "1080" ); |
這里有三點(diǎn)要說明:
- 系統(tǒng)默認(rèn)先使用 HTTP/HTTPS 代理,如果既設(shè)置了 HTTP/HTTPS 代理,又設(shè)置了 SOCKS 代理,SOCKS 代理會(huì)起不到作用
- 由于歷史原因,注意
socksProxyHost
和socksProxyPort
中間沒有小數(shù)點(diǎn) - HTTP 和 HTTPS 代理可以合起來縮寫,如下:
1 2 3 | // 同時(shí)支持代理 HTTP/HTTPS 請(qǐng)求 System.setProperty( "proxyHost" , "127.0.0.1" ); System.setProperty( "proxyPort" , "9876" ); |
2.2 JVM 命令行參數(shù)
可以使用 System.setProperty()
方法來設(shè)置系統(tǒng)代理,也可以直接將這些參數(shù)通過 JVM 的命令行參數(shù)來指定。如果你使用的是 Eclipse ,可以按下面的步驟來設(shè)置:
- 按順序打開:Window -> Preferences -> Java -> Installed JREs -> Edit
- 在 Default VM arguments 中填寫參數(shù):
-DproxyHost=127.0.0.1 -DproxyPort=9876
2.3 使用系統(tǒng)代理
上面兩種方法都可以設(shè)置系統(tǒng),下面要怎么在程序中自動(dòng)使用系統(tǒng)代理呢?
對(duì)于 HttpURLConnection
類來說,程序不用做任何變動(dòng),它會(huì)默認(rèn)使用系統(tǒng)代理。但是 HttpClient
默認(rèn)是不使用系統(tǒng)代理的,如果想讓它默認(rèn)使用系統(tǒng)代理,可以通過 SystemDefaultRoutePlanner
和 ProxySelector
來設(shè)置。示例代碼如下:
1 2 3 4 5 6 | SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(ProxySelector.getDefault()); CloseableHttpClient httpclient = HttpClients.custom() .setRoutePlanner(routePlanner) .build(); HttpGet request = new HttpGet(url); CloseableHttpResponse response = httpclient.execute(request); |