設置Ip代理很多時候都會有用到,尤其是在寫爬蟲相關項目的時候。雖然自己目前沒有接觸這種需求,但由於最近比較閑,就寫着當作練習吧
爬取代理IP
爬取
關於爬取代理IP,國內首先想到的網站當然是 西刺代理 。首先寫個爬蟲獲取該網站內的Ip吧。
先對 國內Http代理 標簽頁面進行爬取,解析頁面使用的Jsoup ,這里大概代碼如下
private List<IPBean> crawl(String api, int index){
String html = HttpUtils.getResponseContent(api + index);
System.out.println(html);
Document document = Jsoup.parse(html);
Elements eles = document.selectFirst("table").select("tr");
for (int i = 0; i < eles.size(); i++){
if (i == 0) continue;
Element ele = eles.get(i);
String ip = ele.children().get(1).text();
int port = Integer.parseInt(ele.children().get(2).text().trim());
String typeStr = ele.children().get(5).text().trim();
int type;
if ("HTTP".equalsIgnoreCase(typeStr))
type = IPBean.TYPE_HTTP;
else
type = IPBean.TYPE_HTTPS;
IPBean ipBean = new IPBean(ip, port, type);
ipList.add(ipBean);
}
return ipList;
}
對某些不明白的變量,可以參考我Github
其中關鍵的就是css選擇器語法,這里需要注意的是不要亂加空格,不然會導致找不到出現空指針。
css選擇器語法具體參考這里 , 這里就不講解了。
爬取的信息包括 ip地址、端口號、和代理類型(http或https), 這三個信息我放在IPBean這個類里面。
過濾
上面爬取完成后,還要進一步過濾,篩選掉不能使用的。
篩選大概原理就是先設置上代理,然后請求某個網頁,若成功則代表此代理ip有效。
其中請求成功的標志我們可以直接獲取請求的返回碼,若為200即成功。
/**
* 檢測代理ip是否有效
*
* @param ipBean
* @return
*/
public static boolean isValid(IPBean ipBean) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ipBean.getIp(), ipBean.getPort()));
try {
URLConnection httpCon = new URL("https://www.baidu.com/").openConnection(proxy);
httpCon.setConnectTimeout(5000);
httpCon.setReadTimeout(5000);
int code = ((HttpURLConnection) httpCon).getResponseCode();
System.out.println(code);
return code == 200;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
注意這里要設置兩個超時,連接超時和讀取超時。連接超時還好,它默認只是有點長;然而讀取超時如果不設置,它好像就會一直阻塞着。
時間設置為5s就夠了,畢竟如果ip有效的話,會很快就請求成功的。這樣過濾后,就得到有效的代理ip了
設置代理
單次代理
單次代理表示只在這一次連接中有效,即每次都需要代理。
http方式的代理非常簡單,在URL對象的openConnection方法中加上個Proxy對象即可
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ipBean.getIp(), ipBean.getPort()));
connection = (HttpsURLConnection) new URL(url).openConnection(proxy);
https 稍微復雜點了,中間加上了ssl協議
/**
* @param url
* @param headerMap 請求頭部
* @param ipBean
* @return
* @throws Exception
*/
public static String getResponseContent(String url, Map<String, List<String>> headerMap, IPBean ipBean) throws Exception {
HttpsURLConnection connection = null;
// 設置代理
if (ipBean != null) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ipBean.getIp(), ipBean.getPort()));
connection = (HttpsURLConnection) new URL(url).openConnection(proxy);
if (ipBean.getType() == IPBean.TYPE_HTTPS) {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
connection.setSSLSocketFactory(sslContext.getSocketFactory());
connection.setHostnameVerifier(new TrustAnyHostnameVerifier());
}
}
if (connection == null)
connection = (HttpsURLConnection) new URL(url).openConnection();
// 添加請求頭部
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36");
if (headerMap != null) {
Iterator<Map.Entry<String, List<String>>> iterator = headerMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<String>> entry = iterator.next();
List<String> values = entry.getValue();
for (String value : values)
connection.setRequestProperty(entry.getKey(), value);
}
}
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
reader.close();
inputStream.close();
return stringBuilder.toString();
}
private static class TrustAnyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
這里https方法參考了 這篇博客
全局代理
直接上代碼,就幾行代碼
package util;
import other.IPBean;
/**
* @author Asche
* @github: https://github.com/asche910
* @date 2019年1月19日
*/
public class ProxyUtils {
/**
* 設置全局代理
* @param ipBean
*/
public static void setGlobalProxy(IPBean ipBean){
System.setProperty("proxyPort", String.valueOf(ipBean.getPort()));
System.setProperty("proxyHost", ipBean.getIp());
System.setProperty("proxySet", "true");
}
}
需要注意一點就是全局只是在該java項目中生效,它不會更改系統中的代理。
檢測
設置完代理后,也可以用另外一種方法來判斷是否代理成功,即直接獲取當前ip地址。
這里我使用的是 https://www.ipip.net/ip.html 這個網站,請求獲取html后再解析得到自己的當前ip
private static final String MY_IP_API = "https://www.ipip.net/ip.html";
// 獲取當前ip地址,判斷是否代理成功
public static String getMyIp() {
try {
String html = HttpUtils.getResponseContent(MY_IP_API);
Document doc = Jsoup.parse(html);
Element element = doc.selectFirst("div.tableNormal");
Element ele = element.selectFirst("table").select("td").get(1);
String ip = element.selectFirst("a").text();
// System.out.println(ip);
return ip;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
優化
emmm 優化些啥呢???
速度
爬取ip時就幾個網頁,優化估計效果不大。而真正耗時的是檢測ip是否有效,因此這里采用多線程,對每個ip的檢測請求使用一個線程,最后副線程全部結束后再統計出有多少有效ip。然而問題又來了,怎么判斷所有副線程全部結束了呢??? 腦中立刻想到的是join方法,然而仔細想想,才發現這樣並不可取。最佳方法應該是設置一個計數器,每個線程結束后計數器加一,然后在主線程循環判斷計數器的值是否與線程總數相等即可。由於涉及到並發,需要給某些方法加上鎖。這里我代碼中實現了,可以參考github
持久化
emmm 由於目前只是練練手,並沒有這樣的需求,比較懶, ( ̄▽ ̄)*
所以這個需求暫時放放吧,以后有時間再寫
最后github入口:Asche910