基于 Golang 完整获取百度地图POI数据的方案
百度地图为web开发者提供了基于HTTP/HTTPS协议的丰富接口,其中包括地点检索服务,web开发者通过此接口可以检索区域内的POI数据。百度地图处于数据保护对接口做了限制,每次访问服务,最多只能检索到400条数据,这样开发者就无法轻易的扒光收录的POI数据。作者基于 Golang 编写程序,完整获取百度地图POI数据。
百度地图WEB服务API基于HTTP/HTTPS协议,用户按照API文档要求的格式发送HTTP请求来获取POI数据,请求获取的数据格式可以为xml或json。
地点检索接口提供了3种检索方式:行政区划区域检索,圆形区域检索,矩形区域检索。详见接口文档。web 开发者在不同的场景可以采用不同的检索方式。
我们的目的是完整的获取POI数据,针对这一使用场景,最先想到是按照行政区划区域检索,先建立区域字典,然后一个区域一个区域的去获取数据。但是,有些区域POI的密度很大,一个市可能有数千家餐馆,而每次检索最多检索到400条数据,显然无法完整的拿到数据。
为突破数据保护的限制,这里采用矩形检索的方式。首先把要抓取的区域放到两经线和两纬线之间,构成一个大矩形(把地球看成方的),然后从大矩形的角落开始,逐个检索一个个小矩形,同时根据上一个小矩形的POI数量动态调整小矩形的面积。当所有小矩形组合覆盖完成大矩形之后,即可认为数据抓取完整。
OK,动手写代码!
需要考虑如下问题:
- 如何设置小矩形的初始面积?如何动态调整小矩形的面积?
- 地图API每日调用次数有限制, 超出限制之后如何继续上次的抓取?
提出这些问题之后,程序的大致结构有了,首先加载抓取状态数据,判断用户上次是否抓取完整,如果已经抓取完整,则让用户输入新的大矩形参数(两个经度,两个纬度);否则让用户决定是否继续抓取。
那么,状态数据应该包含哪些?该怎么存储状态数据呢?显然这么小的一个程序不需要用到数据库,存到文件中即可,而Golang处理JSON格式的数据很方便,所以状态数据可以以JSON格式存到文件中。存储的内容应该包含大矩形信息,小矩形信息,当前POI类别信息(POI文档),小矩形区域最近一次抓取的页号(每页最多20条),API调用信息。于是状态格式可以设计为:
{
"upperLatitude": 40.15,
"lowerLatitude": 38.33,
"leftLongitude": 116.42,
"rightLongitude": 118.04,
"apiAvailableTimes": 2000,
"lastUseDate": "20181219",
"apiKey": "申请的API Key",
"lastLongitudePosition": 118.98,
"lastLatitudePosition": 40.109,
"lastCategoryIndex": 0,
"lastLongitudeLength": 1.28,
"lastLatitudeLength": 0.001,
"lastPageIndex": -1
}
对应的golang结构体:
/**
* 状态结构体
*/
type Status struct {
UpperLatitude float64 `json:"upperLatitude"` //上纬度
LowerLatitude float64 `json:"lowerLatitude"` //下纬度
LeftLongitude float64 `json:"leftLongitude"` //左经度
RightLongitude float64 `json:"rightLongitude"` //右经度
ApiAvailableTimes int `json:"apiAvailableTimes"` //API可用次数
LastUseDate string `json:"lastUseDate"` //上次使用日期
ApiKey string `json:"apiKey"` //API Key
LastLongitudePosition float64 `json:"lastLongitudePosition"` //上次抓取经度位置
LastLatitudePosition float64 `json:"lastLatitudePosition"` //上次抓取纬度位置
LastCategoryIndex int `json:"lastCategoryIndex"` //上次抓取类别索引
LastLongitudeLength float64 `json:"lastLongitudeLength"` //上次抓取矩形区域横向宽度(经度位移)
LastLatitudeLength float64 `json:"lastLatitudeLength"` //上次抓取矩形区域纵向高度(纬度位移)
LastPageIndex int `json:"lastPageIndex"` //上次抓取页索引
}
为使程序模块化,我们将保存,读取状态文件,保存状态等函数挂到此结构体下(为阅读方便,省略了函数体)。
/*
* 保存状态
*/
func (s *Status) Save(path string) error /**
* 加载状态
*/
func (s *Status) Load(path string) error var category = []string{
"酒店", "生活服务", "房地产", "汽车服务",
"教育培训", "丽人", "运动健身", "美食",
"政府机构", "自然地物", "公司企业", "交通设施",
"旅游景点", "医疗", "文化传媒", "出入口", "购物",
"休闲娱乐", "金融"} /**
* 获取类别
*/
func (s Status) GetCategory() string /**
* 类别数量
*/
func (s Status) CategorySize() int /**
* 重置API
*/
func (s *Status) Reset()
/**
* 更新API的使用次数
*/
func (s *Status) RefreshApiAvairableTimes() func today() string
完成了抓取状态控制代码之后,接下来是设计解析POI数据的代码,golang天生处理JSON非常方便,所以我们需要接口返回JSON格式数据,然后按照返回的数据格式设计对应的结构体即可。
首先我们在浏览器中输入如下url,注意要先申请API Key,获取一手样本数据,然后写出对应的golang结构体。
http://api.map.baidu.com/place/v2/search?query=银行&bounds=39.915,116.404,39.975,116.414&output=json&ak={您的密钥}
样例数据
{
"status":0,
"message":"ok",
"total":400,
"results":[
{
"name":"华信半岛酒店(天津百货大楼)",
"location":{
"lat":39.135317,
"lng":117.199346
},
"address":"天津市和平区和平路172号天津百货大楼F1",
"province":"天津市",
"city":"天津市",
"area":"和平区",
"street_id":"546b796f38537107d518b782",
"telephone":"(022)59063666",
"detail":1,
"uid":"546b796f38537107d518b782"
},
{
"name":"维也纳酒店(贵州路店)",
"location":{
"lat":39.118757,
"lng":117.200051
},
"address":"天津市和平区贵州路16号",
"province":"天津市",
"city":"天津市",
"area":"和平区",
"street_id":"c92e659a2a785d188c55ec32",
"telephone":"(022)85588888",
"detail":1,
"uid":"58c3f6ea68ee0da917c8f6f8"
},
{
"name":"鑫茂天财酒店",
"location":{
"lat":39.10411,
"lng":117.125587
},
"address":"天津市西青区华苑新技术产业园区榕苑路1号(临近复康路)",
"province":"天津市",
"city":"天津市",
"area":"西青区",
"street_id":"4edf9fbf49ade88dcc06a459",
"detail":1,
"uid":"4edf9fbf49ade88dcc06a459"
}
]
}
对应的结构体
// POI 位置
type PoiLocation struct {
Lat float64 `json:"lat"` //纬度
Lgt float64 `json:"lng"` //经度
} // POI 信息
type Poi struct {
Name string `json:"name"`
Location PoiLocation `json:"location"`
Address string `json:"address"`
Province string `json:"province"`
City string `json:"city"`
Area string `json:"area"`
Street_id string `json:"street_id"`
Telephone string `json:"telephone"`
//Detail string `json:"detail"`
Uid string `json:"uid"`
} type PoiResponse struct {
Status int `json:"status"`
Message string `json:"message"`
Total int `json:"total"`
Results []Poi `json:"results"`
}
最后是流程控制,简单起见,用户交互采用控制台输入的方式。流程控制较为复杂,有三层循环,分别对应类别切换,纬度切换,经度切换。代码如下:
for ; s.LastCategoryIndex < s.CategorySize(); s.LastCategoryIndex++ {
f, err := os.OpenFile(fmt.Sprintf("%f_%f_%f_%f_%s.txt", s.LowerLatitude, s.LeftLongitude, s.UpperLatitude, s.RightLongitude, s.GetCategory()), os.O_APPEND|os.O_WRONLY|os.O_CREATE, )
if err != nil {
log.Panic("打开文件出错。", err)
}
defer f.Close() for ; s.LastLatitudePosition > s.LowerLatitude; s.LastLatitudePosition -= s.LastLatitudeLength {
s.LastLatitudePosition = math.Round(s.LastLatitudePosition*) /
for s.LastLongitudePosition < s.RightLongitude {
upper := s.LastLatitudePosition
lower := upper - s.LastLatitudeLength
left := s.LastLongitudePosition
right := left + s.LastLongitudeLength NEXT_PAGE:
if s.ApiAvailableTimes < {
log.Println("今日API次数用完了")
os.Exit()
}
log.Printf("开始抓取[%.5f,%.5f,%.5f,%.5f], %s, 页号:%d", lower, left, upper, right, s.GetCategory(), s.LastPageIndex+)
time.Sleep() //避免超出频率限制
s.ApiAvailableTimes--
url := fmt.Sprintf("http://api.map.baidu.com/place/v2/search?output=json&page_size=20&scope=1&coord_type=1&query=%s&bounds=%.5f,%.5f,%.5f,%.5f&ak=%s&page_num=%d",
s.GetCategory(), lower, left, upper, right, s.ApiKey, s.LastPageIndex+) resp, err := http.Get(url)
if nil != err {
log.Panic("网络出现错误", err)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Panic("读取HTTP Body数据时出错", err)
}
poiResponse := &poi.PoiResponse{}
err = json.Unmarshal(body, poiResponse)
if nil != err {
log.Println("解析服务器返回的数据出错,5秒后重新获取", err)
time.Sleep()
} else if poiResponse.Status != {
log.Printf("未能成功获取POI数据,服务器返回:%s\n", poiResponse.Message)
time.Sleep()
} else {
if poiResponse.Total == { //没有获取到数据
//扩大矩形面积
log.Printf("未能获取到数据,区域宽度(经度)由%f调整为%f\n", s.LastLongitudeLength, s.LastLongitudeLength*)
s.LastLongitudePosition += s.LastLongitudeLength
//加个判断,不能让它无限膨胀
if s.LastLongitudeLength* < s.RightLongitude-s.LeftLongitude {
s.LastLongitudeLength *=
}
s.Save(statusFileName)
} else if poiResponse.Total == { //获取的数据达到400上限,可能不完整
log.Printf("获取的数据总数达到400条,可能不完整,区域宽度(经度)由%f调整为%f\n", s.LastLongitudeLength, s.LastLongitudeLength/)
//缩小矩形面积
s.LastLongitudeLength /=
} else { // 获取的数据在 0 到 400之间,有效
log.Printf("获取的数据总条数为%d,有效!\n", poiResponse.Total) for _, p := range poiResponse.Results {
record := fmt.Sprintf("%s,%f,%f,%s,%s,%s,%s,%s\n", p.Name, p.Location.Lgt, p.Location.Lat, p.Telephone, p.Province, p.City, p.Area, p.Address)
log.Print(record)
if _, err = f.WriteString(record); err != nil {
panic(err)
}
} size := len(poiResponse.Results)
if size == { //表示还有下一页
log.Printf("当前页数据条数为20条,表示不是末尾页,页号+1")
s.LastPageIndex++
s.Save(statusFileName)
goto NEXT_PAGE
} else {
log.Printf("当前页数据为%d条,是末尾页,页号重置", size)
s.LastPageIndex = -
s.LastLongitudePosition += s.LastLongitudeLength
s.Save(statusFileName)
}
}
}
}
}
log.Printf("抓取完一行,纬度由%f调整为%f", s.LastLatitudePosition, s.LastLatitudePosition+s.LastLatitudeLength)
//抓完一行了,经度回到原点
s.LastLongitudePosition = s.LeftLongitude
s.Save(statusFileName)
}
f.Close()
//抓完一个类别了,回到原点
s.LastLatitudeLength = s.UpperLatitude
s.LastCategoryIndex++
s.Save(statusFileName)
}
完整代码可点击这里下载
基于 Golang 完整获取百度地图POI数据的方案的更多相关文章
- 获取百度地图POI数据三(模拟关键词搜索)
上一篇博文中讲到如何获取用于搜索的关键词,并且已经准备好了一百五十万的关键词 这其中有门牌号码,餐馆酒店名称,公司名称,道路名称等.有了这些数据,我们就可以通过代码,模拟我们在百度地图的搜索框中搜 ...
- 获取百度地图POI数据二(准备搜索关键词)
上篇讲到 想要获取尽可能多的POI数据 需要准备尽可能多的搜索关键字 那么这些关键字如何得来呢? 本人使用的方法是通过一些网站来获取这些关键词 http://poi.mapbar.com ...
- 获取百度地图POI数据一(详解百度返回的POI数据)
POI是一切可以抽象为空间点的现实世界的实体,比如餐馆,酒店,车站,停车场等.POI数据具有空间坐标和各种属性,是各种地图查询软件的基础数据之一.百度地图作为国内顶尖的地图企业,其上具有丰富的POI数 ...
- 百度地图POI数据爬取,突破百度地图API爬取数目“400条“的限制11。
1.POI爬取方法说明 1.1AK申请 登录百度账号,在百度地图开发者平台的API控制台申请一个服务端的ak,主要用到的是Place API.检校方式可设置成IP白名单,IP直接设置成了0.0.0.0 ...
- 基于指定文本的百度地图poi城市检索的使用(思路最重要)
(转载请注明出处哦)具体的百度地图权限和apikey配置以及基础地图的配置不叙述,百度地图定位可以看这个链接的http://blog.csdn.net/heweigzf/article/details ...
- 教你如何拔取百度地图POI兴趣点
教你如何拔取百度地图POI兴趣点 通过聚合数据提供的接口,获取百度地图的POI兴趣点,并存储至数据库中. 实现: 1.聚合数据百度POI接口说明 调用聚合数据,首先得注册聚合.聚合数据提供的百度地 ...
- 根据关键字获取高德地图poi信息
根据关键字获取高德地图poi信息 百度地图和高德地图都提供了根据关键字获取相应的poi信息的api,不过它们提供给普通开发者使用的次数有限无法满足要求.其次百度地图返回的poi中位置信息不是经纬度,而 ...
- iOS地图集成示例:百度地图POI检索
一.集成百度地图(傻瓜教程,以网站说明文档为准,此处罗列几项主要步骤) 1.登录 http://lbsyun.baidu.com 百度地图开发者平台,获取SDK和集成文档. 2.百度地图可以提供的 ...
- 百度地图POI爬取
我们研究生的课程内容,做下笔记记录一下. 使用的python环境是python3.7 用的图大部分都是老师ppt里的图,懒得自己截了-- 申请百度开发者密匙 (1)注册百度用户,注册过的话,直接登录就 ...
随机推荐
- C++笔记(0)——判定一个数字是否是素数
博主之前使用的编程语言是Python,但是这门语言的效率比较低(通常,不优化的情况下,但是即便如此我还是偏爱Python),而且博主打算参加PAT考试(真正的原因),及博主打算顺便深入学习下机器学习框 ...
- 2-django配置
一.settings.py配置 1.时区配置 现在看到的界面是英文的,将 LANGUAGE_CODE = 'en-us' 改为 LANGUAGE_CODE = 'zh-Hans '就可以看到如下界面 ...
- python3抓取中国天气网不同城市7天、15天实时数据
思路:1.根据city.txt文档来获取不同城市code2.获取中国天气网7d和15d不同城市url3.利用requests库请求url获取html内容4.利用beautifulsoup获取7d和15 ...
- Python基础数据类型str字符串
3.3字符串str ' ' 0 切片选取 [x:y] 左闭右开区间 [x:y:z] 选取x到y之间 每隔z选取一次(选取x,x+z,....) z为正 索引位置:x在y的左边 z为负 索引位置:x在y ...
- gym102201F_Fruit Tree
题意 给一棵带权树,多次询问路径上出现次数超过一半的数. 分析 dfs序建主席树,维护的就是根到某个节点这段路径的值域情况. 因为题目所求的不是一般的众数,而是出现次数大于一半的,所以在主席树上可以直 ...
- RateLimit--使用guava来做接口限流
转:https://blog.csdn.net/jiesa/article/details/50412027 一.问题描述 某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不 ...
- ubuntu 安装mysql5.7
一.Windows mysql5.6 解压版 安装 关于widnows平台上的安装教程,可参考百度经验: 链接:https://jingyan.baidu.com/article/f3ad7d0ffc ...
- 创建Django项目的过程
1.创建Django项目根目录 a.命令式创建法:Django-admin startproject 项目名称 b.pycharm创建法:如下图 2.配置setting环境 a.配置静态文件 STAT ...
- 从分析攻击方式来谈如何防御DDoS攻击
DDoS攻击的定义: DDoS攻击全称——分布式拒绝服务攻击,是网络攻击中非常常见的攻击方式.在进行攻击的时候,这种方式可以对不同地点的大量计算机进行攻击,进行攻击的时候主要是对攻击的目标发送超过其处 ...
- 关于ARM PC值
PC值(Program Counter). ARM采用流水线来提高CPU的利用效率, 对于三级流水线, 一条汇编指令的执行包括 取值, 译码, 执行三个阶段. 当MOV指令的取指动作完毕后, 进入M ...