首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 精準提取網頁發布時間:多模式正則匹配與格式標準化

     編輯:Java 相關知識

網頁發布時間格式極其多樣(URL 中嵌入、正文顯示、中英文混排、帶/不帶時間),單一正則無法覆蓋。本文提供一個生產級提取器,結合URL 模式優先、正文多模式兜底、日曆合法性校驗、統一輸出 yyyy-MM-dd HH:mm:ss,最大程度提高提取準確率。

一、提取策略:分層匹配

  1. URL 結構化時間:優先從 URL 路徑提取(如 /2023/05/15/、/20230515/),準確率最高
  2. 正文顯式時間:多套正則匹配正文可見時間(2023-05-15 14:30:00、2023年5月15日 等)
  3. 合法性過濾:過濾未來時間、邏輯無效日期(如 2 月 30 日)
  4. 格式標準化:統一輸出 yyyy-MM-dd HH:mm:ss

二、核心代碼實現

public class PublishTimeExtractor {
    private static final Calendar NOW = Calendar.getInstance();
    
    // URL 模式:連續 8 位數字
    private static final Pattern URL_YYYYMMDD = Pattern.compile("[-/_]20\\d{6}");
    // URL 模式:分隔符分隔 年-月-日
    private static final Pattern URL_Y_M_D = Pattern.compile("[-/_]20\\d{2}[-/_]\\d{1,2}[-/_]\\d{1,2}");
    // URL 模式:年-月
    private static final Pattern URL_Y_M = Pattern.compile("[-/_]20\\d{2}[-/_]\\d{1,2}");
    
    // 正文精確時間(含時分秒)
    private static final Pattern DETAIL_TIME = Pattern.compile(
        "(20\\d{2}[-/]\\d{1,2}[-/]\\d{1,2}\\s\\d{1,2}:\\d{1,2}:\\d{1,2})" +
        "|(20\\d{2}年\\d{1,2}月\\d{1,2}日\\s\\d{1,2}:\\d{1,2}:\\d{1,2})",
        Pattern.CASE_INSENSITIVE);
    // 正文日期(不含時間)
    private static final Pattern DATE_ONLY = Pattern.compile(
        "(20\\d{2}[-/]\\d{1,2}[-/]\\d{1,2})" +
        "|(20\\d{2}年\\d{1,2}月\\d{1,2}日)",
        Pattern.CASE_INSENSITIVE);
    
    // 完整時間格式驗證正則(含閏年邏輯)
    private static final String VALID_DATETIME_REGEX = "^((\\d{2}(([02468][048])|([13579][26]))[-\\s]?..."; // 省略完整表達式
    
    public static String extract(String url, String htmlContent) {
        String time = extractFromUrl(url);
        if (time == null && htmlContent != null) {
            time = extractFromContent(htmlContent);
        }
        return time;
    }
    
    private static String extractFromUrl(String url) {
        // 1. 連續 8 位
        Matcher m = URL_YYYYMMDD.matcher(url);
        if (m.find()) {
            String s = m.group().substring(1); // 去掉分隔符
            if (isValidDate(s, "yyyyMMdd")) 
                return format(s.substring(0,4), s.substring(4,6), s.substring(6,8), "00:00:00");
        }
        // 2. 分隔符 年-月-日
        m = URL_Y_M_D.matcher(url);
        if (m.find()) {
            String s = m.group().substring(1);
            String[] p = s.split("[-/_]");
            if (isValidDate(p[0], p[1], p[2])) 
                return format(p[0], p[1], p[2], "00:00:00");
        }
        // 3. 年-月
        m = URL_Y_M.matcher(url);
        if (m.find()) {
            String s = m.group().substring(1);
            String[] p = s.split("[-/_]");
            if (isValidYearMonth(p[0], p[1])) 
                return format(p[0], p[1], "01", "00:00:00");
        }
        return null;
    }
    
    private static String extractFromContent(String html) {
        String text = html.replaceAll("\\r?\\n", " ");
        boolean hasTime = false;
        
        Matcher m = DETAIL_TIME.matcher(text);
        if (!m.find()) {
            m = DATE_ONLY.matcher(text);
        } else {
            hasTime = true;
        }
        
        List candidates = new ArrayList<>();
        for (int i = 1; i <= m.groupCount(); i++) {
            String g = m.group(i);
            if (g != null) candidates.add(g.trim());
        }
        
        for (String cand : candidates) {
            String norm = cand.replace("/", "-").replace("年", "-").replace("月", "-").replace("日", "-");
            if (!hasTime) norm += " 00:00:00";
            if (isValidDateTime(norm) && norm.matches(VALID_DATETIME_REGEX)) 
                return norm;
        }
        return null;
    }
    
    private static boolean isValidDate(String y, String m, String d) {
        try {
            Calendar c = Calendar.getInstance();
            c.set(Integer.parseInt(y), Integer.parseInt(m)-1, Integer.parseInt(d));
            return !c.after(NOW) && c.get(Calendar.MONTH) == Integer.parseInt(m)-1;
        } catch (Exception e) { return false; }
    }
    // 其他輔助方法省略...
}

三、關鍵設計亮點

  1. 分層優先級:URL 時間不可篡改、結構固定,優先級最高
  2. 多模式兼容:覆蓋連續數字、分隔符、中文年月日、帶/不帶時間
  3. 日曆合法性校驗:Calendar.set() 自動檢查月份天數、閏年,過濾 2023-02-30 等非法日期
  4. 當前時間上界:過濾未來發布時間(爬蟲常見反作弊)
  5. 統一標準化:輸出固定格式,便於下游存儲與比較

四、擴展建議

  • 引入 DateTimeFormatter (Java 8+) 替代 Calendar
  • 增加 meta 標籤提取(property="article:published_time")
  • 結合 HTML 結構定位(如 .publish-time、#pub-date)提高定向準確率
  • 對於相對時間(3小時前、昨天)需結合抓取時間換算

五、總結

網頁時間提取沒有銀彈,但分層策略 + 多模式正則 + 日曆校驗 + 格式標準化能解決 90%+ 場景。將提取邏輯封裝為可複用組件,配合單元測試覆蓋主流網站樣本,是構建穩健爬蟲系統的關鍵一環。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved