Python之爬取百度地图兴趣点(POI)数据

关于爬虫系列,前三篇文章分别讲了三个简单案例,分别爬取了《你好,李焕英》电影豆瓣热门短评58同城在售楼盘房源信息以及安居客网二手房小区详情页数据。通过前三个案例,相信大家都对爬虫有了简单了解和运用,对于大部分网站来说呢,爬虫的基本流程都相差不多,主要区别在于解析网站标签的xpath语句不同,俗话说得好,熟能生巧,多去尝试爬取不同网站,慢慢地就会熟练使用了。

这篇文章呢,主要来谈一谈如何利用Python调用百度地图API接口,将研究区域看成是一个矩形,以固定经纬度间隔划分网格,爬取百度地图上的兴趣点(Point of interest),获取的字段主要包括名称、纬度、经度、详细地址、省份、市以及区7个字段。 对于有些知识点,比如百度地图的APK密钥注册等,网上各大博客都有很好的讲解,这里我会放上参考链接,小伙伴们各取所需,本文重点主要放在POI数据爬取的代码上。好了,废话不多说,开始走起~

1. 在百度地图开放平台注册,获取AK密钥

在爬取POI数据之前,必须先在百度地图开放平台上完成注册拿到AK密钥,才能调用百度地图的API接口,关于这部分本文不作过多的详解,具体可以参考如下博客:

参考资料:零基础掌握百度地图兴趣点获取POI爬虫(python语言爬取)(基础篇)

 2. 获取石家庄市大致范围的经纬度

由于我们将研究范围看作是一个矩形,必然需要矩形的四个夹角经纬度,也就是说只要得到左下角和右上角经纬度,就可以大致知道地图所在位置。那么如何获取到左下角和右上角经纬度呢?在这篇博客零基础掌握百度地图兴趣点获取POI爬虫(python语言爬取)(进阶篇)中提及到一种方法,代码已经编好,只需将下面代码复制粘贴到txt文件中,然后修改文件后缀名为.html即可。

<!DOCTYPE html>  
<html>  
<head>  
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />  
    <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>

用本地浏览器打开这个html网页,输入研究的省市即可显示左下角和右上角经纬度,具体操作看下图:

所以我们可以得出:

lat_1 = 37.444122 # 石家庄市左下纬度
lon_1 = 113.529103 # 新石家庄市左下经度
lat_2 = 38.768031 # 石家庄市右上纬度
lon_2 = 115.486183 # 石家庄市右上经度

3. 获取URL

URL = "http://api.map.baidu.com/place/v2/search?query=政府& bounds=37.973948,114.382523,38.17371,114.714494&page_size=20&page_num=0&output=json&ak=xxxxx"

URL的各参数含义:① query:表示检索的POI类型;② bounds:采用矩形分割,依次分别为小网格的左下角纬度、左下角经度、右上角纬度、右上角经度;③ page_size:每页所能获取的最大记录数;④ page_num:页数;⑤ output:返回的是json数据;⑥ ak:从百度地图注册平台上申请到的密钥;

具体的参数详解,可以看下这篇博客:零基础掌握百度地图兴趣点获取POI爬虫(python语言爬取)(代码篇)

4. 爬取百度地图POI数据——代码详解

百度地图给出了两种获取POI的方式,一种是按照行政区域搜索,另一种是按照矩形框搜索

第一种,按行政区划检索POI,受到百度地图api的限制,最多开源读取20页数据,共计400条,所以这种方法比较适用于数量较小的POI爬取,不推荐在大的工程项目中使用。

第二种,按矩形框搜索,通过给定矩形框的左下角和右上角经纬度坐标,结合API进行爬取,爬取所需时间受两方面限制,一种是对于比较密集的POI,比如学校、超市这种,如果想尽可能将数据爬取完整,则需要缩小划分网格的经纬度间隔(las),这样就会增加爬取时间;另一种是,爬取所需时间还与检索的区域大小成正相关,检索区域越大的话,花费的时间就会越长;

(1)基本参数设置

lat_1 = 37.973948 #裕华区左下纬度
lon_1 = 114.382523 #新华区左下经度
lat_2 = 38.17371 #新华右上纬度
lon_2 = 114.714494 #长安区右上经度

las = 0.01  # 爬取时划分网格的经纬度间隔
ak='oieDykNvNYpiu7xe3tIuvFZmdfQQB4pt' # 根据自己在百度地图注册平台上申请的AK
place = r'政府' # 爬取的POI类型,可以是学校、政府、超市、商场、小区、餐饮店等等,这里以政府为例

(2)POI区域划分,按照经纬度间隔0.01度,将不同URL存入一个列表中

