百度地图POI爬取(JavaScript语言篇,以武汉市商圈为例)

前言(不搭后语,推荐略过不看): 毕业设计选了个《通过百度地图POI分析城市商圈结构特征》的课题,我作为一个熟(jian)练(dan)使(cao)用(zuo)Echarts的前端开发工(cheng)程(xu)师(yuan),自然免不了来一发数据可视化,那么问题来了,数据在哪儿弄?当然是买买买啦,啊噗,咋可能,作为一名未来优秀的IT人士,当然是选择用双手成就梦想,自己爬多有成就感是吧,还能装个*。那么问题又来了,爬虫程序千千万,python语言占一半,可我不会啊···不会就学呗,换个语法而已,编程语言不都一通百通吗(一股傲气)~~十分钟后……这语法,,这空格回车,,这,,字四啥子鬼东西哦,转手就创建了一个Java Class,再转念一想,老夫即以决绝入前端,不写JavaScript对得起自己吗??随手关掉IDEA,打开了notepad++,然后就开始了JS的爬数据之旅。

正文:

简介:

需要的数据:武汉市主要城市商圈附近各类(衣食住行娱服务)POI点数据
使用语言及版本:JavaScript(ECMAScript 2015+ 即 ES6)
开发环境:Windows 10,Chrome 64,Notepad++

开发过程:

1.选择所需要爬取POI的商圈,及其中心的地理坐标。
附:使用百度地图API,便推荐使用百度地图自身的坐标拾取系统,因为不同框架或网站所使用的投影偏移量是有一些差异的(若非此原因还请告知),如天地图、Google Map等与BMap共同拾取一点得出的数据都是不同的。
链接:

  1. 百度地图坐标拾取系统
  2. 百度地图商圈范围查看系统
    *:坐标拾取值受个人主观影响
    {name:"街道口商圈",coord:[114.360126,30.532832]},
    {name:"光谷商圈",coord:[114.405651,30.511968]},
    {name:"中南商圈",coord:[114.34052,30.545552]},
    {name:"徐东商圈",coord:[114.350659,30.595902]},
    {name:"江汉路商圈",coord:[114.293171,30.590613]},
    {name:"武广商圈",coord:[114.277289,30.586198]},
    {name:"汉正街商圈",coord:[114.281734,30.577378]},
    {name:"王家墩商圈",coord:[114.253693,30.606261]},
    {name:"钟家村商圈",coord:[114.272573,30.554955]},
    {name:"王家湾商圈",coord:[114.214917,30.567876]},

2.确定半径,使用API
本文采用1500M为圆周半径,API文档:百度地图 JavaScript API
大概过程:①初始化BMap对象(注意需要DOM容器) ②实例化BMap.LocalSearch对象 ③调用searchNearby方法即可进行查询 详细过程不作表述,take is cheap,show you code later。
附:百度地图JS开发需要申请Key,过程此处略,有需要请参阅文档中开发指南内的入门指南,有详细步骤解读。

3.编写代码
对百度地图POI数据有一定了解的人可能都会知道官方为了防范恶意爬虫,对单次请求所能爬取的数量是有限制的(此刻为760条),但是如果我们的半径范围比较大,查询的关键词匹配点又比较多,760条肯定不够用,怎么解决呢?解决方案就是切割范围,所谓切割范围就是当前区域数据量超过了760条,那就把该区域切成几块,之后再将结果相加,此方案比较容易想到,也比较常用。不过要用此方案,再用圆形区域就不合适了,因此我们只能选择用矩形进行查询与切割,所以商圈坐标点之类的就得改成左下与右上的坐标,方法也要换成searchInBounds。

嗯,,,最后的代码大概就这吊样,我尽可能加了注释·~· 选择的范围某些比较大,一定程度上影响了获得结果的速度,不过由于请求都是异步发送的,所以最终速度主要由最慢的一个请求决定,因此别太急

<!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">
    html,body{
        height:100%;
        margin:0;
        padding:0;
    }
