前言
??最近收到客服反應(yīng)军援,系統(tǒng)的省市區(qū)數(shù)據(jù)好像不準(zhǔn),并且缺了一些地區(qū)称勋。經(jīng)過詢問同事得知胸哥,數(shù)據(jù)庫內(nèi)的數(shù)據(jù)是從老項(xiàng)目拷貝過來的,有些年頭了赡鲜。難怪會(huì)缺一些數(shù)據(jù)空厌。正好最近在對(duì)接網(wǎng)商銀行,發(fā)現(xiàn)網(wǎng)商提供了省市區(qū)的數(shù)據(jù)的接口银酬。這就很舒服了哇嘲更,抄起鍵盤就是干,很快的就把同步程序?qū)懞昧恕?/p>
??然后在同步的過程中揩瞪,發(fā)現(xiàn)網(wǎng)商提供的數(shù)據(jù)和數(shù)據(jù)庫有些對(duì)不上赋朦。于是默默的打開淘寶
和京東
添加收貨地址,看看到底是誰錯(cuò)了李破。對(duì)比到后面發(fā)現(xiàn)都有些差異宠哄。這就很蛋疼了∴凸ィ看來這個(gè)時(shí)候誰都不能相信了毛嫉,只能信國家了。于是我打開了中華人民共和國民政部網(wǎng)站來比對(duì)異常的數(shù)據(jù)妇菱。
??對(duì)比的過程中狱庇,石錘網(wǎng)商數(shù)據(jù)不準(zhǔn)。值得的是表揚(yáng)淘寶
和京東
已經(jīng)同步了最新的數(shù)據(jù)了恶耽。但是呢,我并沒有找到它們的數(shù)據(jù)接口颜启。為了修正系統(tǒng)的數(shù)據(jù)偷俭,只能自己爬取了。
鎖定爬取目標(biāo)
爬取地址如下:
https://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html
??爬取原理很簡(jiǎn)單缰盏,就是解析HTML元素涌萤,然后獲取到相應(yīng)的屬性值保存下來就好了淹遵。由于使用Java進(jìn)行開發(fā),所以選用Jsoup來完成這個(gè)工作负溪。
<!-- HTML解析器 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
網(wǎng)頁數(shù)據(jù)分析
??由于需要解析HTML才能取到數(shù)據(jù)透揣,所以需要知道數(shù)據(jù)存儲(chǔ)在什么元素上。我們可以打開chrom的控制臺(tái)川抡,然后選中對(duì)應(yīng)的數(shù)據(jù)辐真,即可查看存儲(chǔ)數(shù)據(jù)的元素。
??通過分析崖堤,發(fā)現(xiàn)每一行數(shù)據(jù)都是存儲(chǔ)在一個(gè)<tr>
標(biāo)簽下侍咱。我們需要的 區(qū)域碼
和區(qū)域名稱
存儲(chǔ)在第一和第二個(gè)<td>
內(nèi) 。與此同時(shí)還要很多空白<td>
標(biāo)簽密幔,在編寫代碼是需要將其過濾掉楔脯。
定義基礎(chǔ)代碼
??先定義好我們的爬取目標(biāo),以及Area
實(shí)體類胯甩。
public class AreaSpider{
// 爬取目標(biāo)
private static final String TARGET = "http://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html";
@Data
@AllArgsConstructor
private static class Area {
// 區(qū)域碼
private String code;
// 區(qū)域名稱
private String name;
// 父級(jí)
private String parent;
}
}
爬蟲代碼編寫
public static void main(String[] args) throws IOException{
// 請(qǐng)求網(wǎng)頁
Jsoup.connect(TARGET).timeout(10000).get()
// 篩選出 tr 標(biāo)簽
.select("tr")
// 篩選出 tr 下的 td 標(biāo)簽
.forEach(tr -> tr.select("td")
// 過濾 值為空的 td 標(biāo)簽
.stream().filter(td -> StringUtils.isNotBlank(td.text()))
// 輸出結(jié)果
.forEach(td -> System.out.println(td.text())));
}
解析結(jié)果
代碼優(yōu)化
??通過上面的代碼昧廷,我們已經(jīng)爬取到了頁面上的數(shù)據(jù)。但是并沒有達(dá)到我們的預(yù)期偎箫,所以進(jìn)一步處理將其轉(zhuǎn)換為Area
實(shí)體木柬。
public static void main(String[] args) throws IOException{
// 請(qǐng)求網(wǎng)頁
List<Area> areaList = Jsoup.connect(TARGET).timeout(10000).get()
// 篩選出 tr 標(biāo)簽
.select("tr")
// 篩選出 tr 下的 td 標(biāo)簽
.stream().map(tr -> tr.select("td")
// 過濾 值為空的 td 標(biāo)簽,并轉(zhuǎn)換為 td 列表
.stream().filter(td -> StringUtils.isNotBlank(td.text())).collect(Collectors.toList()))
// 前面提到镜廉,區(qū)域碼和區(qū)域名稱分別存儲(chǔ)在 第一和第二個(gè)td弄诲,所以過濾掉不符合規(guī)范的數(shù)據(jù)行。
.filter(e -> e.size() == 2)
// 轉(zhuǎn)換為 area 對(duì)象
.map(e -> new Area(e.get(0).text(), e.get(1).text(), "0")).collect(Collectors.toList());
// 遍歷數(shù)據(jù)
areaList.forEach(area -> System.out.println(JSONUtil.toJsonStr(area)));
}
解析結(jié)果
??至此娇唯,離我們想要的數(shù)據(jù)還差了父級(jí)區(qū)域碼 齐遵,我們可以通過區(qū)域碼計(jì)算出來。以河北省為例:河北省:130000
塔插、石家莊市:130100
梗摇、長(zhǎng)安區(qū):130102
可以發(fā)現(xiàn)規(guī)律:0000 結(jié)尾是省份,00是市想许。所以就有了如下代碼:
private static String calcParent(String areaCode){
// 省 - 針對(duì)第一行特殊處理
if(areaCode.contains("0000") || areaCode.equals("行政區(qū)劃代碼")){
return "0";
// 市
}else if (areaCode.contains("00")) {
return String.valueOf(Integer.parseInt(areaCode) / 10000 * 10000);
// 區(qū)
}else {
return String.valueOf(Integer.parseInt(areaCode) / 100 * 100);
}
}
最終代碼
public class AreaSpider{
// 爬取目標(biāo)
private static final String TARGET = "http://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html";
@Data
@AllArgsConstructor
private static class Area{
// 區(qū)域碼
private String code;
// 區(qū)域名稱
private String name;
// 父級(jí)
private String parent;
}
public static void main(String[] args) throws IOException{
// 請(qǐng)求網(wǎng)頁
List<Area> areaList = Jsoup.connect(TARGET).timeout(10000).get()
// 篩選出 tr 標(biāo)簽
.select("tr")
// 篩選出 tr 下的 td 標(biāo)簽
.stream().map(tr -> tr.select("td")
// 過濾 值為空的 td 標(biāo)簽伶授,并轉(zhuǎn)換為 td 列表
.stream().filter(td -> StringUtils.isNotBlank(td.text())).collect(Collectors.toList()))
// 前面提到,區(qū)域碼和區(qū)域名稱分別存儲(chǔ)在 第一和第二個(gè)td流纹,所以過濾掉不符合規(guī)范的數(shù)據(jù)行糜烹。
.filter(e -> e.size() == 2)
// 轉(zhuǎn)換為 area 對(duì)象
.map(e -> new Area(e.get(0).text(), e.get(1).text(), calcParent(e.get(0).text()))).collect(Collectors.toList());
// 去除 第一行 "行政區(qū)劃代碼|單位名稱"
areaList.remove(0);
areaList.forEach(area -> System.out.println(JSONUtil.toJsonStr(area)));
}
private static String calcParent(String areaCode){
// 省 - 針對(duì)第一行特殊處理
if(areaCode.contains("0000") || areaCode.equals("行政區(qū)劃代碼")){
return "0";
// 市
}else if (areaCode.contains("00")) {
return String.valueOf(Integer.parseInt(areaCode) / 10000 * 10000);
// 區(qū)
}else {
return String.valueOf(Integer.parseInt(areaCode) / 100 * 100);
}
}
}
數(shù)據(jù)修正
??由于我們需要的是省市區(qū)三級(jí)數(shù)據(jù)聯(lián)動(dòng),但是了直轄市只有兩級(jí)漱凝,所以我們?nèi)斯さ慕o它加上一級(jí)疮蹦。以北京市為例:變成了 北京 -> 北京市- >東城區(qū),對(duì)于其他的直轄市也是同樣的處理邏輯茸炒。
??修正好的數(shù)據(jù)奉上愕乎,有需要的小伙伴可以自取哦阵苇。
對(duì)于直轄市也可以做兩級(jí)的,這個(gè)主要看產(chǎn)品的需求吧
總結(jié)
??總體來講感论,這個(gè)爬蟲比較簡(jiǎn)單绅项,只有簡(jiǎn)單的幾行代碼。畢竟網(wǎng)站也沒啥反扒的機(jī)制比肄,所以很輕松的就拿到了數(shù)據(jù)快耿。
結(jié)尾
??嘿嘿話說,你都爬過哪些網(wǎng)站呢薪前?
??如果覺得對(duì)你有幫助润努,可以多多評(píng)論,多多點(diǎn)贊哦示括,也可以到我的主頁看看铺浇,說不定有你喜歡的文章,也可以隨手點(diǎn)個(gè)關(guān)注哦垛膝,謝謝鳍侣。
??我是不一樣的科技宅,每天進(jìn)步一點(diǎn)點(diǎn)吼拥,體驗(yàn)不一樣的生活倚聚。我們下期見!