最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實(shí)現(xiàn)Html保存為.mhtml文件的代碼邏輯

 更新時(shí)間:2026年01月19日 10:10:43   作者:知秋正在996  
文章介紹了實(shí)現(xiàn)將HTML字符串保存為.mhtml文件的代碼邏輯,包括通過(guò)URL和Cookie免密獲取HTML字符串,將HTML中的圖片、CSS、JS轉(zhuǎn)換為base64字符串,刪除不需要的布局和內(nèi)容,最終將替換后的HTML保存為.mhtml文件,感興趣的朋友跟隨小編一起看看吧

功能需求

將html字符串保存為.mhtml文件

代碼實(shí)現(xiàn)

  • pom.xml依賴
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.43</version>
        </dependency>
        <!-- Jsoup:解析HTML標(biāo)簽、提取圖片/樣式資源,必備 -->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.17.2</version>
        </dependency>
        <!-- Apache工具包:Base64編碼圖片資源、IO流處理,必備 -->
        <!-- Source: https://mvnrepository.com/artifact/commons-codec/commons-codec -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
            <scope>compile</scope>
        </dependency>
        <!-- Source: https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.15.1</version>
            <scope>compile</scope>
        </dependency>
        <!-- Source: https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
  • 獲取通過(guò)訪問(wèn)url獲取html字符串內(nèi)容工具類
@Slf4j
public class WikiUtils {
    /**
     * 獲取wiki 頁(yè)面html
     */
    public static String getConfluencePageHtml(String url,String cookie) {
        String value = "";
        HttpResponse httpResponse = HttpClient.httpGetResponse(url, cookie);
        if (httpResponse.isOk()){
            value = httpResponse.body();
        }else if (httpResponse.getStatus() == 403|| httpResponse.getStatus() == 302){
            log.error("無(wú)效的cookie,無(wú)權(quán)限訪問(wèn)");
        }else {
            log.error("獲取html頁(yè)面失敗");
        }
        return value;
    }
    /**
     * 在請(qǐng)求頭中放入cookie,避免登錄攔截
     */
    public static HttpResponse httpGetResponse(String url,String cookie) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Cookie", cookie);
        //登錄
        HttpResponse response = HttpRequest.get(url).headerMap(headers, true).execute();
        return response;
    }
}
  • Html轉(zhuǎn)換.mhtml核心類
