百度地图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 { 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=公园&region=武昌区&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]+'
')
            for j in range(len(infoList[i][1])):
                f.write(str(i+1)+'-'+str(j+1)+', '+infoList[i][1][j]+'
')
    print("所获得的信息已全部写入txt啦!")

【参考】

[1]我们老师的PPT(不便公开)

原文地址:https://www.cnblogs.com/IvyWong/p/11812412.html