專案中需要權威的全國行政區劃數據,國家統計局提供的數據更新至 2014 年 10 月 31 日,較為完整。查詢頁面為 https://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2014/index.html。經分析,頁面結構清晰,所有區劃信息集中在一個 table.provincetable 中,適合用 Jsoup 解析。
tr 開始,其餘層級從第 2 個 tr 開始;tr 的 class 不同;td 子元素結構不一致。將全國區劃視為一棵樹:中國為根節點,每個節點包含名稱、代碼、子節點列表及下一級頁面 URL。通過遞歸遍歷:
PageFetcher 根據 URL 抓取頁面;ContentParser 解析頁面生成子節點列表;000000)終止遞歸。public void loadRegionNode(RegionNode regionNode, String superUrl) {
String url = subUrl(superUrl) + regionNode.getUrl();
FetchedPage fetchedPage = pageFetcher.getContentFromUrl(url);
List<RegionNode> list = (regionNode.getChildNode() == null)
? contentParser.parseHTML(fetchedPage)
: regionNode.getChildNode();
if (list != null && !list.isEmpty()) {
regionNode.setChildNode(list);
for (RegionNode node : list) {
if (node.getUrl() == null && !node.getId().substring(6, 12).equals("000000")) {
break; // 葉子節點
}
loadRegionNode(node, url);
}
}
}
根據 tr 子元素數量判斷節點類型:
href、代碼、名稱。public static List<RegionNode> parseHTML(FetchedPage page) {
if (page == null || page.getStatusCode() != 200) return null;
List<RegionNode> list = new ArrayList<>();
Document doc = Jsoup.parse(page.getContent());
Element table = doc.getElementsByTag("TABLE").get(4);
Elements trs = table.getElementsByTag("tr");
for (int i = 1; i < trs.size(); i++) {
Element tr = trs.get(i);
Elements els = tr.getAllElements();
RegionNode node = new RegionNode();
if (els.size() == 3) { // 無下級市轄區
node.setId(els.get(1).text());
node.setRegionName(els.get(2).text());
} else if (els.size() == 4) { // 葉子節點
node.setId(els.get(1).text());
node.setRegionName(els.get(3).text());
} else { // 普通節點
node.setId(els.get(2).text());
node.setRegionName(els.get(4).text());
node.setUrl(els.get(2).attr("href"));
}
list.add(node);
}
return list;
}
約一小時可抓取完整樹形結構,結果可序列化為 JSON/XML 或入庫。完整源碼已上傳 GitHub:regionSpider。