@Slf4j
public class Html2MHTCompiler {
    public static String parseTittle(String html) {
        Document doc = Jsoup.parse(html);
        Element titleElement = doc.selectFirst("title");
        if (titleElement != null) {
            String text = titleElement.text();
            int i = text.indexOf("-");
            if (i > 0) {
                return text.substring(0, i).trim();
            }
            return  text.trim();
        }
        return null;
    }
    // 原資源URL  -> 資源的Base64編碼(帶MIME頭)
    public static Map<String, String> parseHtmlPage(String cookie,String html, String baseUrl) {
        Map<String, String> resourceMap = new HashMap<>();
        Document doc = Jsoup.parse(html);
        // ========== 1. 提取所有 img 圖片資源 ==========
        Elements imgElements = doc.select("img[src]");
        for (Element imgElement : imgElements) {
            String imgSrc = imgElement.attr("src");
            parseResource(cookie,imgSrc,"image",baseUrl, resourceMap);
        }
        // ========== 2. 提取所有 link 外鏈CSS樣式表資源==========
        Elements cssElements = doc.select("link[rel=stylesheet][href]");
        for (Element cssElement : cssElements) {
            String cssHref = cssElement.attr("href");
            parseResource(cookie,cssHref, "CSS",baseUrl, resourceMap);
        }
        // ========== 3. 提取所有 script 外鏈JS腳本資源 ==========
        Elements jsElements = doc.select("script[src]");
        for (Element jsElement : jsElements) {
            String jsSrc = jsElement.attr("src");
            parseResource(cookie,jsSrc,"javascript",baseUrl, resourceMap);
        }
        return resourceMap;
    }
    // ========== 刪除部分元素class="acs-side-bar ia-scrollable-section" 、
    // class="ia-splitter-left"、
    // id="header"
    // id="navigation"
    // id="likes-and-labels-container"、
    // id="footer" 、
    // id="comments-section"
    // id="page-metadata-banner"
    // id="breadcrumb-section"
    // 、id="main"的style="margin-left: 285px;" ==========
    public static String removeUnwantedElements(String html) {
        Document doc = Jsoup.parse(html);
        //刪除head標(biāo)簽下的style標(biāo)簽的屬性中的.ia-splitter-left  #main 這兩個(gè)選擇器
        removeCssSelectorFromStyleTag(doc, ".ia-splitter-left");
        removeCssSelectorFromStyleTag(doc, "#main");
        // 1. 刪除指定class的元素 → 側(cè)邊欄/左側(cè)面板 等冗余區(qū)域
        doc.select(".acs-side-bar .ia-scrollable-section").remove();
        doc.select(".ia-splitter-left").remove();
        // 2. 刪除指定id的元素 → 點(diǎn)贊標(biāo)簽區(qū)、頁(yè)腳、評(píng)論區(qū) 等無(wú)用模塊
//        doc.getElementById("likes-and-labels-container").remove();
        doc.getElementById("footer").remove();
        doc.getElementById("header").remove();
        doc.getElementById("navigation").remove();
        doc.getElementById("comments-section").remove();
        doc.getElementById("page-metadata-banner").remove();
        doc.getElementById("breadcrumb-section").remove();
        // 3. 精準(zhǔn)移除 id="main" 標(biāo)簽中【指定的style樣式:margin-left: 285px;】,保留其他style樣式
        Element mainElement = doc.getElementById("main");
        if (mainElement != null && mainElement.hasAttr("style")) {
            // 獲取原style屬性值
            String oldStyle = mainElement.attr("style");
            // 移除指定的樣式段,保留其他樣式
            String newStyle = oldStyle.replace("margin-left: 285px;", "").trim();
            // 處理移除后style為空的情況,避免殘留空的style=""屬性
            if (newStyle.isEmpty()) {
                mainElement.removeAttr("style");
            } else {
                mainElement.attr("style", newStyle);
            }
        }
        return doc.html();
    }
    /**
     * 核心工具方法:刪除<head>標(biāo)簽下所有<style>標(biāo)簽內(nèi)的【指定CSS選擇器】及其對(duì)應(yīng)的所有樣式
     * @param doc jsoup解析后的文檔對(duì)象
     * @param selector 要?jiǎng)h除的css選擇器,如:.ia-splitter-left 、 #main
     */
    private static void removeCssSelectorFromStyleTag(Document doc, String selector) {
        // 1. 獲取head標(biāo)簽下所有的style樣式標(biāo)簽
        Elements styleTags = doc.head().select("style");
        if (styleTags.isEmpty()) {
            return; // 沒有style標(biāo)簽,直接返回
        }
        // 2. 遍歷每一個(gè)style標(biāo)簽,處理內(nèi)部的css內(nèi)容
        for (Element styleTag : styleTags) {
            String cssContent = styleTag.html();
            if (cssContent.isEmpty()) continue;
            // 3. 精準(zhǔn)匹配【選擇器 { 任意樣式內(nèi)容 }】 完整塊,含換行/空格/制表符,匹配規(guī)則全覆蓋
            // 匹配規(guī)則:匹配 .ia-splitter-left { ... }  或  #main { ... }  完整的樣式塊
            String regex = selector + "\\s*\\{[^}]*\\}";
            // 替換匹配到的內(nèi)容為空,即刪除該選擇器及對(duì)應(yīng)樣式
            String newCssContent = cssContent.replaceAll(regex, "").trim();
            // 處理替換后多余的空行/空格,讓css內(nèi)容更整潔
            newCssContent = newCssContent.replaceAll("\\n+", "\n").replaceAll("\\s+", " ");
            // 4. 將處理后的css內(nèi)容重新寫入style標(biāo)簽
            styleTag.html(newCssContent);
        }
    }
    // ========== 圖片/CSS/JS都復(fù)用這個(gè)方法 ==========
    private static void parseResource(String cookie,String resourceSrc,String resourceType,String baseUrl, Map<String, String> resourceMap) {
        try {
            // 拼接完整URL(兼容:絕對(duì)路徑/相對(duì)路徑)
            String fullResourceUrl = getFullUrl(baseUrl, resourceSrc);
            // 下載資源文件,轉(zhuǎn)成【帶MIME頭的Base64編碼】
            String base64Resource = downloadResourceToBase64(fullResourceUrl,resourceType, cookie);
            resourceMap.put(resourceSrc, base64Resource);
        } catch (Exception e) {
            log.error("資源解析失敗,跳過(guò)該資源:" + resourceSrc, e);
        }
    }
    // 拼接完整URL:處理相對(duì)路徑/絕對(duì)路徑 (原有方法,復(fù)用)
    private static String getFullUrl(String baseUrl, String src) {
        if (src.startsWith("http://") || src.startsWith("https://")) {
            return src; // 絕對(duì)路徑,直接返回
        } else if(src.startsWith("http://")){
            return "https:" + src; // 兼容 //xxx.com/xxx.css 這種無(wú)協(xié)議路徑
        } else {
            return src.startsWith("/") ? baseUrl + src : baseUrl + "/" + src; // 相對(duì)路徑,拼接根路徑
        }
    }
    // ========== 通用資源下載+Base64編碼方法,支持【圖片/CSS/JS】所有類型 ==========
    private static String downloadResourceToBase64(String resourceUrl,String resourceType,String cookie) throws Exception {
        URL url = new URL(resourceUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Cookie",cookie);
        // 解決部分網(wǎng)站的反爬/跨域問(wèn)題
        conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0");
        conn.setRequestProperty("Connection", "keep-alive");
        conn.setRequestProperty("Accept", "*/*");
        if (resourceType.equals("image")){
            conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
        }
        if (conn.getResponseCode() == 200) {
            InputStream in = conn.getInputStream();
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            byte[] resourceBytes = out.toByteArray();
            // 對(duì)圖片類型做【體積壓縮+無(wú)損渲染】處理
            if ("image".equalsIgnoreCase(resourceType) && resourceBytes.length > 0) {
                resourceBytes = compressImage(resourceBytes, 0.7f); // 0.7是壓縮質(zhì)量,可調(diào)整
            }
            // 獲取資源的MIME類型 + Base64編碼,自動(dòng)適配圖片/CSS/JS
            String mimeType = conn.getContentType();
            String base64 = Base64.encodeBase64String(resourceBytes);
            in.close();
            out.close();
            conn.disconnect();
            // 返回標(biāo)準(zhǔn)的data-url格式,可直接嵌入HTML替換原URL
            return "data:" + mimeType + ";base64," + base64;
        }
        return null;
    }
    /**
     * 核心圖片壓縮工具方法:圖片質(zhì)量壓縮(核心無(wú)坑)
     * @param imageBytes 原圖字節(jié)流
     * @param quality 壓縮質(zhì)量 0.1~1.0 ,推薦0.6~0.8 (數(shù)值越大越清晰,體積越大)
     * @return 壓縮后的圖片字節(jié)流
     */
    private static byte[] compressImage(byte[] imageBytes, float quality) throws Exception {
        // 質(zhì)量值兜底,防止傳參錯(cuò)誤
        if (quality < 0.1f) quality = 0.1f;
        if (quality > 1.0f) quality = 1.0f;
        ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
        BufferedImage bufferedImage = ImageIO.read(bais);
        if (bufferedImage == null) {
            return imageBytes; // 非標(biāo)準(zhǔn)圖片,返回原圖
        }
        // 獲取圖片格式(png/jpg等)
        String format = getImageFormat(imageBytes);
        if (format == null) {
            format = "jpeg";
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 質(zhì)量壓縮,尺寸不變,清晰度無(wú)損,體積減小
        ImageIO.write(bufferedImage, format, new MemoryCacheImageOutputStream(baos) {
            @Override
            public void write(byte[] b, int off, int len) {
                try {
                    super.write(b, off, len);
                } catch (Exception e) {
                    // 異常時(shí)直接寫入原圖,不影響
                }
            }
        });
        // 如果壓縮后體積變大,返回原圖
        byte[] compressedBytes = baos.toByteArray();
        bais.close();
        baos.close();
        return compressedBytes.length < imageBytes.length ? compressedBytes : imageBytes;
    }
    /**
     * 獲取圖片真實(shí)格式
     */
    private static String getImageFormat(byte[] imageBytes) throws Exception {
        ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
        ImageInputStream iis = ImageIO.createImageInputStream(bais);
        Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
        if (readers.hasNext()) {
            ImageReader reader = readers.next();
            String format = reader.getFormatName();
            iis.close();
            bais.close();
            return format;
        }
        iis.close();
        bais.close();
        return null;
    }
    public static String embedResources(String html, Map<String, String> resources) {
        String embeddedHtml = html;
        // 遍歷所有資源,替換原URL為Base64編碼
        for (Map.Entry<String, String> entry : resources.entrySet()) {
            String resourceUrl = entry.getKey();
            String resourceUrlEscape = resourceUrl.replace("&", "&");
            String embeddedUrl = entry.getValue();
            embeddedHtml = embeddedHtml.replace(resourceUrlEscape, embeddedUrl);
        }
        return embeddedHtml;
    }
    public static void saveAsMhtml(String html, String filePath) {
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(filePath), StandardCharsets.UTF_8)
        )) {
            // 寫入MHTML標(biāo)準(zhǔn)協(xié)議頭
            writer.write("MIME-Version: 1.0");
            writer.newLine();
            writer.write("Content-Type: multipart/related; boundary=\"boundary\"");
            writer.newLine();
            writer.newLine();
            // 寫入內(nèi)容邊界開始標(biāo)識(shí)
            writer.write("--boundary");
            writer.newLine();
            writer.write("Content-Type: text/html; charset=UTF-8");
            writer.newLine();
            writer.newLine();
            // 寫入核心的、已嵌入所有資源的HTML內(nèi)容
            writer.write(html);
            writer.newLine();
            writer.newLine();
            // 寫入MHTML結(jié)束邊界標(biāo)識(shí)(必須寫,否則文件格式不完整)
            writer.write("--boundary--");
            writer.flush();
        }catch (IOException e){
            log.error("保存MHTML文件失?。? + filePath, e);
        }
    }

邏輯調(diào)用:

