首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 爬蟲實戰:采集京東收貨地址行政區劃資料 (省/市/區/街道四級)

     編輯:Java 程式設計入門

電商系統常需完整的行政區劃資料支援收貨地址選擇。本文示範使用 Java 結合 Jsoup 解析 HTML、**Fastjson** 序列化 JSON、Apache Commons IO/Lang** 輔助工具,從京東收貨地址介面采集省、市、區/縣、街道/鄉鎮四級行政區劃資料,並輸出結構化 JSON 檔案供前端直接使用。

專案依賴

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.16.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.12</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

目標 API 端點分析

京東收貨地址介面採用級聯載入,四個端點:

層級API URL參數
省份https://trade.jd.com/dynamic/consignee/getProvinces.action無
城市https://trade.jd.com/dynamic/consignee/getCitys.actionprovinceId
區/縣https://trade.jd.com/dynamic/consignee/getCountys.actioncityId
街道/鎮https://trade.jd.com/dynamic/consignee/getTowns.actioncountyId

回應格式為 HTML `` 列表。

完整采集程式碼

package com.test.crawler;

import com.alibaba.fastjson.JSON;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class JdAddressCrawler {
    private static final int TIMEOUT_MS = 300_000;  // 5 分鐘超時
    private static final int REQUEST_DELAY_MS = 100; // 請求間隔
    private static int requestCount = 0;

    // API 基礎 URL
    private static final String PROVINCE_URL = "https://trade.jd.com/dynamic/consignee/getProvinces.action";
    private static final String CITY_URL = "https://trade.jd.com/dynamic/consignee/getCitys.action?consigneeParam.provinceId=";
    private static final String COUNTY_URL = "https://trade.jd.com/dynamic/consignee/getCountys.action?consigneeParam.cityId=";
    private static final String TOWN_URL = "https://trade.jd.com/dynamic/consignee/getTowns.action?consigneeParam.countyId=";

    public static void main(String[] args) throws IOException, InterruptedException {
        System.out.println("開始采集京東行政區劃資料...");
        
        // 1. 獲取省份列表
        List
provinces = fetchList(PROVINCE_URL); System.out.println("省份數量: " + provinces.size()); // 2. 遞迴采集下級行政區 for (Address province : provinces) { List
cities = fetchList(CITY_URL + province.getId()); province.setChildren(cities); System.out.printf(" 省份 [%s] 城市數: %d%n", province.getName(), cities.size()); for (Address city : cities) { List
counties = fetchList(COUNTY_URL + city.getId()); city.setChildren(counties); for (Address county : counties) { List
towns = fetchList(TOWN_URL + county.getId()); county.setChildren(towns); } } } // 3. 序列化輸出 JSON 檔案 String json = "var addressData = " + JSON.toJSONString(provinces, true); File outputFile = new File("jd_address_data.js"); FileUtils.writeStringToFile(outputFile, json, "UTF-8"); System.out.println("資料已輸出至: " + outputFile.getAbsolutePath()); System.out.println("總請求數: " + requestCount); } /** 通用列表采集方法 */ private static List
fetchList(String url) throws IOException, InterruptedException { List
list = new ArrayList<>(); Document doc = Jsoup.parse(new URL(url), TIMEOUT_MS); Elements options = doc.select("option[value~=\\d+]"); // value 為數字的 option for (Element option : options) { Address addr = new Address(); addr.setName(option.text().replace("*", StringUtils.EMPTY)); // 移除 * 標記 addr.setId(option.attr("value")); list.add(addr); } // 請求頻率控制:每 100 次列印進度,每 500 次暫停 5 秒 requestCount++; if (requestCount % 100 == 0) { System.out.println("已完成請求: " + requestCount); } if (requestCount % 500 == 0) { System.out.println("休眠 5 秒避免頻率限制..."); Thread.sleep(5000); } return list; } } /** 行政區劃資料模型 (支援遞迴樹狀結構) */ class Address { private String name; // 名稱 private String id; // 代碼 private List
children; // 下級行政區 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public List
getChildren() { return children; } public void setChildren(List
children) { this.children = children; } }

輸出 JSON 結構範例

var addressData = [
  {
    "id": "1",
    "name": "北京",
    "children": [
      {
        "id": "72",
        "name": "朝陽區",
        "children": [
          {"id": "2799", "name": "三環以內", "children": []},
          {"id": "4139", "name": "北苑", "children": []}
        ]
      }
    ]
  },
  {
    "id": "2",
    "name": "上海",
    "children": [...]
  }
]

前端直接使用方式

<!-- 引入產出的 JS 檔案 -->
<script src="jd_address_data.js"></script>

<script>
// addressData 變數即可用
console.log(addressData[0].name); // "北京"
console.log(addressData[0].children[0].name); // "朝陽區"

// 級聯選擇器範例 (Vue/React/原生 JS 均可)
function renderProvince() {
    addressData.forEach(p => {
        // 建立 option 元素...
    });
}
</script>

反爬蟲與禮貌策略

策略實作方式
請求間隔每請求 `Thread.sleep(100)`,每 500 請求暫停 5 秒
User-Agent`Jsoup.connect(url).userAgent("Mozilla/5.0...").get()`
Cookie 維持若需登入態,先手動登入複製 Cookie 加入請求頭
IP 輪換大量采集建議使用代理 IP 池
錯誤重試網路異常/非 200 狀態碼指數退避重試 3 次

資料維護建議

  • 行政區劃會定期調整(如區劃合併、更名),建議每季度重新采集
  • 建立版本管理機制:輸出檔案命名包含日期 `jd_address_2024Q1.js`
  • 對比新舊資料產出差異報表,便於前端同步更新
  • 考慮引入國家統計局官方區劃代碼作為權威來源補充驗證

法律與合規提醒

  • 遵守目標網站 robots.txt 規範
  • 控制請求頻率,避免影響正常業務
  • 采集資料僅供內部業務使用,勿對外散佈或商業轉售
  • 優先尋求官方開放 API 或資料合作授權
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved