如何在Java爬虫中设置动态延迟以避免API限制

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
实时计算 Flink 版,1000CU*H 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
简介: 如何在Java爬虫中设置动态延迟以避免API限制

一、动态延迟与代理服务器的重要性

  1. 动态延迟的重要性
    动态延迟是指根据爬虫运行时的环境和API的响应情况,动态调整请求之间的间隔时间。与静态延迟(固定时间间隔)相比,动态延迟能够更灵活地应对API的限制策略,同时最大化爬虫的效率。动态延迟的重要性体现在以下几个方面:
    ● 避免被封禁:通过合理调整请求间隔,爬虫可以避免因请求频率过高而触发API的限制机制。
    ● 提高效率:动态延迟可以根据API的响应时间调整请求间隔,从而在不触发限制的情况下,尽可能提高爬取速度。
    ● 适应性更强:不同API的限制策略可能不同,动态延迟可以根据具体的API响应调整策略,具有更强的适应性。
    二、动态延迟的实现策略
    在Java爬虫中,动态延迟可以通过以下几种策略实现:
  2. 基于API响应时间的延迟调整
    API的响应时间可以作为动态延迟的重要参考。如果API响应时间较短,说明当前请求频率可能较低,可以适当减少延迟;如果响应时间较长,说明可能接近API的限制,需要增加延迟。
  3. 基于错误码的延迟调整
    许多API在达到请求频率限制时会返回特定的错误码(如429 Too Many Requests)。爬虫可以根据这些错误码动态调整延迟。
  4. 基于滑动窗口算法的延迟调整
    滑动窗口算法是一种常用的流量控制算法,可以动态调整请求频率,确保在一定时间窗口内的请求次数不超过API的限制。
    三、基于API响应时间的动态延迟实现(结合代理服务器)
    以下是基于API响应时间的动态延迟实现代码示例,同时结合了代理服务器的使用:
    ```import java.io.IOException;
    import java.net.HttpURLConnection;
    import java.net.InetSocketAddress;
    import java.net.Proxy;
    import java.net.URL;
    import java.util.concurrent.TimeUnit;

public class DynamicDelayCrawlerWithProxy {
private static final String PROXY_HOST = "www.16yun.cn";
private static final int PROXY_PORT = 5445;
private static final String PROXY_USER = "16QMSOML";
private static final String PROXY_PASS = "280651";

private static final int MIN_DELAY = 100; // 最小延迟时间(毫秒)
private static final int MAX_DELAY = 5000; // 最大延迟时间(毫秒)
private static final int TARGET_RESPONSE_TIME = 500; // 目标响应时间(毫秒)

public static void main(String[] args) {
    String apiUrl = "https://api.example.com/data";
    int delay = MIN_DELAY;

    // 设置代理服务器
    System.setProperty("java.net.useSystemProxies", "true");
    System.setProperty("http.proxyHost", PROXY_HOST);
    System.setProperty("http.proxyPort", String.valueOf(PROXY_PORT));
    System.setProperty("https.proxyHost", PROXY_HOST);
    System.setProperty("https.proxyPort", String.valueOf(PROXY_PORT));

    // 设置代理认证
    System.setProperty("java.net.useSystemProxies", "true");
    System.setProperty("http.proxyUser", PROXY_USER);
    System.setProperty("http.proxyPassword", PROXY_PASS);
    System.setProperty("https.proxyUser", PROXY_USER);
    System.setProperty("https.proxyPassword", PROXY_PASS);

    while (true) {
        long startTime = System.currentTimeMillis();
        try {
            // 发起请求
            URL url = new URL(apiUrl);
            Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT));
            HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            connection.connect();

            int responseCode = connection.getResponseCode();
            if (responseCode == 200) {
                // 请求成功,处理响应数据
                System.out.println("Data fetched successfully.");
            } else {
                System.out.println("Failed to fetch data. Response Code: " + responseCode);
            }
        } catch (IOException e) {
            System.out.println("Error occurred: " + e.getMessage());
        }

        long endTime = System.currentTimeMillis();
        long responseTime = endTime - startTime;

        // 根据响应时间调整延迟
        if (responseTime < TARGET_RESPONSE_TIME) {
            delay = Math.max(MIN_DELAY, delay - 100); // 减少延迟
        } else {
            delay = Math.min(MAX_DELAY, delay + 100); // 增加延迟
        }

        // 等待下一次请求
        try {
            TimeUnit.MILLISECONDS.sleep(delay);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

}

代码解析
1. 最小和最大延迟时间:通过MIN_DELAY和MAX_DELAY设置动态延迟的范围。
2. 目标响应时间:通过TARGET_RESPONSE_TIME设置期望的API响应时间。
3. 请求与响应处理:使用HttpURLConnection发起请求,并根据响应时间调整延迟。
4. 动态调整延迟:如果响应时间小于目标响应时间,则减少延迟;否则增加延迟。
四、基于错误码的动态延迟实现
当API返回429错误码时,说明请求频率过高。此时可以动态增加延迟,直到API恢复正常响应。以下是基于错误码的动态延迟实现代码示例:
```import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.util.concurrent.TimeUnit;

