最近收到客服反應,系統的省市區數據好像不許,而且缺了一些地區。通過詢問同事得知,數據庫內的數據是從老項目拷貝過來的,有些年頭了。難怪會缺一些數據。正好最近在對接網商銀行,發現網商提供了省市區的數據的接口。這就很舒服了哇,抄起鍵盤就是幹,很快的就把同步程序寫好了。html
而後在同步的過程當中,發現網商提供的數據和數據庫有些對不上。因而默默的打開淘寶
和京東
添加收貨地址,看看究竟是誰錯了。對比到後面發現都有些差別。這就很蛋疼了。看來這個時候誰都不能相信了,只能信國家了。因而我打開了中華人民共和國民政部網站來比對異常的數據。java
對比的過程當中,石錘網商數據不許。值得的是表揚淘寶
和京東
已經同步了最新的數據了。可是呢,我並無找到它們的數據接口。爲了修正系統的數據,只能本身爬取了。git
爬取地址以下:github
https://preview.www.mca.gov.c...
爬取原理很簡單,就是解析HTML元素,而後獲取到相應的屬性值保存下來就行了。因爲使用Java進行開發,因此選用Jsoup來完成這個工做。sql
<!-- HTML解析器 --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.13.1</version> </dependency>
因爲須要解析HTML才能取到數據,因此須要知道數據存儲在什麼元素上。咱們能夠打開chrom的控制檯,而後選中對應的數據,便可查看存儲數據的元素。數據庫
經過分析,發現每一行數據都是存儲在一個<tr>
標籤下。咱們須要的 區域碼
和區域名稱
存儲在第一和第二個<td>
內 。與此同時還要不少空白<td>
標籤,在編寫代碼是須要將其過濾掉。json
先定義好咱們的爬取目標,以及Area
實體類。ide
public class AreaSpider{ // 爬取目標 private static final String TARGET = "http://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html"; @Data @AllArgsConstructor private static class Area { // 區域碼 private String code; // 區域名稱 private String name; // 父級 private String parent; } }
public static void main(String[] args) throws IOException{ // 請求網頁 Jsoup.connect(TARGET).timeout(10000).get() // 篩選出 tr 標籤 .select("tr") // 篩選出 tr 下的 td 標籤 .forEach(tr -> tr.select("td") // 過濾 值爲空的 td 標籤 .stream().filter(td -> StringUtils.isNotBlank(td.text())) // 輸出結果 .forEach(td -> System.out.println(td.text()))); }
解析結果優化
經過上面的代碼,咱們已經爬取到了頁面上的數據。可是並無達到咱們的預期,因此進一步處理將其轉換爲Area
實體。網站
public static void main(String[] args) throws IOException{ // 請求網頁 List<Area> areaList = Jsoup.connect(TARGET).timeout(10000).get() // 篩選出 tr 標籤 .select("tr") // 篩選出 tr 下的 td 標籤 .stream().map(tr -> tr.select("td") // 過濾 值爲空的 td 標籤,並轉換爲 td 列表 .stream().filter(td -> StringUtils.isNotBlank(td.text())).collect(Collectors.toList())) // 前面提到,區域碼和區域名稱分別存儲在 第一和第二個td,因此過濾掉不符合規範的數據行。 .filter(e -> e.size() == 2) // 轉換爲 area 對象 .map(e -> new Area(e.get(0).text(), e.get(1).text(), "0")).collect(Collectors.toList()); // 遍歷數據 areaList.forEach(area -> System.out.println(JSONUtil.toJsonStr(area))); }
解析結果
至此,離咱們想要的數據還差了父級區域碼 ,咱們能夠經過區域碼計算出來。以河北省爲例:河北省:130000
、石家莊市:130100
、長安區:130102
能夠發現規律:0000 結尾是省份,00是市。因此就有了以下代碼:
private static String calcParent(String areaCode){ // 省 - 針對第一行特殊處理 if(areaCode.contains("0000") || areaCode.equals("行政區劃代碼")){ return "0"; // 市 }else if (areaCode.contains("00")) { return String.valueOf(Integer.parseInt(areaCode) / 10000 * 10000); // 區 }else { return String.valueOf(Integer.parseInt(areaCode) / 100 * 100); } }
public class AreaSpider{ // 爬取目標 private static final String TARGET = "http://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html"; @Data @AllArgsConstructor private static class Area{ // 區域碼 private String code; // 區域名稱 private String name; // 父級 private String parent; } public static void main(String[] args) throws IOException{ // 請求網頁 List<Area> areaList = Jsoup.connect(TARGET).timeout(10000).get() // 篩選出 tr 標籤 .select("tr") // 篩選出 tr 下的 td 標籤 .stream().map(tr -> tr.select("td") // 過濾 值爲空的 td 標籤,並轉換爲 td 列表 .stream().filter(td -> StringUtils.isNotBlank(td.text())).collect(Collectors.toList())) // 前面提到,區域碼和區域名稱分別存儲在 第一和第二個td,因此過濾掉不符合規範的數據行。 .filter(e -> e.size() == 2) // 轉換爲 area 對象 .map(e -> new Area(e.get(0).text(), e.get(1).text(), calcParent(e.get(0).text()))).collect(Collectors.toList()); // 去除 第一行 "行政區劃代碼|單位名稱" areaList.remove(0); areaList.forEach(area -> System.out.println(JSONUtil.toJsonStr(area))); } private static String calcParent(String areaCode){ // 省 - 針對第一行特殊處理 if(areaCode.contains("0000") || areaCode.equals("行政區劃代碼")){ return "0"; // 市 }else if (areaCode.contains("00")) { return String.valueOf(Integer.parseInt(areaCode) / 10000 * 10000); // 區 }else { return String.valueOf(Integer.parseInt(areaCode) / 100 * 100); } } }
因爲咱們須要的是省市區三級數據聯動,可是了直轄市只有兩級,因此咱們人工的給它加上一級。以北京市爲例:變成了 北京 -> 北京市- >東城區,對於其餘的直轄市也是一樣的處理邏輯。
修正好的數據奉上,有須要的小夥伴能夠自取哦。
對於直轄市也能夠作兩級的,這個主要看產品的需求吧
整體來說,這個爬蟲比較簡單,只有簡單的幾行代碼。畢竟網站也沒啥反扒的機制,因此很輕鬆的就拿到了數據。
嘿嘿話說,你都爬過哪些網站呢?
若是以爲對你有幫助,能夠多多評論,多多點贊哦,也能夠到個人主頁看看,說不定有你喜歡的文章,也能夠隨手點個關注哦,謝謝。
我是不同的科技宅,天天進步一點點,體驗不同的生活。咱們下期見!