前言
??最近收到客服反應,系統的省市區數據好像不準,并且缺了一些地區。經過詢問同事得知,數據庫內的數據是從老項目拷貝過來的,有些年頭了。難怪會缺一些數據。正好最近在對接網商銀行,發現網商提供了省市區的數據的接口。這就很舒服了哇,抄起鍵盤就是干,很快的就把同步程序寫好了。
??然后在同步的過程中,發現網商提供的數據和數據庫有些對不上。于是默默的打開淘寶
和京東
添加收貨地址,看看到底是誰錯了。對比到后面發現都有些差異。這就很蛋疼了。看來這個時候誰都不能相信了,只能信國家了。于是我打開了中華人民共和國民政部網站來比對異常的數據。
??對比的過程中,石錘網商數據不準。值得的是表揚淘寶
和京東
已經同步了最新的數據了。但是呢,我并沒有找到它們的數據接口。為了修正系統的數據,只能自己爬取了。
鎖定爬取目標
爬取地址如下:
https://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html
??爬取原理很簡單,就是解析HTML元素,然后獲取到相應的屬性值保存下來就好了。由于使用Java進行開發,所以選用Jsoup來完成這個工作。
<!-- HTML解析器 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
網頁數據分析
??由于需要解析HTML才能取到數據,所以需要知道數據存儲在什么元素上。我們可以打開chrom的控制臺,然后選中對應的數據,即可查看存儲數據的元素。
??通過分析,發現每一行數據都是存儲在一個<tr>
標簽下。我們需要的 區域碼
和區域名稱
存儲在第一和第二個<td>
內 。與此同時還要很多空白<td>
標簽,在編寫代碼是需要將其過濾掉。
定義基礎代碼
??先定義好我們的爬取目標,以及Area
實體類。
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);
}
}
}
數據修正
??由于我們需要的是省市區三級數據聯動,但是了直轄市只有兩級,所以我們人工的給它加上一級。以北京市為例:變成了 北京 -> 北京市- >東城區,對于其他的直轄市也是同樣的處理邏輯。
??修正好的數據奉上,有需要的小伙伴可以自取哦。
對于直轄市也可以做兩級的,這個主要看產品的需求吧
總結
??總體來講,這個爬蟲比較簡單,只有簡單的幾行代碼。畢竟網站也沒啥反扒的機制,所以很輕松的就拿到了數據。
結尾
??嘿嘿話說,你都爬過哪些網站呢?
??如果覺得對你有幫助,可以多多評論,多多點贊哦,也可以到我的主頁看看,說不定有你喜歡的文章,也可以隨手點個關注哦,謝謝。
??我是不一樣的科技宅,每天進步一點點,體驗不一樣的生活。我們下期見!