在本次爬虫项目中,关于应用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 {
//使用线程安全集合类存储代理ip
private 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()) {
//删除无效ip
ipSet.removeIf(ip -> !checkIpAddress(ip));
}
if (ipSet.isEmpty()) {
//如果空,则获取ip
getIpList();
}
}
//抓取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();//关闭BufferedReader
conn.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;
// 要访问的目标 URL
String targetUrl = "https://www.baidu.com";
try {
// 配置 Jsoup 使用代理 IP
Document 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;
// 要访问的目标 URL
String 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 池管理工具。代码模块化清晰,具有良好的扩展性和稳定性,非常适合在分布式爬虫或高并发网络请求场景中应用。