public class ErrorBasedDynamicDelayCrawlerWithProxy {
    private static final String PROXY_HOST = "www.16yun.cn";
    private static final int PROXY_PORT = 5445;
    private static final String PROXY_USER = "16QMSOML";
    private static final String PROXY_PASS = "280651";

    private static final int MIN_DELAY = 100; // 最小延迟时间(毫秒)
    private static final int MAX_DELAY = 5000; // 最大延迟时间(毫秒)
    private static final int INITIAL_DELAY = 500; // 初始延迟时间(毫秒)

    public static void main(String[] args) {
        String apiUrl = "https://api.example.com/data";
        int delay = INITIAL_DELAY;

        // 设置代理服务器
        System.setProperty("java.net.useSystemProxies", "true");
        System.setProperty("http.proxyHost", PROXY_HOST);
        System.setProperty("http.proxyPort", String.valueOf(PROXY_PORT));
        System.setProperty("https.proxyHost", PROXY_HOST);
        System.setProperty("https.proxyPort", String.valueOf(PROXY_PORT));

        // 设置代理认证
        System.setProperty("java.net.useSystemProxies", "true");
        System.setProperty("http.proxyUser", PROXY_USER);
        System.setProperty("http.proxyPassword", PROXY_PASS);
        System.setProperty("https.proxyUser", PROXY_USER);
        System.setProperty("https.proxyPassword", PROXY_PASS);

        while (true) {
            try {
                URL url = new URL(apiUrl);
                Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT));
                HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(5000);
                connection.setReadTimeout(5000);
                connection.connect();

                int responseCode = connection.getResponseCode();

                if (responseCode == 200) {
                    // 请求成功,处理响应数据
                    System.out.println("Data fetched successfully.");
                    delay = Math.max(MIN_DELAY, delay / 2); // 成功时减少延迟
                } else if (responseCode == 429) {
                    // 请求频率过高,增加延迟
                    System.out.println("Rate limit exceeded. Increasing delay.");
                    delay = Math.min(MAX_DELAY, delay * 2);
                } else {
                    System.out.println("Failed to fetch data. Response Code: " + responseCode);
                }
            } catch (IOException e) {
                System.out.println("Error occurred: " + e.getMessage());
            }

