[小白向][Python]使用高德API進行地址編碼

  • 2021 年 5 月 17 日
  • AI

太長不看說明

需要使用相關程序可以直接看最後的「程序整體」一節。

前言

@李國寶 寶哥說得好,菜是原罪。

在這裡要向我的關注者們說明一下,目前階段我的代碼能力極弱,辜負了你們的信任。自轉專業以來,我一直想着有問題可以靠使用 MATLAB 的現成工具去解決,也沒動手寫一下代碼,臨找實習和做實驗的時候才發現自己沒有這方面的能力。

不論是做研究還是做工作,其實都和做人一樣講究「知行合一」,既要有理論指引,也應該有動手能力。過去我一直想着和學習數學一樣,從頭開始,從全面到細緻的角度去學習計算機,而又因為執行力的原因總是半途而廢。

現在我發現,編程方面較好的學習方法還是根據個人需要(學習、研究、工作)設定項目,然後自己從頭開始執行項目,等到項目完成了,也有了實踐經驗。

項目說明

項目需求

因工作需求,我需要從地址信息中抽取【省市區】信息,數據量大約有5W條,有嚴重的數據缺失和不規範現象。

方案設計

以往的做法是將數據拆分,分給每個人進行處理(每個人大約處理500條信息),這樣大約需要100個人參與這項工作。評估下來較為費時費力。

經過與需求方的討論,需求方預想了兩個可執行方案:

  1. 在EXCEL中設置VBS宏對地址進行校驗(是否有省市區信息,省市區對應關係是否正確);
  2. 通過數據校驗在EXCEL中設置省市區三列,第一列全國各省列表,第二列全國各市列表…(這裡無法校驗省市區對應關係是否正確,僅能確保對應字段有意義)。

我設想了三個方案:

  1. 在具有三級地址輸入的問卷網站,完成地址信息的重新錄入並導出數據;
  2. 通過字符長度或者正則表達式,從原地址數據中拆分出省市區信息;
  3. 調用相關API接口,完成從地址數據到結構化地址數據的轉化。

結合個人能力和性價比方面的考慮,初步選擇使用調用API幫助完成工作(後面驗證這個方法確實很方便)

方案驗證

通過查詢,了解到根據現有的無格式地址數據生成對應格式化的地址數據,這個應該是「地址清洗」的工作。有很多網站與平台提供相應服務,價格大概在100次/元以下[1][2]

後經 @李國寶 老師的指導,告訴我高德提供「地理編碼」服務[3],個人認證用戶可以每天免費使用30W次[4],處理效果也很好。

編程處理

需求分析

根據前面所述,確定需要批量調用高德API,且還涉及到文件讀寫的功能。

功能模塊

調用高德API完成地址編碼並返回結果

本段為 @李國寶 用2分鐘幫我寫的第1版本,已實現讀取CSV文件,調用高德API完成地址編碼並返回地址編碼結果。

Python3"># 寶哥花2分鐘寫的內容,已完全解決核心需求
import requests

amap_key = "amap_key"

def get_address_info(address):
    url = f"//restapi.amap.com/v3/geocode/geo?key={amap_key}&address={address}"
    payload = {}
    headers = {}
    response = requests.request("GET", url, headers=headers, data=payload)
    return response.json()


all_address = []

with open("xxxxx.csv", "r+") as xx_fp:
    for onle_line in xx_fp.readlines():
        address_info = get_address_info(onle_line)
        all_address.append(address_info)
print(all_address)

處理數據內容

可以看到返回的JSON數據有多層。

特別需要注意可能出現沒有『geocodes』字段及其子級字段的情況。

(在單條數據請求中,當’count’字段取值為’1’時,才會有’geocodes’的內容)

返回JSON格式數據,根據需求處理為字典列表,再輸出為CSV。

# 設置地址json數據轉dict格式函數
def oneline_geo_json2dict(address_info):
    address_info_dict={}
    # 當'count'字段取值為'1'時,才會有'geocodes'的內容
    # if len(address_info.get('geocodes',[]))!=0:
    if address_info.get('count',[])=='1':
        address_info_dict['formatted_address'] = address_info['geocodes'][0]['formatted_address']
        address_info_dict['country'] = address_info.get('geocodes')[0].get('country')
        address_info_dict['province'] = address_info.get('geocodes')[0].get('province')
        address_info_dict['city'] = address_info.get('geocodes')[0].get('city')
        address_info_dict['district'] = address_info.get('geocodes')[0].get('district')
        address_info_dict['lng'] = address_info.get('geocodes')[0].get('location').split(',')[0]
        address_info_dict['lat'] = address_info.get('geocodes')[0].get('location').split(',')[1]
    else:
        address_info_dict['formatted_address'] = ''
        address_info_dict['country'] = ''
        address_info_dict['province'] = ''
        address_info_dict['city'] = ''
        address_info_dict['district'] = ''
        address_info_dict['lng'] = ''
        address_info_dict['lat'] = ''
    return address_info_dict

程序主體

調用前述地址編碼和格式轉換函數,再加上文件讀寫操作。

程序整體

以下代碼經測試可以運行。

需要先申請高德webAPI key,然後將得到的key賦值到 ‘amap_key’ 變量。

註冊高德賬號,創建新應用,添加WEB服務API,得到Key值

需要準備 ‘inputdata.csv’ 文件,文件第一行為表頭,第一列為編號,第二列為地址。

文件格式如圖所示

# 調用高德地址編碼服務,實現將地址文本轉換為結構化地址數據功能
# API文檔 //lbs.amap.com/api/webservice/guide/api/georegeo
# 輸入文件 inputdata.csv 文件第一行為表頭,第一列為編號,第二列為地址
# 輸出文件 test_main.json 查詢後的結構化地址信息JSON文件
# 輸出文件 outputdata_main.csv 輸入文件及結構化地址信息合併後,輸出為CSV文件
# via 白小魚 //github.com/youngfish42