print ('*******************{}POI开始获取****************'.format(place))
urls=[] #声明一个数组列表
lat_count=int((lat_2-lat_1)/las+1)
lon_count=int((lon_2-lon_1)/las+1)
for lat_c in range(0,lat_count):
    lat_b1=lat_1+las*lat_c
    for lon_c in range(0,lon_count):
        lon_b1=lon_1+las*lon_c
        for i in range(0,20):
            page_num=str(i)
            url='http://api.map.baidu.com/place/v2/search?query='+place+'& bounds='+str(lat_b1)+','+str(lon_b1)+','+str(lat_b1+las)+','+str(lon_b1+las)+'&page_size=20&page_num='+str(page_num)+'&output=json&ak='+ak
            urls.append(url)
print ('url列表读取完成')

(3)为了方便看结果,这里设置些参数

total_before = 0
total = 0 # 获取总条数
label = 0 # 查看是否获取到数据的标志
count_20 = 0 # 查看每页超过20条的页数
K = 0 # 运行出错的断点数,代表第多少次循环

urls = urls[K:] # 防止出现意外,比如在百度地图获取的数据达到上限,就会终止服务,这里的K主要是为了从当前断点处继续爬取,所以需要根据自己程序终止断点设置;
count_xunhuan = len(urls) # 循环次数
count_xunhuan2 = count_xunhuan # 还剩循环次数,主要让自己知道大概有多少循环,每次都打印下,对程序运行时间有个大致了解;

(4)创建文件以及将爬取到的数据读入文件

f=open(r''+place+'.csv','a',encoding='utf-8') # 根据爬取的POI类型创建文件
print("+++++++++++爬取需循环{}次++++++++++++".format(count_xunhuan))
for url in urls:
    #time.sleep(10) # 为了防止并发量报警,设置了一个10秒的休眠。认证后就不需要了
    html = requests.get(url) # 获取网页信息
    data = html.json() # 获取网页信息的json格式数据
    total_before = total
    for item in data['results']:
        jname = item['name'] # 获取名称
        jlat = item['location']['lat'] # 获取纬度
        jlon = item['location']['lng'] # 获取经度
        jadd = item['address'] # 获取详细地址
        jpro = item['province'] # 获取所在省
        jcity = item['city'] # 获取所在城市
        jarea = item['area'] # 获取所在区或县
        j_str = jname + ',' + str(jlat) + ',' + str(jlon) + ',' + jadd + ',' + jpro + ',' + jcity + ',' + jarea + '\n' # 以逗号格式,将数据存入一个字符串
        f.write(j_str) # 将数据以行的形式写入CSV文件中
        total = total + 1 # 获取的数据记录数
        label = 1 # 表示每个小网格是否爬取到数据,如果为1,则表示获取到数据,执行下面的if语句,如果为0,则表示没有获取到数据;
    count_xunhuan2 = count_xunhuan2 - 1 # 循环次数减一,方便查看了解循环进度
    if label == 1:
        print("需循环{}次, 已循环{}次, 还剩{}次循环结束".format(count_xunhuan, count_xunhuan-count_xunhuan2, count_xunhuan2))
        print('新增{}条数据'.format(total-total_before))
        if total-total_before == 20:
                count_20 = count_20 + 1 # 查看获取到20条数据的页数,因为百度地图限制每页获取20条数据,如果该网格区域超过的话,也是爬取到20条,所以这里设置count_20查看下没有爬取完整的网格数,如果过多,则最好修改las经纬度间隔;
        print("---------已获取{}条数据----------".format(total))
    label = 0
print("每页新增超过20条的页数:{}".format(count_20))
f.close()
print ('*****************{}POI获取完成******************'.format(place))

5. 完整代码汇总

关于该案例的完整代码,见下,小伙伴们可以结合我写的注释共同食用;

#-*-coding:UTF-8-*-
import sys
import requests  #导入requests库,这是一个第三方库,把网页上的内容爬下来用的
ty=sys.getfilesystemencoding()  #这个可以获取文件系统的编码形式

## 1. 基本参数设置:

lat_1 = 37.444122 # 石家庄市左下纬度
lon_1 = 113.529103 # 新石家庄市左下经度
lat_2 = 38.768031 # 石家庄市右上纬度
lon_2 = 115.486183 # 石家庄市右上经度

las = 0.01  # 爬取时划分网格的经纬度间隔
ak='xxxxxxxx' # 根据自己在百度地图注册平台上申请的AK
place = r'政府' # 爬取的POI类型,可以是学校、政府、超市、商场、小区、餐饮店等等,这里以政府为例


## 2. POI区域划分,按照经纬度间隔0.01度,将不同URL存入一个列表中

print ('*******************{}POI开始获取****************'.format(place))
urls=[] #声明一个数组列表
lat_count=int((lat_2-lat_1)/las+1)
lon_count=int((lon_2-lon_1)/las+1)
for lat_c in range(0,lat_count):
    lat_b1=lat_1+las*lat_c
    for lon_c in range(0,lon_count):
        lon_b1=lon_1+las*lon_c
        for i in range(0,20):
            page_num=str(i)
            url='http://api.map.baidu.com/place/v2/search?query='+place+'& bounds='+str(lat_b1)+','+str(lon_b1)+','+str(lat_b1+las)+','+str(lon_b1+las)+'&page_size=20&page_num='+str(page_num)+'&output=json&ak='+ak
            urls.append(url)
