­

[小白向][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