抓取網頁關鍵信息,填充word模板生成目標文檔

語言: CN / TW / HK

有個政府部門工作的朋友,每天有個令人心煩的工作就是,根據一個固定格式的html網頁和一個txt,提取出其中的關鍵字段信息,填充進一個固定格式的word文檔,需要填充的字段比較多,目測三四十個吧,朋友之前一直都是先從html文件和txt中人肉扒拉出需要的內容,然後逐個複製粘貼進模板word文檔,費力又容易出錯,每天重複這一通操作,想想都痛苦,於是他找到了我。

大概思考了下,解析html可以用Jsoup,一款輕量級的網頁解析器,幾年前剛入門java時研究過這個玩意開發了一個入門級的網頁爬蟲,沒想到現在又用上了,txt這個就更簡單了,直接用文件流按行讀取內容,解析相應目標值就OK了,現在字段值有了,剩下的就是製作一個模板word,然後填充屬性值就完事了,這裏可以用freemaker和docx,思路確認,開搞。

 

一、首先創建一個springboot工程(略:作為專業的java工作者,應該沒有人不會吧)

二、引入Jsoup,freemaker,docx的依賴

<!-- 網頁解析器 -->
<dependency>
   <groupId>org.jsoup</groupId>
   <artifactId>jsoup</artifactId>
   <version>1.13.1</version>
</dependency>
<!-- 文檔模板操作依賴 -->
<dependency>
   <groupId>fr.opensagres.xdocreport</groupId>
   <artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
   <version>2.0.1</version>
</dependency>
<dependency>
   <groupId>fr.opensagres.xdocreport</groupId>
   <artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
   <version>2.0.1</version>
</dependency>

三、編寫模板word

實際用到的模板中含有敏感信息,就不貼出來了,給個簡單例子,看到的小哥應該能懂

1)新建一個文檔,裏面添加一些模板字段

2)分別給這些模板字段設置佔位符標記

光標停留在相應字段那裏,Ctrl + F9,創建出一個對應的域

3)選中域,右鍵->編輯域->郵件合併->輸入${XXX}

4)每個字段都做如上操作

四、直接貼代碼

package com.ball.wayne;

