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

使用Java Jsoup爬取网站时如何下载静态资源文件

实现网页静态资源同步下载方案

你现有的Jsoup爬取逻辑只保存了原始HTML,补充静态资源下载和路径替换逻辑即可实现浏览器「另存为」的效果,具体修改如下:

核心实现逻辑

  • 为每个HTML文件创建同名的资源存储文件夹,命名规则参考浏览器默认规则:[HTML文件名]_files
  • 解析HTML提取所有静态资源链接:图片、CSS、JS三类核心资源,可按需扩展字体、音视频等资源类型
  • 将资源的相对路径统一转换为原网站的绝对路径,避免跨域或路径错误导致下载失败
  • 下载资源到本地对应文件夹后,替换HTML中原资源链接为本地相对路径,保证本地打开HTML时可正常加载资源

完整修改后代码

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

private List<FileRelation> scrapUrls(List<String> urlsToScrap, String jobPath) throws IOException {
    List<FileRelation> files = new ArrayList<>();
    Document doc;
    for (String url: urlsToScrap) {
        try {
            // 原请求HTML逻辑保留
            Connection connection = Jsoup.connect(url)
                    .userAgent("Mozilla/5.0 (X11; U; Linux i586; en-US; rv:1.7.3) Gecko/20040924 Epiphany/1.4.4 (Ubuntu)")
                    .timeout(5000);
            doc = connection.get();
            String baseUrl = new URL(url).getProtocol() + "://" + new URL(url).getHost();
            
            // 生成HTML文件名和对应资源文件夹
            String fileName = UUID.randomUUID() + ".html";
            String assetFolderName = fileName.replace(".html", "_files");
            String assetFolderPath = jobPath + File.separator + assetFolderName;
            new File(assetFolderPath).mkdirs();
            
            // 提取并下载所有img资源
            Elements imgs = doc.select("img[src]");
            for (Element img : imgs) {
                String src = img.attr("src");
                String absoluteSrc = getAbsoluteUrl(baseUrl, src);
                String localFileName = downloadAsset(absoluteSrc, assetFolderPath);
                if (localFileName != null) {
                    img.attr("src", assetFolderName + "/" + localFileName);
                }
            }
            
            // 提取并下载所有CSS资源
            Elements links = doc.select("link[rel=stylesheet][href]");
            for (Element link : links) {
                String href = link.attr("href");
                String absoluteHref = getAbsoluteUrl(baseUrl, href);
                String localFileName = downloadAsset(absoluteHref, assetFolderPath);
                if (localFileName != null) {
                    link.attr("href", assetFolderName + "/" + localFileName);
                }
            }
            
            // 提取并下载所有JS资源
            Elements scripts = doc.select("script[src]");
            for (Element script : scripts) {
                String src = script.attr("src");
                String absoluteSrc = getAbsoluteUrl(baseUrl, src);
                String localFileName = downloadAsset(absoluteSrc, assetFolderPath);
                if (localFileName != null) {
                    script.attr("src", assetFolderName + "/" + localFileName);
                }
            }
            
            // 保存修改后的HTML
            BufferedWriter writer =
                    new BufferedWriter(
                            new FileWriter(jobPath + File.separator + fileName)
                    );
            writer.write(doc.html());
            writer.close();

            FileRelation file = new FileRelation(fileName, jobPath);
            files.add(file);
        } catch (Exception e) {
            System.out.println("ScarpingService - " + e);
        }
    }
    return files;
}

// 辅助方法:相对路径转绝对路径
private String getAbsoluteUrl(String baseUrl, String path) {
    if (path.startsWith("http")) {
        return path;
    }
    if (path.startsWith("//")) {
        return "https:" + path;
    }
    return baseUrl + (path.startsWith("/") ? path : "/" + path);
}

// 辅助方法:下载静态资源到本地文件夹
private String downloadAsset(String assetUrl, String saveFolderPath) {
    try {
        URL url = new URL(assetUrl);
        String fileName = new File(url.getPath()).getName();
        // 处理文件名为空的情况
        if (fileName.isBlank()) {
            fileName = UUID.randomUUID().toString();
        }
        // 避免文件名冲突可加UUID前缀,不需要可以删掉
        fileName = UUID.randomUUID() + "_" + fileName;
        File saveFile = new File(saveFolderPath + File.separator + fileName);
        
        try (InputStream in = url.openStream();
             FileOutputStream out = new FileOutputStream(saveFile)) {
            byte[] buffer = new byte[4096];
            int len;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
        }
        return fileName;
    } catch (Exception e) {
        // 资源下载失败直接跳过,不影响主流程
        System.out.println("资源下载失败:" + assetUrl + ",错误信息:" + e.getMessage());
        return null;
    }
}

补充说明

  • 代码里用File.separator替代了硬编码的\\,适配Windows、Linux等不同操作系统的路径规则
  • 资源下载失败时会直接跳过,保留原链接不替换,不会影响HTML的正常打开
  • 如果需要下载CSS里引用的字体、背景图等资源,需要额外解析CSS文件内容提取对应链接下载,再替换CSS里的路径
  • 可以根据需求调整静态资源的选择器,比如添加video[src]、audio[src]等选择器下载音视频资源

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 18:45:03