百度地图街景图片爬取

您所在的位置:网站首页 怎样下载街景地图 百度地图街景图片爬取

百度地图街景图片爬取

2024-07-13 12:59| 来源: 网络整理| 查看: 265

1 需求

       抓取整个杭州市的百度/腾讯街景地图及其时光机功能(实时图片和历史图片),进行图像分析。

2 分析

       百度地图街景模式下,点击向前可发现,街景图片是异步加载的,我们可以打开百度地图的街景模式,f12打开开发者模式,清空所有响应,并点击向前,可以看到产生了很多的图片请求

在这里插入图片描述 2.1 街景request简要分析

       本文以杭州市余杭区文一西路海创园附近处(由西向东)的街景为例仔细分析这些请求的作用:        首先,第一条请求:

https://mapsv0.bdimg.com/?qt=qsdata&x=13361258.73664768&y=3518572.1440338427&time=201709&mode=day&px=1336124156&py=351856780&pz=14.89&roadid=eeaa41-bd79-2cf9-3491-2ca35d&type=street&action=0&pc=1&auth=8Nv8G8DBLbO2F1vLvHNAgXOTz4UFPbxHuxHLxBLVHTRt1qo6DF%3D%3DCcvY1SGpuztAFwWv1GgvPUDZYOYIZuVt1cv3uHxtOmm0mEb1PWv3GuxNVt%3DErpTgZp1GHJMP6V8%40aDcEWe1GD8zv7u%40ZPuVteuEthjzgjyBKOBEEUWWOxtx77INHu%3D%3D8x35&udt=20190619&fn=jsonp.p30899897

       此请求也可简化为(为简便,以下请求均不带auth参数,不影响结果获取):

https://mapsv0.bdimg.com/?qt=qsdata&x=13361258.73664768&y=3518572.1440338427&time=201709&mode=day

       作用:根据地图上点击的位置,生成百度地图坐标x和y值,再得到服务器的json响应,(如下id即为该位置的百度街景ID,也就是后续要用到的panoID) 在这里插入图片描述        第二条请求:

https://mapsv0.bdimg.com/?qt=sdata&sid=09025200121709031616142855K&pc=1

       作用:该请求包含了panoId参数,返回该位置的街景相关信息(附近panoID,以及该位置的历史图片,拍摄时期等等),下文会详细分析。json响应如下图所示: 在这里插入图片描述        第三条请求:

https://mapsv0.bdimg.com/?qt=guide&sid=09025200121709031616142855K&fn=jsonp29109114

       作用: 获取该位置附近的公司或学校。json响应如下图所示: 在这里插入图片描述        第四条请求:

https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=0_0&z=1&udt=20190619

       作用: 该请求返回该位置街景的全景图片。如下所示: 在这里插入图片描述        第五条请求:

https://mapsv0.bdimg.com/?qt=pr3d&fovy=35&quality=80&panoid=09025200121709031616142855K&heading=72.801&pitch=0&width=198&height=108

       作用:该请求返回一个当前视角的小尺寸图片,如图所示: 在这里插入图片描述        其中的参数解释,网上找到了一个图片,有些参数未亲自验证。 在这里插入图片描述        由于小尺寸图片放大之后比较模糊,所以不对其进行获取。

       第六条请求(这是一组大图请求):

https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=1_4&z=4 https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=2_4&z=4 https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=1_5&z=4 https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=2_5&z=4 https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=1_6&z=4 https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=2_6&z=4 https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=1_7&z=4 https://mapsv0.bdimg.com/?qt=pdata&sid=09025200121709031616142855K&pos=2_7&z=4

在这里插入图片描述        作用:获取从左至右的街景大图,其中参数pos=1_*表示高角度(就是我们需要获取的),从左至右一共是4张(高角度),这样就拼成了我们在百度街景中所看到的一整张大图;pos=2_*表示低角度。

       因此我们要得到一座城市、一条街道的街景图片,首先要获取该城市、该街道所有位置的街景ID,然后通过模拟上述图片请求即可,但是如何获取杭州市所有街道的街景ID呢?        网上查了些资料,有人提出的想法是:

1、暴力循环panoID,错误就忽略,正确就返回结果。2、在一条道路寻找一个种子panoID,然后爬取整条道路的所有图片。3、根据百度地图的坐标,设置一个区域,遍历整个区域的所有坐标,正确就返回panoID,错误就不处理。

       粗略一看感觉很有道理,但仔细一分析就知扯淡! 首先,第一条,暴利循环相当低效,由于百度没有提供整条街道的经纬度接口,也没有提供获得整条街道的所有panoID值的接口(至少我没找到),所以如果根据网友的意思,在给定起始panoID情况下循环试错获取下一个panoID需要发送上万次、数十万次请求,很可能没获取到下一个ID就已经被百度封IP了,第三条,我确实想设置一个区域,但如何遍历该区域所有坐标呢?如何保证街景图片是同一条街道且连续的呢?有大牛想到解决办法的话烦请告知!再来看第二条,似乎可取,重点是如何获取下一个panoID。答案还是在上述"请求二"中。

