一、初拟的发明名称
一种基于网格分割的大规模城市坐标获取方法
二、所属技术领域
本发明通过将不规则的城市区域划分成一个个小的规则的网格区域,通过对小的网格区域的坐标点检索,并配合一套过滤算法,尽可能详尽的将城市的坐标点不重复、不遗漏的抓取下来,属于地理编码地址获取技术领域。
三、背景技术
传统获取城市坐标点的手段是通过在线访问各大地图厂商公开接口,输入检索区域对角两个顶点的经纬度坐标,获取区域内的坐标点。这种方法存在诸多缺陷,具体表现为:
(1)城市边界是不规则的,而检索区域的边界是规则的,这就导致边界处的检索区域很难划分;
(2)不同城市面积大小不同,城市内部的坐标点密度也差异很大,导致检索区域长宽大小很难确定;
(3)从地图厂商注册的账号有检索次数限制,单一账号难以大规模获取坐标点。
四、发明创造的目的
本发明的目的是提供一种详尽获取中国不同城市坐标点、且不重不漏的通用解决方案。
五、发明创造的技术方案
1.初始网格分割
按照城市的最西南端和东北端作为矩形的两个顶点,用这个大矩形框粗略地划分出城市的范围,再通过规定小网格的边长大小(以经纬度为单位),将大的矩形区域依次划分为很多个小网格,并将划分出的每个小网格西南端和东北端的经纬度存入数据库,完成初始网格分割。
2.网格遍历
首先,依次遍历步骤1划分出的小网格,拿到小网格西南端和东北端的经纬度坐标;其次,依次遍历地图厂商提供的检索标签(例:金融);最后,每页返回的坐标点有限制,所以要每页依次遍历,直至该页不存在返回值或达到地图厂商最大页数限制为止。将小网格西南端和东北端经纬度坐标、检索标签内容、地图厂商API ak、第几页、城市名等信息作为参数传入,调用地图厂商矩形区域地址检索API,获取该区域内的所有坐标点。坐标点信息包括:公司名、公司类别、经度、纬度、公司地址、城市名、区域名等。将这些信息保存到数据库中。
3.坐标点过滤
在步骤2遍历小网格的过程中,若小网格所在的区域一部分属于该城市,另一部分不属于该城市,则需要对该小网格区域获取坐标点进行过滤筛选,本发明采用的方法是:对地图厂商返回的每个坐标点的所属城市进行判断,若不属于当前城市,则对该坐标点做丢弃处理。
4.网格分割动态调整
在步骤2遍历小网格的过程中,若划分的小网格区域过大,导致该区域实际存在的坐标点超过地图厂商能返回的上限(例:百度地图针对一个区域的地址检索,最多返回400条记录),就会造成坐标点遗漏。为了避免这个问题,本发明采用动态切割小网格方法,保证动态切割后的每个小网格区域内的坐标点都不会超过地图厂商的返回上限。具体实现为:依次遍历每一页,若遍历到地图厂商限制的最大页,依然有坐标点返回,则说明可能还遗漏有坐标点没有遍历到,于是采用递归方式,将切分网格点的边长设置为参数值的一半,再次执行步骤2的逻辑,直到切分的网格足够小,不会遍历到地图厂商限制的最大页为止。
5.动态切换账号
因为每个账号每天访问次数有限制,所以单个账号能够访问的信息有限。为了突破每天访问次数限制,本方案采用申请多个账号,把这些账号按顺序存入账号列表中。在使用账号时,按照先进先出的原则依次拿到账号信息。当一个账号配额使用完毕,再调用地图厂商矩形区域地址检索API,会返回配额使用完毕的报错信息。程序捕获到这个异常,直接切换下一个账号即可。根据推算,一个账号的配额可以供程序连续访问2个小时,最多需12个账号,便可以让程序24h不间断运行。
六、本申请相对现有技术而言,所具有的优点和效果。
本发明专利针对现有基于地图厂商矩形区域地址检索服务获取城市坐标点,存在以下优势:
1.解决了矩形检索区域在不规则城市边界难以划分的问题;
2.动态调整检索区域面积,解决不同城市检索区域难以按照一个标准划分问题;
3.突破了地图厂商单账号每天检索次数限制,解决了大规模获取坐标点难题。
七、附图
图1 网格搜索地图厂商API
八、实施例
以百度地图为例,介绍本方案实施步骤:
1.初始网格分割
找出城市的最西南端和东北端两个顶点的经纬度坐标,再以初始边长为0.015的矩形(以经纬度为单位),将大的矩形区域依次划分为很多个小网格,并记录每个小网格西南端和东北端的经纬度,完成初始网格分割。
2.网格遍历
首先,依次遍历步骤1划分出的小网格,拿到小网格西南端和东北端的经纬度坐标;其次,依次遍历地图厂商提供的检索标签(例:金融);最后,将小网格西南端和东北端经纬度坐标、检索标签内容、地图厂商API ak、第几页、城市名等信息作为参数传入,调用地图厂商矩形区域地址检索API,获取该区域内的所有坐标点。坐标点信息包括:公司名、公司类别、经度、纬度、公司地址、城市名、区域名等。将这些信息保存到数据库中。
3.坐标点过滤
对地图厂商返回的每个坐标点的所属城市进行判断,若不属于当前城市,则对该坐标点做丢弃处理。
4.网格分割动态调整
依次遍历每一页,若遍历到地图厂商限制的最大页,依然有坐标点返回,则采用递归方式,将切分网格点的边长设置为参数值的一半,再次执行步骤2的逻辑,直到切分的网格足够小,不会遍历到地图厂商限制的最大页为止。
5.动态切换账号
在使用账号时,按照先进先出的原则依次拿到账号信息。当一个账号配额使用完毕,再调用地图厂商矩形区域地址检索API,会返回配额使用完毕的报错信息。程序捕获到这个异常,直接切换下一个账号即可。
附件1:python实现代码
# -*- coding:utf-8 -*-
import json
import codecs
import os
import urllib
import sys
import time
from urllib.request import urlopen, quote
import csv
import traceback
class WrongCityException(Exception):
def __init__(self,message):
Exception.__init__(self)
self.message=message
class Over400Exception(Exception):
def __init__(self,message):
Exception.__init__(self)
self.message=message
class ChangeAKException(Exception):
def __init__(self,message):
Exception.__init__(self)
self.message=message
def readTag(road):
global tag
tag = []
#file = road
#data = open(file,'a+')
data = open(road, encoding='utf-8-sig')
#data = csv.reader(file)
for line in data:
#for i in line[1:]:
#tag.append(i)
tag.append(line.replace("\n", ""))
print(len(tag))
data.close()
return tag
def GaoDeAPI(key,bounds,ak,page_num):
flag = True
url = "xxx"
output = 'json'
def BaiDuAPI(key,bounds,ak,page_num,city):
flag = True
url = "xxx"
output = 'json'
ak = ak
keys = quote(key)
url_send = url + "?query=%s&bounds=%s&output=json&ak=%s&page_size=20&page_num=%s" % (keys, bounds, ak, page_num)
req = urlopen(url_send,timeout=120)
res = req.read().decode() # 将其他编码的字符串解码成unicode
temp = json.loads(res)
print(temp)
if temp['status'] == 301 or temp['status'] == 302 or temp['status'] == 401 or temp['status'] == 402:
print("换AK异常")
raise ChangeAKException("要换AK了")
return flag
if temp['results'] == [] or temp['status'] == 1:
flag = False
elif temp['results'][0]['name'] == city:
pass
elif temp['results'][0]['city'] != city : #如果爬取信息不在想要获得的城市之中
raise WrongCityException("城市不对")
return flag
elif temp['total'] == 400 :
for line in temp['results']:
name = line.get('name','')
lat = line['location'].get('lat','')
lng = line['location'].get('lng','')
address = line.get('address','')
city = line.get('city','')
area = line.get('area','')
try:
company_data.write(str(name)+"^"+str(key)+"^"+str(lat)+"^"+str(lng)+"^"+str(address)+"^"+str(city)+"^"+str(area)+"\n")
company_data.flush()
except Exception as e:
pass
#flag = True
raise Over400Exception("总个数超过400") #抛出超过400的错误需要调整矩形框大小
else:
for line in temp['results']:
name = line.get('name','') #如果没有name键,就默认为空值
lat = line['location'].get('lat','')
lng = line['location'].get('lng','')
address = line.get('address','')
city = line.get('city','')
area = line.get('area','')
try:
company_data.write(str(name)+"^"+str(key)+"^"+str(lat)+"^"+str(lng)+"^"+str(address)+"^"+str(city)+"^"+str(area)+"\n")
company_data.flush()
except Exception as e:
pass
flag = True
return flag
class LocaDiv(object):
def __init__(self, loc_all, square_size): #square_size 为切分的小矩形框的大小 以经纬度为单位 例如0.03
self.loc_all = loc_all
self.square_size = square_size
def lat_all(self):
lat_sw = float(self.loc_all.split(',')[0].strip())
lat_ne = float(self.loc_all.split(',')[2].strip())
lat_list = []
for i in range(0, int((lat_ne - lat_sw + 0.0001) / self.square_size)+1): # 0.1为网格大小,可更改
lat_list.append(round(lat_sw + self.square_size * i,2)) # 0.05
lat_list.append(lat_ne)
#print("lat_list", lat_list)
return lat_list
def lng_all(self):
lng_sw = float(self.loc_all.split(',')[1].strip())
lng_ne = float(self.loc_all.split(',')[3].strip())
lng_list = []
for i in range(0, int((lng_ne - lng_sw + 0.0001) / self.square_size)+1): # 0.1为网格大小,可更改
lng_list.append(round(lng_sw + self.square_size * i,2)) # 0.1为网格大小,可更改
lng_list.append(lng_ne)
#print("lng_list", lng_list)
return lng_list
def ls_com(self):
l1 = self.lat_all()
l2 = self.lng_all()
ab_list = []
for i in range(0, len(l1)):
a = str(l1[i])
for i2 in range(0, len(l2)):
b = str(l2[i2])
ab = a + ',' + b
ab_list.append(ab)
return ab_list
def ls_row(self):
l1 = self.lat_all()
l2 = self.lng_all()
ls = []
for i in range(len(l1)-1):
for j in range(len(l2)-1):
a = str(l1[i]) + "," + str(l2[j])
b = str(l1[i+1]) + "," + str(l2[j+1])
ls.append(a+","+b)
return ls
def initial_AK_pond(): #初始化ak 池 0 为有额度 1为额度已经用完 保存成数组格式
global ak_dic
ak_dic = {}
ak_dic = {
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": 0
}
def exchange_AK():
for line in ak_dic.items():
if line[1] == 0:
return line[0]
print("ak池的额度全部用完了")
return None
def run():
initial_AK_pond()
ak = exchange_AK()
print("初始AK为",ak)
city = "昆明市" #填入需要爬取的城市名字
print("开始爬取数据,请稍等...")
global company_data
company_data = open("/Users/ake/Downloads/公司/昆明.txt", 'a+',encoding='utf8')
tag_list = readTag("/Users/ake/Downloads/公司/行业.txt")
global error_list
error_list = open("/Users/ake/Downloads/公司/昆明error.txt", 'a+',encoding='utf8')
print("标签列表",tag_list)
bounds = '24.388,102.170,26.545,103.668'
loc = LocaDiv(bounds, 0.015) #将城市用最西南 和 最东北的经纬度划分 0.02为划分的矩形大小
locs_to_use = loc.ls_row() #生成划分完毕后的 bounds
print("总共有", len(locs_to_use), "个矩形框")
global loc_list
global loc_lists
loc_lists=[]
loc_list=open("/Users/ake/Downloads/公司/昆明经纬度.txt", 'a+',encoding='utf8')#用于保存已经检索的矩形区域
loc_list.seek(0,0)#将光标位置移到文本开始位置
for line in loc_list:
loc_lists.append(line.replace("\n", ""))
break_flag = False #用于表示跳出循环 当前矩形在框外时触发 跳出两层循环 换到下一个小矩形
for loc_to_use in locs_to_use: #遍历每个小矩形框
print("下一个矩形框",loc_to_use)
if loc_to_use in loc_lists: #判断矩形是否已经检索过
print(loc_to_use+"已经检索")
continue
for x in tag_list: #按照标签来跑 例如金融
sum = 0
for i in range(20): #最多20条
print("第", i, "条", x)
try:
flag = BaiDuAPI(x, loc_to_use, ak, i, city) #调用百度地图API
if flag == False: # 如果flag 为False 意味着这一次掉用哪个API结果为空,跳出第一层循环
break
except WrongCityException as e:
print("捕获到 城市不对异常",e)
break_flag = True
break
except Over400Exception as e:
print("捕获到 大于400个消息异常",e)
if sum == 0: #第一次遇到问题写入
writing_str = "超过400个数量错误 在 " + loc_to_use + " " + x + "出了错误"
error_list.write(writing_str)
error_list.write("\n")
error_list.flush()
sum += 1
continue
except ChangeAKException as e: #捕捉AK额度不够的异常
print("捕获到AK额度不够的异常")
ak_dic[ak] = 1 #将当前AK的状态设置为 已经跑完 P.S 1为已经跑完 0 为还有剩余额度
ak = exchange_AK() #换一个AK
print("已经更换AK",ak)
print("-----------------等待3s-------------------")
time.sleep(3)
if ak == None: #如果调用ak 之后为None 证明ak池的额度全部用完 错误文件记录当前运行结束时的状态
error_list.write("在这里停止了:" + loc_to_use + "爬取大区域为"+ bounds)
error_list.write("\n")
error_list.flush()
return None
else:
continue
except Exception as e:
error_list.write("其他异常:" + loc_to_use + "爬取大区域为"+ bounds)
error_list.write("\n")
error_list.write(traceback.format_exc()+"\n")
error_list.flush()
if break_flag == True:
break_flag = False
break
loc_list.write(loc_to_use+"\n")
loc_list.flush()
if __name__ == '__main__':
run()
company_data.close()
error_list.close()
loc_list.close()