在本次爬蟲項目中,關于應用IP代理池方面,具體完成以下功能:
-
從指定API地址提取IP到ip池中(一次提取的IP數量可以自定義更改)
-
每次開始爬蟲前(多條爬蟲線程并發執行),從ip池中獲取一條可用ip和端口號(并用此ip進行代理爬蟲)
-
每條IP的有效時間為1~5分鐘,如果爬蟲過程中當前代理ip失效時,程序可以自動切換IP,并從當前爬到的頁數開始繼續爬蟲。
目錄
一、四葉天動態代理IP的使用步驟
二、在Java中動態IP代理的工具類
三、如何使用動態Ip進行網站訪問
四、實際爬蟲過程中的注意事項
五、代碼中的亮點:
一、四葉天動態代理IP的使用步驟
想要使用ip代理池來進行代理IP爬蟲,我們首先要購買一些可用IP,下面介紹一個好用實惠的IP代理網站:(https://www.siyetian.com)提供高質量的動態IP 服務 ,以下是購買和使用該服務的詳細步驟:
(一)購買動態 IP 服務
-
注冊并登錄
-
實名認證:在使用服務前,需完成實名認證。登錄后,前往實名認證頁面(登錄 - 四葉天HTTP),按照提示提交相關信息進行認證。
-
選擇套餐:點擊頂部導航欄“動態IP”,選擇適合的動態 IP 套餐。這里我使用的是:按使用量購買,四塊錢1000條IP
-
支付購買
(二)使用動態 IP 服務
點擊頂部導航欄的提取API
我的配置如下:
①IP協議為Http
②提取數量為每次一條,
③數據格式設置為Json
④在白名單中添加本機IP
最后點擊生成api鏈接,你會得到一個URL地址,每訪問一次該地址,就會返回一條IP地址和一個端口號(同時你剛買的1000條IP中就少一條🐶)
注意事項(該部分AI生成用于湊字數,不想看可不看🐶):
-
設置白名單:在使用代理 IP 前,需將您的本機 IP地址 添加到白名單,以確保代理服務的正常使用。
前往白名單設置頁面:(登錄 - 四葉天HTTP),添加您的本地 IP 地址。
-
配置代理IP:您可以在應用程序或瀏覽器中,設置使用獲取的代理 IP。具體步驟如下:
-
在瀏覽器設置:在瀏覽器的網絡設置中,選擇手動代理配置,輸入獲取的代理 IP 地址和端口號,保存設置。
-
在程序設置:在您的爬蟲、網絡請求等程序中,按照編程語言的網絡請求庫要求,設置代理 IP 和端口。
-
-
驗證代理有效性:在開始正式使用前,建議測試代理 IP 的有效性。您可以通過訪問特定網站或使用相關工具,檢查當前的外網 IP 是否與代理 IP 匹配,以確保代理設置成功。
注意事項(這是本人寫的,大家要注意)
-
IP 時效性:每條動態 IP 的有效時長通常較短(這里是 1-5 分鐘后就會失效),請根據您的業務需求,合理設置提取頻率和使用策略,當一條IP到期時確保你的爬蟲程序可以自動更換IP。
通過以上步驟,您即可購買并使用四葉天代理的動態 IP 服務,滿足您的網絡代理需求。
二、在Java中動態IP代理的工具類
當你購買完代理IP后,可以參考官方提供的SDK代碼示例來構造自己的IP代理池,如下圖:
?
但是官方代碼的并不適合我的需求,因此本人自己找了一個Java工具類,用于IP代理池的構建和使用,非常方便。
代碼如下(供大家參考):
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.qcby.byspringbootdemo.entity.AgencyIp;
?
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
?
public class YzxIpPoolUtil {//使用線程安全集合類存儲代理ipprivate static CopyOnWriteArraySet<AgencyIp> ipSet = new CopyOnWriteArraySet<>();
?// 使用線程池執行IP驗證任務static ExecutorService pool = Executors.newFixedThreadPool(10);
?
?
?//從ip集合中獲取一個有效的代理IP,若集合中沒有則返回null/*public static AgencyIp getAvailableIp() {if (!ipSet.isEmpty()) {Iterator<AgencyIp> iterator = ipSet.iterator();while (iterator.hasNext()) {AgencyIp ip = iterator.next();if (checkIpAddress(ip)) {return ip;}else {iterator.remove();}}
?}return null;}*/
?/*** 改進版getAvailableIp() 確保返回的ip一定有效* @return*/public static AgencyIp getAvailableIp() {//更新ipSet集合,確保里面有ip且一定可用updateIpSet();for (AgencyIp ip : ipSet) {if (checkIpAddress(ip)) {return ip;}}return null;}
?/*** 更新ipSet集合,刪除無效ip,如果為空則獲取ip*/public static void updateIpSet(){if (!ipSet.isEmpty()) {//刪除無效ipipSet.removeIf(ip -> !checkIpAddress(ip));}
?if (ipSet.isEmpty()) {//如果空,則獲取ipgetIpList();}}
?//抓取ip,放進ip代理池private static void getIpList() {System.out.println("正在抓取IP......");String apiUrl = "http://proxy.siyetian.com/apis_get.html?token=AesJWLNpXR51kaJdXTqFFeNRVS14EVJlXTn1STqFUeORUR41karlXTU1kePRVS10ERNhnTqFFe.wN2YTN0IDNzcTM&limit=1&type=0&time=&data_format=json";
?String resultJsonStr = null;try {resultJsonStr = getData(apiUrl);} catch (IOException e) {System.out.println("ip抓取失敗,請檢查URL是否正確");throw new RuntimeException("ip抓取失敗");}JSONObject jsonObject = JSON.parseObject(resultJsonStr);
?// 從返回的數據中提取代理列表if (jsonObject.getIntValue("code") == 1) {JSONArray data = (JSONArray) jsonObject.get("data");for (int i = 0;i<data.size();i++){// 創建 AgencyIp 對象AgencyIp agencyIp = new AgencyIp();agencyIp.setAddress(data.getJSONObject(i).get("ip").toString());agencyIp.setPort((int)data.getJSONObject(i).get("port"));
?if (checkIpAddress(agencyIp)){ipSet.add(agencyIp);System.out.println("已經放入集合一個ip:"+agencyIp.toString());}
?
/*開啟子線程檢查該IP是否可用(選用)*如果不使用子線程,* 而是在主線程中依次檢查每個 IP 的可用性,* 那么每次檢查都需要等待上一次檢查完成,這個過程是順序執行的。* 當 IP 數量較多或者檢查 IP 可用性的操作(比如發起網絡請求去驗證等)比較耗時的時候,* 主線程就會被長時間阻塞,導致后續其他代碼無法及時執行,影響整個程序的響應速度和執行效率。* 而通過開啟子線程,可以讓多個 IP 的可用性檢查操作并發進行,* 主線程不必等待每個檢查操作結束就能繼續往下執行其他任務,比如繼續去抓取更多 IP* 提升了整體的執行效率。
*/// ? ? ? ? ? ? ? pool.execute(new Runnable() {
// ? ? ? ? ? ? ? ? ? @Override
// ? ? ? ? ? ? ? ? ? public void run() {
// ? ? ? ? ? ? ? ? ? ? ? if (checkIpAddress(agencyIp)){
// ? ? ? ? ? ? ? ? ? ? ? ? ? ipSet.add(agencyIp);
// ? ? ? ? ? ? ? ? ? ? ? }
// ? ? ? ? ? ? ? ? ? }
// ? ? ? ? ? ? ? });}}else {System.out.println("抓取代理IP失敗,狀態碼為0");}}
?// 檢查代理IP地址是否有效public static boolean checkIpAddress(AgencyIp agencyIp) {if(agencyIp.getAddress()==null){return false;}Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(agencyIp.getAddress(), agencyIp.getPort()));HttpURLConnection connection = null;int retries = 3; ?// 重試次數while (retries > 0) {try {connection = (HttpURLConnection) new URL("https://www.baidu.com/").openConnection(proxy);connection.setConnectTimeout(3000); ?// 設置連接超時connection.setReadTimeout(3000); ? ? // 設置讀取超時connection.setUseCaches(false);
?if (connection.getResponseCode() == 200) {System.out.println(agencyIp.getAddress() + " 該IP有效");return true;}} catch (IOException e) {System.out.println(agencyIp.getAddress() + " 該IP無效,原因:" + e.getMessage());retries--;if (retries == 0) {System.out.println(agencyIp.getAddress() + " 無效代理,嘗試 " + (3 - retries) + " 次后失敗");}}}return false;}
?// 獲取指定url內容private static String getData(String requestUrl) throws IOException {URL url = new URL(requestUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.connect();//建立連接(可選)//InputStream是字節流,下行代碼是把字節流轉換為字符流,然后再轉換為BufferedReader字符流,以便于按行讀取數據BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));//StringBuffer(線程安全的可變字符序列)StringBuffer buffer = new StringBuffer();String str;while ((str = reader.readLine()) != null) {buffer.append(str);}if (buffer.length() == 0) {buffer.append("[]");}String result = buffer.toString();reader.close();//關閉BufferedReaderconn.disconnect();//關閉連接(可選)return result;}
?public static void main(String[] args) {updateIpSet();System.out.println(ipSet.toString());}
}
?
下面是對這段代碼中每個方法的功能總結:
類變量
-
ipSet
:使用線程安全的CopyOnWriteArraySet
存儲代理 IP 的集合。 -
pool
:線程池,用于執行 IP 驗證任務。
方法列表
-
getAvailableIp
-
功能:從
ipSet
中獲取一個有效的代理 IP。 -
實現:
調用
updateIpSet
方法,確保集合中的 IP 是最新且有效的。遍歷
ipSet
,找到并返回第一個可用的 IP。
-
-
updateIpSet
-
功能:更新
ipSet
,刪除無效 IP。如果集合為空,則調用getIpList
獲取新的 IP。 -
實現:
遍歷
ipSet
,調用checkIpAddress
方法,移除不可用的 IP。如果集合為空,調用
getIpList
抓取新的 IP。
-
-
getIpList
-
功能:從指定的 API 地址抓取代理 IP,并添加到
ipSet
中。 -
實現:
調用
getData
方法從指定 URL 獲取 JSON 數據。解析 JSON,提取 IP 和端口,創建
AgencyIp
對象。檢查每個 IP 的可用性(調用
checkIpAddress
),將有效 IP 添加到集合中。
-
-
checkIpAddress
-
功能:檢查代理 IP 地址是否有效。
-
實現:
使用
Proxy
類設置代理。嘗試通過代理訪問
https://www.baidu.com/
。如果響應碼為 200,表示代理有效。
支持多次重試(3 次)。
-
-
getData
-
功能:從指定的 URL 獲取內容并返回為字符串。
-
實現:
通過
HttpURLConnection
建立連接。讀取響應內容并返回。
-
主方法
-
main
方法:功能:測試
updateIpSet
方法,并打印當前ipSet
中的代理 IP。
總結
-
核心流程:程序通過 API 抓取代理 IP,驗證其可用性后存入集合,支持動態更新和多線程并發驗證。
-
線程安全:利用
CopyOnWriteArraySet
和線程池確保在多線程環境下數據操作的安全性。
三、如何使用動態Ip進行網站訪問
項目邏輯:每次開始爬蟲前,使用工具類從ip池中獲取一條可用ip和端口號,并用此ip進行代理爬蟲,以防止本機IP被封。
下面分別給出使用 Jsoup 和 HttpClient 結合代理 IP 訪問網站的示例代碼:
1. Jsoup 使用代理 IP
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
?
public class JsoupProxyExample {public static void main(String[] args) {// 獲取代理 IP 和端口號(通過工具類獲取)String proxyIp = "127.0.0.1";int proxyPort = 8080;
?// 要訪問的目標 URLString targetUrl = "https://www.baidu.com";
?try {// 配置 Jsoup 使用代理 IPDocument document = Jsoup.connect(targetUrl).proxy(proxyIp, proxyPort) // 設置代理IP和端口號.timeout(5000) ? ? ? ? ? ?// 設置超時時間.get(); ? ? ? ? ? ? ? ? ? // 發送 GET 請求
?// 打印響應內容System.out.println(document.title());} catch (IOException e) {System.out.println("請求失敗,錯誤信息: " + e.getMessage());}}
}
2. HttpClient 使用代理 IP
import org.apache.http.HttpHost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
?
import java.io.IOException;
?
public class HttpClientProxyExample {public static void main(String[] args) {// 獲取代理 IP 和端口號(通過工具類獲取)String proxyIp = "127.0.0.1";int proxyPort = 8080;
?// 要訪問的目標 URLString targetUrl = "https://www.baidu.com";
?// 配置 HttpClient 使用代理ip和端口號HttpHost proxy = new HttpHost(proxyIp, proxyPort);CloseableHttpClient httpClient = HttpClients.custom().setProxy(proxy) // 設置代理.build();
?
// 這里用 HTTP GET 請求作為演示,你也可以使用post請求并攜帶一些參數HttpGet httpGet = new HttpGet(targetUrl);
?try {CloseableHttpResponse response = httpClient.execute(httpGet)// 打印響應狀態碼System.out.println("Response Status: " + response.getStatusLine());
?// 打印響應內容String responseBody = EntityUtils.toString(response.getEntity());System.out.println("Response Content: \n" + responseBody);} catch (IOException e) {System.out.println("請求失敗,錯誤信息: " + e.getMessage());} finally {try {httpClient.close();} catch (IOException e) {System.out.println("關閉 HttpClient 時出錯: " + e.getMessage());}}}
}
注意事項
-
超時處理:建議設置超時時間,避免長時間等待。
-
目標網站設置:確保目標網站允許被代理訪問,并避免觸發反爬機制。
可以根據你的業務場景,將上述代碼與 IP 池工具類集成,實現高效的代理訪問功能。
四、實際爬蟲過程中的注意事項
每條IP的有效時間為1~5分鐘,如果爬蟲過程中當前代理ip失效時,程序可以自動切換IP,并從當前爬到的頁數開始繼續爬蟲。
實現思路:
- 首先,封裝一個爬蟲方法crawler( ),接收參數是int類型的頁碼,返回信息是String類型字符串,該方法若成功執行完畢則返回"success";
- 用try-catch包裹爬蟲核心代碼,當捕捉到異常后,調用工具類的checkIpAddress( )方法檢查當前ip是否有效,若當前ip有效,則代表異常不是因為IP失效引起的,此時應手動拋出“爬蟲異常”,并自定義異常信息;若當前ip已經失效,則很有可能因為是ip失效引起的異常,此時我們返回一個Json格式的字符串,其中記錄爬蟲中斷時的頁碼和關鍵字。
- 當收到爬蟲方法crawler( )返回的字符串str后,檢查str,若是"success"則表示本次爬蟲成功;否則,則用JSON.isValid( )方法解析該字符串,若解析成功則表示當前需要更換新的代理ip:然后①調用工具類獲取新IP ②記錄下字符串中的頁碼;
- 然后用新IP和當前頁碼再次調用爬蟲方法crawler( )繼續爬蟲。
下面是用ChatGPT進行的一個上述思路的總結(如果覺得上面的大段字看著費勁,可以參考下面的🐶):
A.爬蟲方法 crawler( )
的實現要求:
方法功能
- 方法名稱:
crawler( )
- 參數:
int
類型的頁碼。 - 返回值:
String
類型,表示爬蟲狀態。
執行邏輯
- 核心功能:嘗試爬取指定頁碼的數據。
- 成功時:如果爬取成功,返回
"success"
。 - 異常處理:使用
try-catch
包裹爬蟲核心代碼,捕捉可能出現的異常。捕獲到異常后,調用工具類的checkIpAddress( )
方法,檢查當前代理 IP 是否有效。
- 如果當前 IP 無效:可能因 IP 失效導致爬蟲中斷,返回一個 JSON 格式的字符串,其中包含:中斷時的 關鍵字、中斷時的 頁碼。
- 如果當前 IP 有效:
說明異常不是因 IP 失效引起的,此時需手動拋出“爬蟲異常”。
手動拋出的異常應包含自定義的異常信息。
B.調用 crawler( )
方法的主邏輯:
調用 crawler( )
方法后,接收其返回的字符串 str
。
檢查返回值str
- 如果
str == "success"
:表示本次爬蟲成功,無需進一步操作。 - 如果
str
不是"success"
:- 使用
JSON.isValid( )
方法解析該字符串。 - 若解析成功則表示代理ip失效需要更換新的?IP。
- 執行以下步驟:① 獲取新代理 IP:調用工具類獲取新的代理 IP。
????????????????????????② 記錄頁碼:從str
中提取中斷時的頁碼。 - 使用新 IP 和記錄的頁碼,重新調用
crawler( )
方法,繼續執行爬蟲。
- 執行以下步驟:① 獲取新代理 IP:調用工具類獲取新的代理 IP。
- 使用
循環上述過程,直到爬蟲任務完成。
五、代碼中的亮點:
以下是工具類代碼中使用的一些亮點技術:
1. 線程安全的集合類:CopyOnWriteArraySet
-
特點:
CopyOnWriteArraySet
是基于CopyOnWriteArrayList
實現的線程安全集合,適合在多線程環境下需要頻繁讀取且寫操作較少的場景。 -
應用:用于存儲代理 IP,保證在多線程操作時不出現并發問題。
-
亮點:通過
removeIf
方法移除無效 IP,實現高效、安全的集合操作。
2. 多線程并發操作
-
線程池使用:通過
Executors.newFixedThreadPool(10)
創建固定大小的線程池。 -
目的:并發執行代理 IP 的可用性檢查,提高程序執行效率。
-
亮點:
-
避免每次創建和銷毀線程的開銷,提升性能。
-
子線程的檢查操作不會阻塞主線程,從而使抓取和檢查 IP 可以并發進行。
-
3. 動態更新代理 IP 池
-
邏輯:updateIpSet()
方法會定期更新 IP 池:
-
刪除無效 IP。
-
當 IP 池為空時,自動調用
getIpList
方法獲取新的代理 IP。
-
-
亮點:
-
保證了代理 IP 池的有效性,避免程序因代理失效而中斷。
-
自動化管理 IP 池,減少了人工干預的需求。
-
4. JSON 數據解析
-
工具:使用
fastjson2
解析 JSON 數據。 -
功能:
-
從代理 IP 提供商的 API 返回結果中提取 IP 地址和端口。
-
動態構造
AgencyIp
對象,并添加到代理 IP 池中。
-
-
亮點:
fastjson2
提供了高效、簡潔的 JSON 解析方式,適合處理復雜數據結構。
5. 動態代理ip的網絡連接檢查
-
技術:通過
Proxy
類創建 HTTP 動態代理,并使用HttpURLConnection
檢查代理 IP 的可用性。 -
亮點:
-
使用
Proxy.Type.HTTP
動態指定代理服務器和端口。 -
設置連接超時和讀取超時,防止長時間阻塞。
-
支持多次重試機制,提高代理驗證的魯棒性。
-
6. 可擴展的代理 IP 池管理邏輯
-
結構設計:
-
getAvailableIp
方法封裝了獲取有效 IP 的邏輯,確保返回的 IP 一定有效。 -
checkIpAddress
方法獨立負責代理 IP 的可用性檢查,職責清晰。 -
getIpList
方法負責動態從 API 獲取新的代理 IP。
-
-
亮點:模塊化設計,代碼清晰且易于擴展,便于日后維護和功能拓展。
7. 異常處理機制
-
重試機制:在
checkIpAddress
方法中,加入了重試邏輯,當某次檢查失敗時會進行多次嘗試。 -
故障恢復:當抓取 IP 或檢查 IP 時發生異常,提供了詳細的日志信息,有助于問題排查。
-
亮點:通過異常捕獲和日志記錄,使程序更穩定、可靠。
8. 自動化與高效性
-
亮點:
-
自動化抓取和更新代理 IP,減少了手動操作。
-
結合多線程和動態代理技術,提升了執行效率。
-
總結
代碼通過線程安全集合、多線程處理、動態代理、JSON 數據解析等技術,高效實現了一個可靠的代理 IP 池管理工具。代碼模塊化清晰,具有良好的擴展性和穩定性,非常適合在分布式爬蟲或高并發網絡請求場景中應用。