網頁發布時間格式極其多樣(URL 中嵌入、正文顯示、中英文混排、帶/不帶時間),單一正則無法覆蓋。本文提供一個生產級提取器,結合URL 模式優先、正文多模式兜底、日曆合法性校驗、統一輸出 yyyy-MM-dd HH:mm:ss,最大程度提高提取準確率。
/2023/05/15/、/20230515/),準確率最高2023-05-15 14:30:00、2023年5月15日 等)yyyy-MM-dd HH:mm:sspublic 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; }
}
// 其他輔助方法省略...
}
Calendar.set() 自動檢查月份天數、閏年,過濾 2023-02-30 等非法日期DateTimeFormatter (Java 8+) 替代 Calendarmeta 標籤提取(property="article:published_time").publish-time、#pub-date)提高定向準確率3小時前、昨天)需結合抓取時間換算網頁時間提取沒有銀彈,但分層策略 + 多模式正則 + 日曆校驗 + 格式標準化能解決 90%+ 場景。將提取邏輯封裝為可複用組件,配合單元測試覆蓋主流網站樣本,是構建穩健爬蟲系統的關鍵一環。