print ('url列表读取完成')


## 3. 为了方便看结果,这里设置了些参数

total_before = 0 
total = 0 # 获取总条数
label = 0 # 查看是否获取到数据的标志
count_20 = 0 # 查看每页超过20条的页数
K = 0 # 运行出错的断点数,代表第多少次循环

urls = urls[K:] # 防止出现意外,比如在百度地图获取的数据达到上限,就会终止服务,这里的K主要是为了从当前断点处继续爬取,所以需要根据自己程序终止断点设置;
count_xunhuan = len(urls) # 循环次数
count_xunhuan2 = count_xunhuan # 还剩循环次数,主要让自己知道大概有多少循环,每次都打印下,对程序运行时间有个大致了解;


# 4. 创建文件以及将爬取到的数据读入文件

f=open(r''+place+'.csv','a',encoding='utf-8') # 根据爬取的POI类型创建文件
print("+++++++++++爬取需循环{}次++++++++++++".format(count_xunhuan))
for url in urls:
    #time.sleep(10) # 为了防止并发量报警,设置了一个10秒的休眠。认证后就不需要了
    html = requests.get(url) # 获取网页信息
    data = html.json() # 获取网页信息的json格式数据
    total_before = total
    for item in data['results']: 
        jname = item['name'] # 获取名称
        jlat = item['location']['lat'] # 获取纬度
        jlon = item['location']['lng'] # 获取经度
        jadd = item['address'] # 获取详细地址
        jpro = item['province'] # 获取所在省
        jcity = item['city'] # 获取所在城市
        jarea = item['area'] # 获取所在区或县
        j_str = jname + ',' + str(jlat) + ',' + str(jlon) + ',' + jadd + ',' + jpro + ',' + jcity + ',' + jarea + '\n' # 以逗号格式,将数据存入一个字符串
        f.write(j_str) # 将数据以行的形式写入CSV文件中
        total = total + 1 # 获取的数据记录数
        label = 1 # 表示每个小网格是否爬取到数据,如果为1,则表示获取到数据,执行下面的if语句,如果为0,则表示没有获取到数据;
    count_xunhuan2 = count_xunhuan2 - 1 # 循环次数减一,方便查看了解循环进度
    if label == 1:
        print("需循环{}次, 已循环{}次, 还剩{}次循环结束".format(count_xunhuan, count_xunhuan-count_xunhuan2, count_xunhuan2))
        print('新增{}条数据'.format(total-total_before))
        if total-total_before == 20:
                count_20 = count_20 + 1 # 查看获取到20条数据的页数,因为百度地图限制每页获取20条数据,如果该网格区域超过的话,也是爬取到20条,所以这里设置count_20查看下没有爬取完整的网格数,如果过多,则最好修改las经纬度间隔;
        print("---------已获取{}条数据----------".format(total))
    label = 0
print("每页新增超过20条的页数:{}".format(count_20))
f.close()
print ('*****************{}POI获取完成******************'.format(place))

6. 爬取的数据展示

从百度地图上获取到的POI数据,主要包括以下字段:POI名称、纬度、经度、详细地址、省份、市以及7个字段;数据展示如下:


好了,到这里为止,本文的所有内容就结束了。本文主要利用Python调用百度地图API接口,来爬取POI兴趣点数据,当初做练习的时候获取了政府、小区、餐饮店、KTV、医院、商场、超市、学校、写字楼等等POI数据;总体来说,本文的难度相对来说不大,主要在于理解上,因为这跟之前三篇爬虫案例有一些差别,体现在这次获取的是json格式的文件,不再是之前的html文件了,html网页文件需要通过xpath进行解析,而json文件中得数据都存储在字典中,通过遍历这些字典来获取对应的值;关于json文件中数据的获取,已经在代码里了,小伙伴们不懂得可以多看看代码理解下。

感谢小伙伴们看到这里,如果哪里写的不是很全面的地方,可以在评论区留言哦,我会不断完善的!如果小伙伴们对Python感兴趣的话,可以来波关注,后续还有很多干货呀!

本文的参考资料:

零基础掌握百度地图兴趣点获取POI爬虫(python语言爬取)(基础篇)

零基础掌握百度地图兴趣点获取POI爬虫(python语言爬取)(代码篇)

零基础掌握百度地图兴趣点获取POI爬虫(python语言爬取)(进阶篇)


                          来都来了,确定不留下点什么嘛,嘻嘻~

                                         

 

  • 42
    点赞
  • 223
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 23
    评论
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数分小白龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值