</style>
</head> 
<body>
<div id="container" style="display:none;"></div>    <!-- 地图元素不用,隐藏即可 -->
<div id="main" style="width:100%;height:100%;"></div>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=k74dnzzNHEmOsCkzTCxVrKhEH62vcVYP"></script>
<script type="text/javascript">
//变量、常量等声明在前以便修改
const wh_pois = [           //武汉市几大商圈及其大致区域,范围选取受主观影响
    {name:"街道口商圈",coord:[[114.349305,30.521108],[114.365187,30.535137]]},
    {name:"光谷商圈",coord:[[114.392249,30.505847],[114.421785,30.51767]]},
    {name:"中南商圈",coord:[[114.326778,30.539281],[114.352002,30.54998]]},
    {name:"徐东商圈",coord:[[114.344079,30.590915],[114.360751,30.599868]]},
    {name:"江汉路商圈",coord:[[114.291582,30.581122],[114.30308,30.591817]]},
    {name:"武广商圈",coord:[[114.269789,30.583361],[114.281539,30.597444]]},
    {name:"汉正街商圈",coord:[[114.278643,30.574976],[114.285533,30.579376]]},
    {name:"王家墩商圈",coord:[[114.256422,30.592439],[114.268927,30.59819]]},
    {name:"钟家村商圈",coord:[[114.262135,30.539296],[114.28118,30.560632]]},
    {name:"王家湾商圈",coord:[[114.208677,30.562645],[114.236884,30.571103]]},
];
const keywords = ["服装店","餐馆","酒店","公交站","网吧","银行"];    //查询关键词,对应 衣 食 住 行 娱 服务
window.onload = function(){
    const map = new BMap.Map("container");         //初始化一个地图元素
    map.centerAndZoom(new BMap.Point(114.32, 30.57), 14); 
    getData(map);
};
//顶层函数声明推荐不要使用ES6的箭头函数,因为箭头函数打穿作用域会导致你在函数内部一层作用于的this指向全局
function getData(map){
    const arrs = [];  //总存储
    let once = true; // 只输出一次
    //下一段代码涉及到了循环内部嵌类递归,逻辑可能比较复杂,后期我尝试使用进行优化,不知道Promise是否可行
    for(let q=0;q<wh_pois.length;q++){
        //注意这里推荐使用let或者const,不能用var,涉及到作用域问题
        //arr:存储数据的数组  maxPage:结果最多的条件的页面总数  maxI:记录结果最多的条件的下标位置  isMuch:判断是否大于760
        let [arr,maxPage,maxI,isMuch] = [[],0,0,false];
        arr.address = wh_pois[q].name;
        const options = {      
            //这个大方法如果是实际业务开发最好细化拆分一下,一块代码太长是很不好的,这里是为了方便查看逻辑
            onSearchComplete (results){      
                if (local.getStatus() === 0){      // 判断状态是否正确 0是BMAP_STATUS_SUCCESS的值
                    for(let j=0;j<results.length;j++){
                        if(results[j].getNumPois() === 760){  //判断数据量是否大于760,大于760则进行切割
                            isMuch = true;                            
                            break;
                        }else{
                            isMuch = false;
                        }
                    }
                    if(isMuch){
                            const oldBounds = results[0].bounds;
                            /*计算切割中心经度,要按纬度切也是没问题的。
                            这里的Ge Le Ke Fe就是两个端点的坐标由于bounds没有提供getX()之类的方法,就只能直接debug看属性名了*/
                            const halfLon = Number(((oldBounds.Ge+oldBounds.Le)/2).toFixed(6));
                            // 查询左区域
                            local.searchInBounds(keywords, new BMap.Bounds(new BMap.Point(oldBounds.Le,oldBounds.Ke),
                                new BMap.Point(halfLon,oldBounds.Fe)));
                            //查询右区域
                            local.searchInBounds(keywords, new BMap.Bounds(new BMap.Point(halfLon,oldBounds.Ke),
                                new BMap.Point(oldBounds.Ge,oldBounds.Fe)));
                    }else{
                        for(let i=0;i<results.length;i++){
                            !arr[i]?(arr[i] = {}):"";  //如果值存在就不用再初始化
                            arr[i].type = arr[i].type||results[i].keyword;
                            arr[i].pois = arr[i].pois?arr[i].pois.slice(0):[];
                            for(let k=0;k<results[i].getCurrentNumPois();k++){
                                arr[i].pois.push(results[i].getPoi(k))   //这样就能进行poi拼接了
                            }
                            arr[i].count = arr[i].pois.length;
                            maxPage = Math.max(maxPage,results[i].getNumPages());
                            if(maxPage <= results[i].getNumPages()){
                                maxI = i;
                            }
                        }
                        if(results[maxI].getPageIndex() < maxPage-1){  //该组数据未搜索完成
                            local.gotoPage(results[maxI].getPageIndex()+1);
                        }else if(results[maxI].getPageIndex() === maxPage-1){   //该组数据搜索完成
                            arrs.push(arr);
                        }
                        if (arrs.length === wh_pois.length){  //所有数据搜索完成
                          // 分段可能导致多次输出,延迟十秒等待第一份数组数据填充结束
                          if (!once) {
                            return false;
                          }
                          once = false;
                          setTimeout(()=>{saveData(arrs); }, 2000)
                        }
                    }

                } else{
                    //这里不写的时候也没啥,不过切割之后每次都有错误报出来(错误码5),对结果没用影响,产生原因有待研究·~·
                    console.warn("请求好像出了点问题,但不影响结果,错误码:"+local.getStatus())  
                }      
            },
            pageCapacity:50  //单页数据数量
        };
        const local = new BMap.LocalSearch(map, options); 
        const poiStart = new BMap.Point(wh_pois[q].coord[0][0], wh_pois[q].coord[0][1]);
        const poiEnd = new BMap.Point(wh_pois[q].coord[1][0], wh_pois[q].coord[1][1]);
        const bounds = new BMap.Bounds(poiStart, poiEnd)
        local.searchInBounds(keywords, bounds);
    }
}

function saveData(data){
  console.log(data);
}
</script>
</body>
</html>

嗯~ o( ̄▽ ̄)o数据篇就到此为止了,接下来是存储篇,我并不打算用熟悉的Java,而是籍此机会学习一下NodeJS的后台开发,存储篇预计没啥人感兴趣,我到时候看有没有什么值得记录的点再决定写不写出来。

猜你喜欢

转载自blog.csdn.net/qq_39300332/article/details/79552403