#### 初始化
import requests
import json
import time
import csv

# 高德webAPI key 申請鏈接 //lbs.amap.com/dev/key
amap_key = "amap_key"

# 設置地址編碼函數
def get_formatted_address(address):
    url = f"//restapi.amap.com/v3/geocode/geo?key={amap_key}&address={address}" # f加字符串處理後,可將{}內的值完成賦值
    payload = {}
    headers = {}
    response = requests.request("GET", url, headers=headers, data=payload)
    return response.json()


# 設置地址json數據轉dict格式函數
def oneline_geo_json2dict(address_info):
    address_info_dict={}
    # 當'count'字段取值為'1'時,才會有'geocodes'的內容
    # if len(address_info.get('geocodes',[]))!=0:
    if address_info.get('count',[])=='1':
        address_info_dict['formatted_address'] = address_info['geocodes'][0]['formatted_address']
        address_info_dict['country'] = address_info.get('geocodes')[0].get('country')
        address_info_dict['province'] = address_info.get('geocodes')[0].get('province')
        address_info_dict['city'] = address_info.get('geocodes')[0].get('city')
        address_info_dict['district'] = address_info.get('geocodes')[0].get('district')
        address_info_dict['lng'] = address_info.get('geocodes')[0].get('location').split(',')[0]
        address_info_dict['lat'] = address_info.get('geocodes')[0].get('location').split(',')[1]
    else:
        address_info_dict['formatted_address'] = ''
        address_info_dict['country'] = ''
        address_info_dict['province'] = ''
        address_info_dict['city'] = ''
        address_info_dict['district'] = ''
        address_info_dict['lng'] = ''
        address_info_dict['lat'] = ''
    return address_info_dict


#### 數據輸入
all_formatted_addr = []
all_formatted_addr_dict = []
with open("./inputdata.csv", "r+",encoding='utf-8-sig') as input_data:
    # inputdata.csv 文件第一列為編號,第二列為地址,此處讀取第二列的「地址」數據並進行地址編碼
    inputcsv = csv.DictReader(input_data) #將CSV文件每行數據讀取為字典對象
    for addr_info_origin in inputcsv:
        # print(one_line) #測試某條輸入數據
        formatted_addr = get_formatted_address(addr_info_origin['地址']) 
        formatted_addr_dict = oneline_geo_json2dict(formatted_addr)
        all_formatted_addr.append(formatted_addr)
        all_formatted_addr_dict.append(dict(addr_info_origin,**formatted_addr_dict)) #將原始數據和地址編碼後數據合併,並存入字典列表
        time.sleep(0.01) #控制並發量(上限每秒200次請求)

#### 數據輸出
# 測試全部數據的輸出
#print(all_formatted_addr)
# 將JSON寫入文件
with open('test_main.json', 'w', encoding='UTF-8') as output_data:
    json.dump(all_formatted_addr, output_data, ensure_ascii=False)
# 將字典內容寫入CSV文件
csv_columns = ['編號','地址','formatted_address', 'country', 'province','city','district','lng','lat']
try:
    with open("outputdata_main.csv", 'w',newline="") as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=csv_columns)
        writer.writeheader()
        for data in all_formatted_addr_dict:
            writer.writerow(data)
except IOError:
    print("I/O error")

隨手輸入地址得到結構化地址版本[5]

import requests
import json
address = input('請輸入地點:')
par = {'address': address, 'amap_key': 'amap_key'}
url = '//restapi.amap.com/v3/geocode/geo'
res = requests.get(url, par)
json_data = json.loads(res.text)
json_data = res.json()
geo = json_data['geocodes'][0]['location']
lng = geo.split(',')[0]
lat = geo.split(',')[1]

print(json_data['geocodes'][0]['country'])
print(json_data['geocodes'][0]['province'])
print(json_data['geocodes'][0]['city'])
print(json_data['geocodes'][0]['district'])
print(lng,lat)

下一步改進

  • 數據驗證。在輸入數據階段就執行數據驗證,如果遇到空的地址信息就不傳給調用API了。
  • 提高批量處理能力。該API可以每次同時處理10條地址數據,之後或許可以從這個角度提升處理效率。

但目前階段需要處理的數據一共5W條,幾年才處理一次,API每天可以處理30W條,需求不大,先擱置一下…

感謝

感謝以@李國寶老師為代表的朋友們的支持, 雖然代碼也就幾行,但我在不了解Python語法的情況下走了很多彎路,問了他們很多奇奇怪怪的初學者的問題。

另外在這個過程中一些細節參考了文檔(已列在參考資料部分)和兩本書籍(文後推薦)。

一直以來害怕編程,終於寫了個小demo感覺莫名的開心~(雖然還是很菜…

果然消除恐懼的最好辦法就是面對恐懼,堅持,才是勝利!加油,奧利給!

參考

  1. ^中文地址識別api的使用測試,快遞地址自動補全,自動識別省市區,地址清洗,到底哪個好用? //blog.csdn.net/Afterwards_/article/details/105905149
  2. ^智能地址解析,物流快遞中文地址識別格式化補全  //market.cloud.tencent.com/products/19581
  3. ^地理/逆地理編碼-API文檔-開發指南-Web服務 API | 高德地圖API //lbs.amap.com/api/webservice/guide/api/georegeo
  4. ^流量限制說明-實用工具-開發指南-Web服務 API | 高德地圖API //lbs.amap.com/api/webservice/guide/tools/flowlevel
  5. ^【Python_地理編碼】高德地理編碼簡易實現 //blog.csdn.net/YWP_2016/article/details/115488099