获取高德地图省市区县列表

时间:2024-02-21 19:02:10

项目中需要用省市区来进行检索,原想高德地图肯定会有API来获得这些数据,结果没有找到,有一个接口好像可以用,但是会附带大量的边界坐标点。

所以就不如自己把高德的省市区列表扒下来,自己写接口来完成这个功能。

看到高德地图的js的demo里面有这样的展示页面:http://lbs.amap.com/api/JavaScript-api/example/u/2001-2/,所以我就直接利用它来分析。

1. 省列表

省的列表是直接写死在这个界面里的,所以我也照搬,把省都直接写死:

provinceList = [\'北京市\', \'天津市\', \'河北省\', \'山西省\', \'内蒙古自治区\', \'辽宁省\', \'吉林省\',\'黑龙江省\', \'上海市\', \'江苏省\', \'浙江省\', \'安徽省\', \'福建省\', \'江西省\', \'山东省\',\'河南省\', \'湖北省\', \'湖南省\', \'广东省\', \'广西壮族自治区\', \'海南省\', \'重庆市\',\'四川省\', \'贵州省\', \'云南省\', \'**自治区\', \'陕西省\', \'甘肃省\', \'青海省\', \'宁夏回族自治区\', \'**自治区\', \'台灣\', \'香港特别行>政区\', \'澳门特别行政区\'];

2. 市列表

从市列表开始,就要扒高德地图的接口了,首先模仿浏览器的访问设置几个Http头:

复制代码
send_headers = {
        \'Accept\':\'*/*\',
        \'Accept-Encoding\':\'gzip, deflate, sdch\',
        \'Accept-Language\':\'zh-CN,zh;q=0.8\',
        \'Connection\':\'keep-alive\',
        \'Host\':\'restapi.amap.com\',
        \'Referer\':\'http://lbs.amap.com/fn/iframe/?id=3556\',
}
复制代码

然后是根据省名获取市列表的函数:

复制代码
def getCity(province):
    print "Province->" + province
    url = "http://restapi.amap.com/v3/config/district?subdistrict=1&extensions=all&level=province&key=608d75903d29ad471362f8c58c550daf&s=rsv3&output=json&callback=jsonp_" + getJsonP() + "_&keywords=" + province;
    data = getData(url)
    item = data[\'districts\'][0];
    if item[\'citycode\'] == []:
        item[\'citycode\'] = \'\'
    save([item[\'citycode\'], item[\'adcode\'], item[\'name\'], item[\'center\'], item[\'level\'], \'\'])
    for item in data[\'districts\'][0][\'districts\']:
        save([item[\'citycode\'], item[\'adcode\'], item[\'name\'], item[\'center\'], item[\'level\'], \'\'])
        while 1:
            try:
                getDistrict(item)
                break
            except Exception, e:
                print \'retry:\' + item[\'name\'] + "->" + str(e)
复制代码

这里有跨域访问用的getJsonP、发起网络请求的getData、存数据库的save,这三个函数的具体实现一会再说,先看其它逻辑,高德服务端返回数据是长得这个样子的:

districts域就是搜索的结果,因为这是按名称进行搜索,而不是id,所以很可能会搜出来多个结果,当然省名不会重,但一样得到的是一个 JSONArray,所以用data[\'districts\'][0]来得到省的信息,除了直辖市之外,省级单位的citycode都是空的,用 item[\'citycode\'] = \'\'给它一个空字符串做默认值,然后调用save方法把它存入数据库,在上图中也看到了,data[\'districts\'][0]里面还有一个 districts域,这里面就是所有市的信息,同样把它们存入数据库,然后针对每个市,去获取区的列表,这里有可能会出现网络问题,所以加上了失败重 试。

下面依次来看上面几个关键方法的实现:

1)getJsonP:

跨域访问是通过jsonp回调的方式进行的,函数名都是jsonp_**xx_()的形式,这个**xx是一个变化的随机数字,我采用依次递增的形式来生成这个jsonp的数字:

复制代码
jsonp = 9999
def getJsonP():
    global jsonp
    jsonp = jsonp + 1
    if (jsonp > 99999):
        jsonp = 10000
    return str(jsonp)
复制代码

这里我保证这个数字是一个5位数(理论上几位都可以),因为在图里可以看到,返回值是jsonp_**xx_({aaa})的形式,我们要分析json串需要把前后没用的字符去掉,如果长度固定就很好删了。

2)getData:

发起网络请求并把返回的数据转成JSONObject:

复制代码
def getDataWithEx(url):
    req = urllib2.Request(url,headers=send_headers)
    r = urllib2.urlopen(req,timeout=30)

    if r.info().get(\'Content-Encoding\') == \'gzip\':
        buf = StringIO(r.read())
        f = gzip.GzipFile(fileobj=buf)
        data = f.read()
    else:
        data = r.read()
    return json.loads(data[13:-1])

def getData(url):
    while 1:
        try:
            response = getDataWithEx(url)
            break
        except Exception, e:
            print \'retry:\' + url + " with error " + str(e)
    return response
复制代码

这里也加入了失败重试,一般的数据都要用gzip进行解码,但是我忘了是哪个了,好像是有一个区的数据很特别没有用gzip,所以这里要分开判断,getJsonP里面介绍了,返回的数据前后是写无用的字符,用data[13:-1]删掉。

3)save

这是往MySQL里存数据的函数,没有特殊的点需要介绍,直接上代码:

复制代码
def save(value):
    try:
        conn=MySQLdb.connect(host=\'www.**.com\',user=\'**\',passwd=\'**\',port=3306,charset="utf8")
        conn.select_db(\'test\')
        cur=conn.cursor()
        cur.EⅩEcute("insert into amap(citycode, adcode, name, center, level, areacode) values(%s,%s,%s,%s,%s,%s)",value)
        conn.commit()
        cur.close()
        conn.close()
    except MySQLdb.Error,e:
        print "Mysql Error %d: %s" % (e.args[0], e.args[1])
复制代码

3. 区列表

跟获取城市列表相似:

复制代码
def getDistrict(city):
    print "->City->" + city[\'name\']
    url = "http://restapi.amap.com/v3/config/district?subdistrict=1&extensions=all&level=city&key=608d75903d29ad471362f8c58c550daf&s=rsv3&output=json&callback=jsonp_" + getJsonP() + "_&keywords=" + city[\'name\']
    data = getData(url)
    for possible in data[\'districts\']:
        if possible[\'adcode\'] == city[\'adcode\']:
            for item in possible[\'districts\']:
                save([item[\'citycode\'], item[\'adcode\'], item[\'name\'], item[\'center\'], item[\'level\'], \'\'])
                while 1:
                    try:
                        getBusiness(item)
                        break
                    except Exception, e:
                        print \'retry:\' + item[\'name\'] + "->" + str(e)
            break
复制代码

和获取市列表稍微不同的是这里不能直接data[\'districts\'][0]了,按市、区搜就有可能重名了,所以用城市的adcode来匹配,针对匹配上的市,遍历区的信息存入数据库,然后再针对区搜索商圈。

4. 商圈列表

跟获取市列表和获取区列表没有太大的区别:

复制代码
def getBusiness(district):
    print "->->District->" + district[\'name\']
    url = "http://restapi.amap.com/v3/config/district?subdistrict=1&extensions=all&level=district&key=608d75903d29ad471362f8c58c550daf&s=rsv3&output=json&callback=jsonp_" + getJsonP() + "_&keywords=" + district[\'name\']
    data = getData(url)
    for possible in data[\'districts\']:
        if possible[\'adcode\'] == district[\'adcode\']:
            values=[]
            for item in possible[\'districts\']:
                values.append((item[\'citycode\'], item[\'adcode\'], item[\'name\'], item[\'center\'], item[\'level\'], item[\'areacode\']))
            saveAll(values)
            break
复制代码

唯一不同的是直接用saveAll一次性存储所有的数据而不是一条条的save:

复制代码
def saveAll(values):
    try:
        conn=MySQLdb.connect(host=\'www.**.com\',user=\'**\',passwd=\'**\',port=3306,charset="utf8")
        conn.select_db(\'test\')
        cur=conn.cursor()
        cur.EⅩEcutemany("insert into amap(citycode, adcode, name, center, level, areacode) values(%s,%s,%s,%s,%s,%s)",values)
        conn.commit()
        cur.close()
        conn.close()
    except MySQLdb.Error,e:
        print "Mysql Error %d: %s" % (e.args[0], e.args[1])
复制代码

关键的逻辑就都介绍完了,下面是头部库的导入和编码设置:

复制代码
# -*- coding: utf-8 -*-
#encoding=utf-8

import urllib2
import sys, json
from StringIO import StringIO
import gzip
import MySQLdb
import datetime

reload(sys)
sys.setdefaultencoding(\'utf-8\')
复制代码

这里要说的一点是:不知道为什么编译器会报错说setdefaultencoding方法不存在,有这个错误的话不用理会,可以正常运行,如果不设置会出现乱码。

最后是上述方法的调用:

复制代码
starttime = datetime.datetime.now()
for province in provinceList:
    getCity(province)
print \'over\'
endtime = datetime.datetime.now()
print (endtime - starttime).seconds
复制代码

扒数据的时间很长,n多小时,我忘了具体是多久了,而且为了避免出错我是把所有省分成了好几段分别扒的。

如果不想自己扒数据,想直接得到省市区的数据,可以直接下载我转存的sql文件。

也可以临时使用我的接口:

获取省列表:http://www.yxbilu.com/sites/tools/amap/province;

获取市列表:http://www.yxbilu.com/sites/tools/amap/city?adcode=110000(省的adcode);

获取区列表:http://www.yxbilu.com/sites/tools/amap/district?adcode=110100(市的adcode)。