實戰|省市區三級聯動數據爬取

前言

  最近收到客服反應,系統的省市區數據好像不許,而且缺了一些地區。通過詢問同事得知,數據庫內的數據是從老項目拷貝過來的,有些年頭了。難怪會缺一些數據。正好最近在對接網商銀行,發現網商提供了省市區的數據的接口。這就很舒服了哇,抄起鍵盤就是幹,很快的就把同步程序寫好了。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);
        }
    }
}

數據修正

  因爲咱們須要的是省市區三級數據聯動,可是了直轄市只有兩級,因此咱們人工的給它加上一級。以北京市爲例:變成了 北京 -> 北京市- >東城區,對於其餘的直轄市也是一樣的處理邏輯。

  修正好的數據奉上,有須要的小夥伴能夠自取哦。

對於直轄市也能夠作兩級的,這個主要看產品的需求吧

總結

  整體來說,這個爬蟲比較簡單,只有簡單的幾行代碼。畢竟網站也沒啥反扒的機制,因此很輕鬆的就拿到了數據。

結尾

  嘿嘿話說,你都爬過哪些網站呢?

  若是以爲對你有幫助,能夠多多評論,多多點贊哦,也能夠到個人主頁看看,說不定有你喜歡的文章,也能夠隨手點個關注哦,謝謝。

  我是不同的科技宅,天天進步一點點,體驗不同的生活。咱們下期見!

相關文章
相關標籤/搜索