            // 等待下一次请求
            try {
                TimeUnit.MILLISECONDS.sleep(delay);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

代码解析

  1. 初始延迟时间:通过INITIAL_DELAY设置初始延迟。
  2. 错误码处理:当API返回429错误码时,增加延迟;当请求成功时,减少延迟。
  3. 动态调整延迟:根据API的响应状态动态调整请求间隔。
    五、基于滑动窗口算法的动态延迟实现
    滑动窗口算法是一种常用的流量控制算法,可以动态调整请求频率,确保在一定时间窗口内的请求次数不超过API的限制。以下是基于滑动窗口算法的动态延迟实现代码示例:
    ```import java.io.IOException;
    import java.net.HttpURLConnection;
    import java.net.InetSocketAddress;
    import java.net.Proxy;
    import java.net.URL;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.TimeUnit;

public class SlidingWindowCrawlerWithProxy {
private static final String PROXY_HOST = "www.16yun.cn";
private static final int PROXY_PORT = 5445;
private static final String PROXY_USER = "16QMSOML";
private static final String PROXY_PASS = "280651";

private static final int WINDOW_SIZE = 60000; // 时间窗口大小(毫秒)
private static final int MAX_REQUESTS_PER_WINDOW = 100; // 每个时间窗口内的最大请求次数
private static final ConcurrentLinkedQueue<Long> requestTimes = new ConcurrentLinkedQueue<>();

public static void main(String[] args) {
    String apiUrl = "https://api.example.com/data";

    // 设置代理服务器
    System.setProperty("java.net.useSystemProxies", "true");
    System.setProperty("http.proxyHost", PROXY_HOST);
    System.setProperty("http.proxyPort", String.valueOf(PROXY_PORT));
    System.setProperty("https.proxyHost", PROXY_HOST);
    System.setProperty("https.proxyPort", String.valueOf(PROXY_PORT));

    // 设置代理认证
    System.setProperty("java.net.useSystemProxies", "true");
    System.setProperty("http.proxyUser", PROXY_USER);
    System.setProperty("http.proxyPassword", PROXY_PASS);
    System.setProperty("https.proxyUser", PROXY_USER);
    System.setProperty("https.proxyPassword", PROXY_PASS);

    while (true) {
        // 清理超出时间窗口的请求记录
        long currentTime = System.currentTimeMillis();
        while (!requestTimes.isEmpty() && currentTime - requestTimes.peek() > WINDOW_SIZE) {
            requestTimes.poll();
        }

        // 检查是否达到请求频率限制
        if (requestTimes.size() >= MAX_REQUESTS_PER_WINDOW) {
            long delay = WINDOW_SIZE - (currentTime - requestTimes.peek());
            System.out.println("Rate limit exceeded. Waiting for " + delay + " ms.");
            try {
                TimeUnit.MILLISECONDS.sleep(delay);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 发起请求
        try {
            URL url = new URL(apiUrl);
            Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT));
            HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            connection.connect();

            int responseCode = connection.getResponseCode();
            if (responseCode == 200) {
                // 请求成功,处理响应数据
                System.out.println("Data fetched successfully.");
            } else {
                System.out.println("Failed to fetch data. Response Code: " + responseCode);
            }
        } catch (IOException e) {
            System.out.println("Error occurred: " + e.getMessage());
        }

        // 记录请求时间
        requestTimes.add(System.currentTimeMillis());
    }
}

}
```
代码解析

  1. 时间窗口大小:通过WINDOW_SIZE设置时间窗口的大小。
  2. 最大请求次数:通过MAX_REQUESTS_PER_WINDOW设置每个时间窗口内的最大请求次数。
  3. 请求时间记录:使用ConcurrentLinkedQueue记录每次请求的时间。
  4. 动态调整延迟:根据时间窗口内的请求次数动态调整请求间隔。
    六、总结
    在Java爬虫开发中,设置动态延迟是避免API限制的关键技术,而代理服务器的使用则进一步提高了爬虫的稳定性和安全性。通过基于API响应时间、错误码或滑动窗口算法的动态延迟策略,爬虫可以在不触发API限制的情况下,高效地抓取数据。
相关文章
|
27天前
|
Java API 数据处理
Java新特性:使用Stream API重构你的数据处理
Java新特性:使用Stream API重构你的数据处理
|
1月前
|
Java 大数据 API
Java Stream API:现代集合处理与函数式编程
Java Stream API:现代集合处理与函数式编程
193 100
|
1月前
|
Java API 数据处理
Java Stream API:现代集合处理新方式
Java Stream API:现代集合处理新方式
215 101
|
1月前
|
并行计算 Java 大数据
Java Stream API:现代数据处理之道
Java Stream API:现代数据处理之道
189 101
|
1月前
|
安全 Java API
使用 Java 构建强大的 REST API 的四个基本技巧
本文结合探险领域案例,分享Java构建REST API的四大核心策略:统一资源命名、版本控制与自动化文档、安全防护及标准化异常处理,助力开发者打造易用、可维护、安全可靠的稳健API服务。
142 2
|
1月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
1月前
|
存储 数据可视化 Java
Java Stream API 的强大功能
Java Stream API 是 Java 8 引入的重要特性,它改变了集合数据的处理方式。通过声明式语法,开发者可以更简洁地进行过滤、映射、聚合等操作。Stream API 支持惰性求值和并行处理,提升了代码效率和可读性,是现代 Java 开发不可或缺的工具。
Java Stream API 的强大功能
|
1月前
|
存储 JSON 监控
亚马逊:调用促销管理API设置跨店满赠,提升客单价
本文介绍了如何通过亚马逊SP-API实现跨店满赠促销,提升卖家客单价。内容涵盖概念、优势、API调用步骤及代码示例,助力自动化高效促销部署。
81 0
|
6月前
|
数据采集 测试技术 C++
无headers爬虫 vs 带headers爬虫:Python性能对比
无headers爬虫 vs 带headers爬虫:Python性能对比
|
6月前
|
数据采集 存储 监控
Python 原生爬虫教程:网络爬虫的基本概念和认知
网络爬虫是一种自动抓取互联网信息的程序,广泛应用于搜索引擎、数据采集、新闻聚合和价格监控等领域。其工作流程包括 URL 调度、HTTP 请求、页面下载、解析、数据存储及新 URL 发现。Python 因其丰富的库(如 requests、BeautifulSoup、Scrapy)和简洁语法成为爬虫开发的首选语言。然而,在使用爬虫时需注意法律与道德问题,例如遵守 robots.txt 规则、控制请求频率以及合法使用数据,以确保爬虫技术健康有序发展。
921 31