百度地图POI爬取
我们研究生的课程内容,做下笔记记录一下。
使用的python环境是python3.7
用的图大部分都是老师ppt里的图,懒得自己截了……
申请百度开发者密匙
(1)注册百度用户,注册过的话,直接登录就可以。登录地址为百度地图开放平台
(2)登录后,在控制台点击【创建应用】。
(3)填写表单,创建应用
(4)这样就得到了API Key了
百度地图搜索API语法
poi查询的基本url为http://api.map.baidu.com/place/v2/search?
按矩形框坐标范围检索
有如下参数
参数名 | 参数含义 | 类型 | 是否必须 |
---|---|---|---|
query | 检索关键字,周边检索和矩形区域检索 支持多关键字(以$隔开)并集检索,最多支持10个。 |
string(45) | Y |
bounds | 设置查询的坐标范围 矩形框的左下角经纬度和右上角经纬度。 |
string(50) | Y |
output | 输出格式为json或xml | string(50) | Y |
scope | 检索结果的详细程度 取值为1或空,返回基本信息;取值2,返回详细信息 |
string(50) | N |
page_size | 页面显示POI数量,默认值为10条,最大值为20 | int | N |
page_num | 分页页码,从0开始 | int | N |
coord_type | 坐标类型 1:WGS84即GPS经纬度坐标 2:国家测绘局GCJ-02坐标 3:bd09,即百度经纬度坐标 4:bd09mc即百度米坐标 |
int | N |
ret_coordtype | 返回国测局经纬度坐标 | string(50) | N |
ak | 开发者访问密钥(刚刚申请的API KEY) | string(50) | Y |
关于上面这个bounds的值,可以使用百度地图自己提供的坐标拾取器获得。也可以通过别人写的一个网页获得具体的行政区矩形框。
别人写的网页的html如下
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
body, html,#allmap {width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}
#panel{
position:absolute;
left:5px;
top:5px;
}
#result{
background: #fff;
padding:5px;
}
</style>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=1XjLLEhZhQNUzd93EjU5nOGQ"></script>
<title>添加行政区划</title>
</head>
<body>
<div id="allmap"></div>
<div id="panel">
<div>
<input type="text" id="keyword" value="昆明市"/>
<input type="button" value="查看范围" id="commitBtn"/>
边界经纬度坐标
<textarea id="pathStr"></textarea>
边界墨卡托坐标
<textarea id="pathMc"></textarea>
</div>
<div id="result">
</div>
</div>
</body>
</html>
<script type="text/javascript">
// 百度地图API功能
var map = new BMap.Map("allmap");
map.centerAndZoom(new BMap.Point(116.403765, 39.914850), 5);
map.enableScrollWheelZoom();
var mercatorProjection = map.getMapType().getProjection();
$("#commitBtn").bind('click', function(){
getBoundary($("#keyword").val());
});
function getBoundary(city){
var bdary = new BMap.Boundary();
bdary.get(city, function(rs){ //获取行政区域
map.clearOverlays(); //清除地图覆盖物
var count = rs.boundaries.length; //行政区域的点有多少个
if (count === 0) {
alert('未能获取当前输入行政区域');
return ;
}
var pointArray = [];
for (var i = 0; i < count; i++) {
var ply = new BMap.Polygon(rs.boundaries[i], {strokeWeight: 2, strokeColor: "#ff0000"}); //建立多边形覆盖物
map.addOverlay(ply); //添加覆盖物
pointArray = pointArray.concat(ply.getPath());
}
var pathStr = "";
var pathMc = "";
for (var i = 0; i < pointArray.length; i++) {
var mc = mercatorProjection.lngLatToPoint(pointArray[i]);
pathStr += pointArray[i].lng + "," + pointArray[i].lat + ";";
pathMc += mc.x + "," + mc.y + ";";
}
$('#pathStr').html(pathStr);
$('#pathMc').html(pathMc);
var ply = new BMap.Polygon(pointArray , {strokeWeight: 2, strokeColor: "#ff0000"}); //建立多边形覆盖物
var bounds = ply.getBounds();
var ne = bounds.getNorthEast();
var sw = bounds.getSouthWest();
var neMc = mercatorProjection.lngLatToPoint(ne);
var swMc = mercatorProjection.lngLatToPoint(sw);
var str = "经纬度:左下角,右上角:" + sw.lng + "," + sw.lat + ";" + ne.lng + "," + ne.lat
+ "<br/>墨卡托坐标:左下角,右上角:" + swMc.x + "," + swMc.y + ";" + neMc.x + "," + neMc.y;
$('#result').html(str);
console.log(bounds);
map.setViewport(pointArray); //调整视野
});
}
//getBoundary('北京');
</script>
打开后,类似这样
按城市检索
刚刚说的是按bound检索。也可以使用region参数按城市检索,比如说
http://api.map.baidu.com/place/v2/search?query=公园®ion=武昌区&output=json&ak=xxxxxxxxx&page_size=20&page_num=0
周边检索
使用location参数(设置中心点坐标)和radius参数(设置半径)进行周边检索。比如说
http://api.map.baidu.com/place/v2/search?query=酒店&location=30.531642,114.366409&radius=300&output=json&ak=xxxxxxxxxx&page_size=20&page_num=0
返回内容
返回的内容,status为状态(0为成功返回,其他为异常),message为提示信息,total为返回的poi数量,results为当前页的poi信息。
{
"status":0,
"message":"ok",
"total":57,
"results":[]
}
一般scope为1的poi信息如下
{
"name":"四美塘",
"location":{
"lat":30.603315,
"lng":114.344284
},
"address":"武汉市武昌区和平大道589号(长江二桥下)",
"province":"湖北省",
"city":"武汉市",
"area":"武昌区",
"street_id":"15a93810075519bd15c8f6b4",
"detail":1,
"uid":"f21f49135aad2a3ce856fad9"
}
一般scope为2的poi信息如下
{
"name":"四美塘",
"location":{
"lat":30.603315,
"lng":114.344284
},
"address":"武汉市武昌区和平大道589号(长江二桥下)",
"province":"湖北省",
"city":"武汉市",
"area":"武昌区",
"street_id":"15a93810075519bd15c8f6b4",
"detail":1,
"uid":"f21f49135aad2a3ce856fad9"
"detail_info":{
"tag":"旅游景点;公园",
"navi_location":{
"lng":114.34584347395,
"lat":30.604976470638
},
"type":"scope",
"detail_url":"http://api.map.baidu.com/place/detail?uid=f21f49135aad2a3ce856fad9&output=html&source=placeapi_v2",
"overall_rating":"4.3",
"comment_num":"14",
"children":[
]
}
}
百度地图POI搜索爬虫设计
由于百度地图限制每次返回的poi最多只有400个,所以我们要把矩形区域进行分割以获得更多的数据。
具体实现的代码如下
def splitArea(bound):
# 分割矩形区域的函数
boundList=[]
row_num = 2 # 按照 2 X 2 进行分割
bound=list(map(float,bound.split(',')))
step_lat = (bound[2] - bound[0]) / row_num
step_lon = (bound[3] - bound[1]) / row_num
for i in range(0,row_num):
for j in range(0,row_num):
boundTemp = []
boundTemp.append(bound[0]+step_lat*i)
boundTemp.append(bound[1]+step_lon*j)
boundTemp.append(bound[0]+step_lat*(i+1))
boundTemp.append(bound[1]+step_lon*(j+1))
boundTemp=",".join(["%s" %x for x in boundTemp])
boundList.append(boundTemp)
return boundList
课后作业完成
题目如下:
寻找武汉市中学(或小学)周围500米(或其他)内的网吧
要求:提交代码py文件及运行结果文件(txt),txt文件格式如下:
1,XXX小学
1-1,XXX网吧
1-2, XXX网吧
2,XXX小学
2-1,XXX网吧
2-2, XXX网吧
………..
写的python3代码为
# coding:utf-8
# version:python3.7
# author:Ivy
############# 程序功能 ####################
# 本程序用来获取百度地图某城市某种poi周围一定范围内的另一种类型的poi数据
# 如示例是获取武汉市中学周围500米内的网吧
# 生成的info.txt格式如下
# 1, xxx中学
# 1-1, xxx网吧
# 1-2, xxx网吧
# 2, xxx中学
# 2-1, xxx网吧
############################################
import requests,json
import time,sys
############### 自主设置区 ###############
ak = 'xxxxxxxxx' #API key
keyword="中学"
keyword2="网吧"
radius=500
city="武汉市"
baseBound = '29.972898,113.707695,31.367052,115.085775' #武汉市矩形框的左下角经纬度和右上角经纬度
############################################
# 构造header
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
'Accept-Encoding':'gzip, deflate',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'Host': 'api.map.baidu.com',
'Upgrade-Insecure-Requests': '1',
'Connection': 'keep-alive',
'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'DNT': '1'
}
baseUrl="http://api.map.baidu.com/place/v2/search?query={}&bounds={}&page_size=20&page_num={}&output=json&ak={}"
searchBaseUrl="http://api.map.baidu.com/place/v2/search?query={}&location={}&radius={}&output=json&ak={}&page_size=20&page_num={}"
def req(url):
# 访问url获取返回的数据读取为json
errorNum=0
while True:
try:
res = requests.get(url,headers=headers)
res.encoding='utf-8'
jd = json.loads(res.text)
return jd
except Exception as e:
errorNum+=1
print("出错了!")
print(e)
if errorNum==5:
sys.exit(1)
time.sleep(1)
def splitArea(bound):
# 分割矩形区域的函数
boundList=[]
row_num = 2 # 按照 2 X 2 进行分割
bound=list(map(float,bound.split(',')))
step_lat = (bound[2] - bound[0]) / row_num
step_lon = (bound[3] - bound[1]) / row_num
for i in range(0,row_num):
for j in range(0,row_num):
boundTemp = []
boundTemp.append(bound[0]+step_lat*i)
boundTemp.append(bound[1]+step_lon*j)
boundTemp.append(bound[0]+step_lat*(i+1))
boundTemp.append(bound[1]+step_lon*(j+1))
boundTemp=",".join(["%s" %x for x in boundTemp])
# 是否要继续分割
if check(boundTemp):
boundList.append(boundTemp)
else:
boundList+=splitArea(boundTemp)
return boundList
def check(bound):
# 检查一下当前矩形框是否需要再次分割
checkUrl=baseUrl.format(keyword,bound,0,ak)
checkJd=req(checkUrl)
if checkJd["total"]<400:
return True
return False
def getPois(bound):
# 获取一定范围内所有的想要的poi的信息
poiListTemp=[]
pageNum=0
while True:
getUrl=baseUrl.format(keyword,bound,pageNum,ak)
getJd=req(getUrl)
if getJd['results']==[]: # 如果这一页没数据了就是结束啦,退出循环
break
for result in getJd['results']:
if result["city"] != city: # 在矩形框中,但是不是想要的城市的,就不要记录啦
continue
poiListTemp.append(result)
pageNum+=1
print("当前区域{}的poi已爬完,共有{}个".format(bound,len(poiListTemp)))
return poiListTemp
def searchPois(poiInfo):
# 获取某个poi周边的其他poi信息
poiListTemp=[]
pageNum=0
while True:
searchUrl=searchBaseUrl.format(keyword2,str(poiInfo['location']['lat'])+','+str(poiInfo['location']['lng']),radius,ak,pageNum)
searchJd=req(searchUrl)
if searchJd['results']==[]:
break
for result in searchJd['results']:
poiListTemp.append(result["name"])
print(result["name"]," 已添加")
pageNum+=1
print(poiInfo["name"],"周边poi已爬完")
return [poiInfo["name"],poiListTemp]
if __name__ == '__main__':
# 分割想要的区域
boundList=splitArea(baseBound)
print(boundList)
# 获取范围内所有的poi信息
poiList=[]
for bound in boundList:
poiList+=getPois(bound)
print('已获得所有poi信息')
# 对于获取到的每个poi进行周边检索
infoList=[]
for poi in poiList:
infoList.append(searchPois(poi))
print('已获得所有周边poi信息')
# 把获取到的信息都写入txt
with open('info.txt','w') as f:
for i in range(len(infoList)):
f.write(str(i+1)+', '+infoList[i][0]+'\n')
for j in range(len(infoList[i][1])):
f.write(str(i+1)+'-'+str(j+1)+', '+infoList[i][1][j]+'\n')
print("所获得的信息已全部写入txt啦!")
【参考】
[1]我们老师的PPT(不便公开)