  1. 通過(guò)url和cookie免密獲取html字符串
  2. 獲取html中的圖片、CSS、JS轉(zhuǎn)成base64的字符串,因?yàn)?mhtml文件中超鏈接類型的樣式無(wú)法渲染
  3. 刪除html中不需要的布局和內(nèi)容
  4. 使用2. 中獲取的圖片、CSS、JS轉(zhuǎn)成base64的字符串 替換html字符串中的超鏈接
  5. 保存為.mhtml文件
                    String html = WikiUtils.getConfluencePageHtml(link, cookie);
                    if (html.isEmpty()){
                        log.error("獲取html頁(yè)面失敗");
                        return;
                    }
                    Map<String, String> htmlMap = Html2MHTCompiler.parseHtmlPage(cookie, html, properties.baseURL);
                    String tittle = Html2MHTCompiler.parseTittle(html);
                    String html2 = Html2MHTCompiler.removeUnwantedElements(html);
                    String parseHtml = Html2MHTCompiler.embedResources(html2, htmlMap);
                    Html2MHTCompiler.saveAsMhtml(parseHtml, currentDir+File.separator + tittle + ".mhtml");

到此這篇關(guān)于Java實(shí)現(xiàn)Html保存為.mhtml文件的代碼邏輯的文章就介紹到這了,更多相關(guān)java html保存為.mhtml文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Springboot項(xiàng)目中定時(shí)任務(wù)的四種實(shí)現(xiàn)方式詳解

    Springboot項(xiàng)目中定時(shí)任務(wù)的四種實(shí)現(xiàn)方式詳解

    Spring的@Scheduled注解是一種非常簡(jiǎn)單和便捷的實(shí)現(xiàn)定時(shí)任務(wù)的方式,通過(guò)在方法上添加@Scheduled注解,我們可以指定方法在特定的時(shí)間間隔或固定的時(shí)間點(diǎn)執(zhí)行,本文給大家介紹Springboot項(xiàng)目中定時(shí)任務(wù)的四種實(shí)現(xiàn)方式,感興趣的的朋友一起看看b
    2024-02-02
  • 詳解JAVA中ListIterator和Iterator的辨析

    詳解JAVA中ListIterator和Iterator的辨析

    這篇文章主要為大家詳細(xì)介紹了JAVAListIterator和Iterator的辨析,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-02-02
  • SpringMVC配置JSP視圖解析器的最佳實(shí)踐

    SpringMVC配置JSP視圖解析器的最佳實(shí)踐

    當(dāng)你在深夜調(diào)試SpringMVC項(xiàng)目時(shí),有沒有遇到過(guò)明明控制器返回了"success"卻死活找不到對(duì)應(yīng)JSP文件的情況?別急!這很可能是因?yàn)橐晥D解析器配置出了問(wèn)題,今天我們就來(lái)好好聊聊怎么用SpringMVC配置JSP視圖解析器這件事,需要的朋友可以參考下
    2025-05-05
  • SpringBoot整合Canal與RabbitMQ監(jiān)聽數(shù)據(jù)變更記錄

    SpringBoot整合Canal與RabbitMQ監(jiān)聽數(shù)據(jù)變更記錄

    這篇文章主要介紹了SpringBoot整合Canal與RabbitMQ監(jiān)聽數(shù)據(jù)變更記錄,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • SpringBoot利用@Validated注解優(yōu)雅實(shí)現(xiàn)參數(shù)校驗(yàn)

    SpringBoot利用@Validated注解優(yōu)雅實(shí)現(xiàn)參數(shù)校驗(yàn)

    在開發(fā) Web 應(yīng)用時(shí),用戶輸入的合法性校驗(yàn)是保障系統(tǒng)穩(wěn)定性的基礎(chǔ),?Spring Boot 的 @Validated 注解 提供了一種更優(yōu)雅的解決方案,下面就跟隨小編一起學(xué)習(xí)一下吧
    2025-04-04
  • java?Comparable和Comparator的區(qū)別及作用面試精講

    java?Comparable和Comparator的區(qū)別及作用面試精講

    這篇文章主要為大家介紹了java?Comparable和Comparator的區(qū)別及作用面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • 帶你了解如何使用Spring基于ProxyFactoryBean創(chuàng)建AOP代理

    帶你了解如何使用Spring基于ProxyFactoryBean創(chuàng)建AOP代理

    這篇文章主要介紹了Spring基于ProxyFactoryBean創(chuàng)建AOP代理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-08-08
  • 淺談spring security入門

    淺談spring security入門

    這篇文章主要介紹了淺談spring security入門,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Springboot配置Swagger2登錄密碼的實(shí)現(xiàn)

    Springboot配置Swagger2登錄密碼的實(shí)現(xiàn)

    本文主要介紹了Springboot配置Swagger2登錄密碼的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建類操作示例

    Java實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建類操作示例

    這篇文章主要介紹了Java實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建類操作,結(jié)合完整示例形式分析了Java動(dòng)態(tài)創(chuàng)建類的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下
    2020-02-02

最新評(píng)論

梅河口市| 吉木萨尔县| 化隆| 嘉黎县| 叶城县| 九寨沟县| 柳河县| 乌鲁木齐县| 凤山县| 汤阴县| 瓮安县| 志丹县| 石柱| 翁源县| 渝中区| 九龙县| 思南县| 长丰县| 深州市| 阿拉善盟| 大同县| 谢通门县| 绥阳县| 辛集市| 衡南县| 瓮安县| 淮安市| 凤山县| 子洲县| 抚宁县| 呈贡县| 马龙县| 百色市| 从江县| 图木舒克市| 祁东县| 成武县| 日土县| 双峰县| 甘德县| 神木县|