2.2 请求2详细分析

       我们可以看到,Roads[0].Panos标签下,包含有多个panoID,我们可以在街景模式下点击向前10米,浏览器又加载了下个panoID(09025200121709031616155125K)的图片,这里有一个规律,向前20米左右会加载当前panoID的下下个panoID图片,以此类推。 在这里插入图片描述        当再点击向前,加载到panoID为09025200121709031616211215K的图片时,我们再看它的"请求二"的json响应如下图所示: 在这里插入图片描述        如图所示,Roads[0]为当前位置节点信息(IsCurrent: 1),Roads是一个数组,通常会有多个元素作为候选节点,这里仅有一个,即Roads[1],Roads[1].ID和Links[0].RID对应,继续点击向前,发现加载的正是我们从图中所见的panoID:09025200121709031616224035K。我们再观察09025200121709031616224035K这个位置的"请求二"响应,看到又有当前位置下的前向panoID集合了。我们再放心大胆地尝试几次点击,发现是有这个规律。紧锁的眉头渐渐舒缓,是时候喝杯咖啡了。在这里插入图片描述        coffee归来,思路再捋一捋。由上,如何根据给定的起始panoID爬取所在街道的所有街景图片? 业务逻辑流程梳理大致如下:

选择合适的起始panoID(可将其存入配置文件或数据库);根据panoID拼接上述"请求二",并发送请求获取响应;json解析该响应,获取当前位置附近的前向panoID集合;遍历该集合,拼接每个位置的图片下载链接(4张图片)并下载;当遍历到集合最后一个元素时,还需解析Roads数组中的非当前元素,并将其对应到Links数组中的panoID,我们姑且称之为锚节点;用该锚节点的panoID代入STEP 2中,这样整个逻辑就形成了递归函数。 2.3 图片存储

       在这里可以根据不同的业务需求采用不同的数据库,关系型或非关系型均可。某种程度上非关系型列式存储可能更好,因为当我们爬取一个城市所有街道的街景时,有的位置有历史数据,有的则没有,因此大规模数据集下可利用列式存储的弹性伸缩实现数据的高效存取。但考虑到后续图像分析时,可能需要建立空间连续(离散)位置的图像模型,这又更适合采用关系型数据库来存取。以mysql与Hbase为例,再回顾一下典型的关系型数据库和非关系型数据库之间的区别:

2.3.1 HBase与MySQL的区别 属性HBaseMySQL存储按列存储,可灵活增加列,列为空时不占存储空间按行存储伸缩扩展性支持需要第三方中间层支持高并发读写支持不支持条件查询只支持按rowkey查询支持数据类型字符串类型多种类型数据操作只有查询、插入、删除、清空等还包括各种连接操作等数据更新实际是插入新的数据,多版本替换修改 2.3.2 二进制流的存储

       下载几张图片看下大小,然后决定选用何种字段类型,经比较分析,得如下要点:

图片转为二进制字节流表中图片字段设为blob型

       MySQL 存储blob型数据可参阅此处

3 代码

       对应上述业务逻辑,编写不同步骤的代码

3.1 建表及读表记录

       建表,并将起始panoID存入数据库。建表语句如下:

CREATE TABLE `test`.`baidu_pano` ( `id` int(11) NOT NULL AUTO_INCREMENT, `pano_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '街景ID', `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '街道名称', `lati` double(32, 0) NULL DEFAULT NULL COMMENT '纬度', `lonti` double(32, 0) NULL DEFAULT NULL COMMENT '经度', `direction` bit(1) NULL DEFAULT NULL COMMENT '0:由西向东;1:由东向西', PRIMARY KEY USING BTREE (`id`) ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'InnoDB free: 30720 kB' ROW_FORMAT = Compact;

       插入panoID如图所示: 在这里插入图片描述        在coding之前先在builder.gradle文件中一并引入所有依赖:

ext { commonsDbutilsVersion = '1.6' druidVersion = '1.0.18' mysqlConnectorVersion = '5.1.37' } dependencies { compile "com.cetiti.ddc:ddc-core:0.1.11-alpha1" compile "commons-dbutils:commons-dbutils:${commonsDbutilsVersion}" compile 'org.apache.logging.log4j:log4j-core:2.8.2' compile "com.alibaba:druid:${druidVersion}" compile "mysql:mysql-connector-java:${mysqlConnectorVersion}" compile 'org.postgresql:postgresql:42.2.5' testCompile 'junit:junit:4.9' compile 'org.apache.commons:commons-pool2:2.4.2' compile 'redis.clients:jedis:2.9.0' } // 除此之外,还要引入snakeyaml-1.18.jar、fasjson、okhttp等jar包,对应版本可以自行搜索,本项目因依赖了公司自研的jar包,所以未显式说明

       我们需编写一个从MySQL读取记录并封装结果集的工具类MySQLHelper ,源码如下:

public class MySQLHelper { private static final Logger logger = Logger.getLogger(MySQLHelper.class); private static QueryRunner runner; private static ResultSetHandler h = new ResultSetHandler() { @Override public HashMap handle(ResultSet rs) throws SQLException { if (!rs.next()) { return null; } ResultSetMetaData meta = rs.getMetaData(); int cols = meta.getColumnCount(); HashMap result = new HashMap(16); for (int i = 0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3