import org.jsoup.Jsoup;
import org.jsoup.nodes.*;
import org.jsoup.select.Elements;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.io.*;
import java.util.*;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(DemoApplication.class, args);
        deal();
    }

    public static void deal() throws Exception {
        String sourceUrl = "C:\\work";
        String targetUrl = "C:\\work";

        File sourceFloder = new File(sourceUrl);
        File[] sourceFiles = sourceFloder.listFiles();


        Map<String, String> resultMap = new HashMap<>();

        for (File file : sourceFiles) {
            String fullName = file.getName();
            //解析html文件獲取相應變量值
            if (fullName.endsWith(".html")) {
                //獲取文件名,用於生成目標文件
                Map<String, String> htmlMap = jsoupParse(file);
                resultMap.putAll(htmlMap);
                //解析txt文件獲取相應變量值
            } else if (fullName.endsWith(".txt")) {
                Map<String, String> txtMap = parseTxt(file);
                resultMap.putAll(txtMap);
            }
        }

        resultMap.keySet().forEach(e -> {
            if (resultMap.get(e) == null || resultMap.get(e) == "") {
                resultMap.put(e, ""+ e +"");
            }
            if (e.equals("設備型號")) {
                String value = resultMap.get("設備型號").replace(" ","")
                        .replace("iPhoneiPhone","iPhone")
                        .replace("iphoneiphone","iphone");
                resultMap.put("設備型號",value);
            }
        });

        //根據光盤編號拼接最終生成的文件名  例:光盤編號:[2021]5059  ——> [2021]-5059電子數據檢查筆錄.docx
        String diskNO = resultMap.get("光盤編號");
        String fileName = targetUrl + "\\" + diskNO.substring(0,diskNO.indexOf("]")+1) + "-" + diskNO.substring(diskNO.indexOf("]")+1) + "電子數據檢查筆錄.docx";

        //替換變量
        putVal(resultMap, fileName);

    }


    public static Map<String, String> jsoupParse(File file) throws IOException {
        String[] fields = new String[]{"送檢人", "案件名稱", "檢材持有人", "手機品牌", "設備型號", "IMEI", "IMEI2", "操作系統", "系統版本"};

        Map<String, String> result = new HashMap<>();
        Document document = Jsoup.parse(file, "UTF-8");
        Element body = document.getElementsByTag("body").get(0);
        List<TextNode> nodes = body.textNodes();
        //解析產品版本
        for (TextNode node : nodes) {
            String productVersion;
            if (node.text().indexOf("產品版本") >= 0) {
                String productVersionTemp = node.text().trim();
                String[] arr = productVersionTemp.split("");
                if (arr != null && arr.length == 2) {
                    productVersion = arr[1].trim();
                } else {
                    productVersion = "";
                }
                result.put("產品版本", productVersion);
            }
        }

        //解析table中元素
        Elements tables = body.getElementsByTag("table");
        for (Element table : tables) {
            if (!table.hasClass("OuterTableList")) {
                continue;
            }
            //遍歷field 分別拿到相應值
            for (String field : fields) {
                if (result.get(field) != null && !"".equals(result.get(field))) {
                    continue;
                }
                String val = parseValue(table, field);
                result.put(field, val);
            }
        }
        return result;
    }

    private static String parseValue(Element table, String field) {
        String result = "";
        if (table.text().indexOf(field) >= 0) {
            Element tbody = table.getElementsByTag("tbody").get(0);
            Elements trs = tbody.getElementsByTag("tr");
            for (Element tr : trs) {
                if (tr.text().indexOf(field) >= 0) {
                    Elements tds = tr.getElementsByTag("td");
                    if (tds != null && tds.size() >= 3 && field.equals(tds.get(1).text().trim())) {
                        result = tds.get(2).text();
                    }
                }
            }

        }
        return result;
    }

    public static Map<String, String> parseTxt(File file) throws Exception {
        Map<String, String> txtMap = new HashMap<>();
        BufferedReader reader = new BufferedReader(new FileReader(file));
        String line = "";
        String flag = "";
        int count = 0;
        while (count < 15) {
            count++;
            line = reader.readLine();
            if (line == null || line.length() <= 0) {
                continue;
            }
            //手機照片文件夾
            if (line.indexOf("文件") >= 0 && line.indexOf("照片") >= 0) {
                flag = "picture";
                String[] arr = line.split("\\\\");
                String fileName = arr[arr.length - 1];
                String val = fileName.substring(0, fileName.indexOf(".rar"));
                txtMap.put("手機照片文件夾", val);
            }

            //手機照片文件MD5
            if (line.indexOf("MD5") >= 0 && flag.equals("picture")) {
                String[] arr = line.split("|:");
                if (arr != null && arr.length == 2) {
                    String md5 = arr[1].trim();
                    txtMap.put("手機照片文件MD5", md5);
                }
            }

            //手機報告文件夾
            if (line.indexOf("文件") >= 0 && line.indexOf("報告") >= 0) {
                flag = "report";
                String[] arr = line.split("\\\\");
                String fileName = arr[arr.length - 1];
                String val = fileName.substring(0, fileName.indexOf(".rar"));
                txtMap.put("手機報告文件夾", val);
            }

            //手機報告文件MD5
            if (line.indexOf("MD5") >= 0 && flag.equals("report")) {
                String[] arr = line.split("|:");
                if (arr != null && arr.length == 2) {
                    String md5 = arr[1].trim();
                    txtMap.put("手機報告文件MD5", md5);
                }
            }

            //光盤編號
            if (line.indexOf("光盤編號") >= 0) {
                String[] arr = line.split("|:");
                if (arr != null && arr.length == 2) {
                    String md5 = arr[1].trim();
                    txtMap.put("光盤編號", md5);
                }
            }
        }
        return txtMap;
    }


    /**
     * 給模板文檔替換value
     * @param map
     * @param fileName
     */
    public static void putVal(Map<String, String> map, String fileName) {
        System.out.println("============================");
        for (Map.Entry<String,String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

        ExportData evaluation = WordUtil.createExportData("/template.docx");
        evaluation.setData("光盤編號", map.get("光盤編號"));
        evaluation.setData("送檢人", map.get("送檢人"));
        evaluation.setData("案件名稱", map.get("案件名稱"));
        evaluation.setData("檢材持有人", map.get("檢材持有人"));
        evaluation.setData("手機品牌", map.get("手機品牌"));
        evaluation.setData("設備型號", map.get("設備型號"));
        evaluation.setData("IMEI", map.get("IMEI"));
        evaluation.setData("IMEI2", map.get("IMEI2"));
        evaluation.setData("產品版本", map.get("產品版本"));
        evaluation.setData("操作系統", map.get("操作系統"));
        evaluation.setData("系統版本", map.get("系統版本"));
        evaluation.setData("手機報告文件夾", map.get("手機報告文件夾"));
        evaluation.setData("手機報告文件MD5", map.get("手機報告文件MD5"));
        evaluation.setData("手機照片文件夾", map.get("手機照片文件夾"));
        evaluation.setData("手機照片文件MD5", map.get("手機照片文件MD5"));

        byte[] data = evaluation.getByteArr();

        // 可以直接寫入本地的文件
        try (
            FileOutputStream fos = new FileOutputStream(fileName);
        ) {
            fos.write(data, 0, data.length);
        } catch (IOException ex) {
            throw new RuntimeException(ex.getMessage());
        }

    }

}

 

package com.ball.wayne;

import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import org.springframework.core.io.ClassPathResource;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
 * @author wayne
 * @intro
 */
public class WordUtil {

    /**
     * 獲取 Word 模板的兩個操作對象 IXDocReport IContext
     * @param url 模板相對於類路徑的地址
     * @return 模板數據對象
     */
    public static ExportData createExportData(String url) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            IXDocReport report = createReport(url);
            IContext context = report.createContext();
            return new ExportData(report, context);
        } catch (XDocReportException ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }

    /**
     * 加載模板的方法,主要是指定模板的路徑和選擇渲染數據的模板
     * @param url 模板相對於類路徑的地址
     * @return word 文檔操作類
     */
    private static IXDocReport createReport(String url) {
        try (
                InputStream in = new ClassPathResource(url).getInputStream();
        ) {
            IXDocReport ix = XDocReportRegistry.getRegistry().loadReport(in, TemplateEngineKind.Freemarker);
            return ix;
        } catch (Exception ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }

}

 

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * @author wayne
 * @intro
 */
public class ExportData {

    private IXDocReport report;
    private IContext context;

    /**
     * 構造方法
     * @param report
     * @param context
     */
    public ExportData(IXDocReport report, IContext context) {
        this.report = report;
        this.context = context;
    }

    /**
     * 設置普通數據,包括基礎數據類型,數組,試題對象
     * 使用時,直接 ${key.k} 或者 [#list d as key]
     * @param key        * @param value      */
    public void setData(String key, Object value) {
        context.put(key, value);
    }
    /**
     * 獲取文件流數據
     * @return 文件流數組
     */
    public byte[] getByteArr() {
        try (
                ByteArrayOutputStream out = new ByteArrayOutputStream();
        ) {
            report.process(context, out);
            return out.toByteArray();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex.getMessage());
        }
    }

}