You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Restful API与Spring实现多站点数据提取及并发调用?

问题解决指南

1. 前置依赖配置

首先在项目的pom.xml(Maven项目)中添加Jsoup依赖:

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.15.3</version>
</dependency>

如果使用Lombok,可同时添加Lombok依赖简化日志声明。

2. 核心问题修复说明

2.1 log未定义问题

两种解决方案任选其一:

  • 方式1(推荐):在控制器类上添加@Slf4j注解(需要Lombok支持)
  • 方式2:在类中手动声明日志对象:
    private static final Logger log = LoggerFactory.getLogger(APIController.class);
    
    注意需要导入org.slf4j.Logger和org.slf4j.LoggerFactory相关包。

2.2 成功/失败调用统计

在每个网页的抓取逻辑外层包裹try-catch块,并行场景下使用线程安全的原子类统计数值,避免并发数据错误。

2.3 Jsoup使用修正

原有代码硬编码了抓取地址,需要替换为循环遍历的website变量,页面元素选择器可根据目标站点的实际DOM结构调整,如需提取特定内容可通过浏览器F12开发者工具右键对应元素,复制CSS选择器传入select方法即可。

2.4 并行/串行调用实现

根据入参numberOfWebsites动态创建对应大小的线程池:入参为1时线程池大小为1即全串行执行,入参为4时线程池大小为4即全并行执行,搭配CountDownLatch等待所有任务执行完成后再聚合返回结果。

3. 完整实现代码

首先定义结构化返回实体类:

// 如需简化代码可添加@Data注解(Lombok)
public class ScrapeResult {
    private int successCount;
    private int failCount;
    private List<String> extractedTitles;
    private List<String> extractedAddresses;

    // 此处省略getter、setter方法
}

控制器完整代码:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CopyOnWriteArrayList;

@RestController
public class APIController {
    // 解决log未定义问题
    private static final Logger log = LoggerFactory.getLogger(APIController.class);

    private final List<String> websites = Arrays.asList(
            "https://pizzerijalimbo.si/meni/",
            "https://pizzerijalimbo.si/kontakt/",
            "https://pizzerijalimbo.si/my-account/",
            "https://pizzerijalimbo.si/o-nas/"
    );

    @GetMapping("/podatki")
    public ScrapeResult getData(@RequestParam(required = true) int numberOfWebsites) throws InterruptedException {
        // 参数合法性校验
        if (numberOfWebsites < 1 || numberOfWebsites > 4) {
            throw new IllegalArgumentException("参数范围必须为1-4");
        }
        List<String> websitesToScrape = websites.subList(0, numberOfWebsites);

        // 线程安全的统计变量和结果集合
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failCount = new AtomicInteger(0);
        List<String> titles = new CopyOnWriteArrayList<>();
        List<String> addresses = new CopyOnWriteArrayList<>();

        // 按入参创建对应大小线程池,实现并行/串行控制
        ExecutorService executor = Executors.newFixedThreadPool(numberOfWebsites);
        CountDownLatch latch = new CountDownLatch(websitesToScrape.size());

        for (String website : websitesToScrape) {
            executor.submit(() -> {
                try {
                    // Jsoup抓取页面,设置5秒超时
                    Document doc = Jsoup.connect(website).timeout(5000).get();
                    // 提取页面短标题
                    String title = doc.title();
                    log.info("抓取页面成功,标题:{}", title);
                    titles.add(title);
                    // 示例:提取联系页地址,可根据实际需求调整选择器
                    Elements addressElements = doc.select(".contact-content .address");
                    addressElements.forEach(ele -> addresses.add(ele.text().trim()));
                    successCount.incrementAndGet();
                } catch (IOException e) {
                    log.error("抓取页面{}失败:{}", website, e.getMessage());
                    failCount.incrementAndGet();
                } finally {
                    latch.countDown();
                }
            });
        }
        // 最多等待30秒,避免死等
        latch.await(30, TimeUnit.SECONDS);
        executor.shutdown();

        // 组装返回结果
        ScrapeResult result = new ScrapeResult();
        result.setSuccessCount(successCount.get());
        result.setFailCount(failCount.get());
        result.setExtractedTitles(titles);
        result.setExtractedAddresses(addresses);
        return result;
    }
}

4. 注意事项

  • 可根据实际需求调整Jsoup的超时时间、请求头、User-Agent等参数,避免被站点反爬拦截
  • 生产环境建议自定义线程池参数而非使用Executors默认实现,避免资源耗尽风险
  • 页面元素选择器需要和目标站点当前DOM结构匹配,站点更新后需要同步调整选择器规则

内容的提问来源于stack exchange,提问作者AquaBalls

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 09:30:03