一、前置概念
1.1 web墨卡托投影
目前主流的互联网地图服务,如Google Map、Microsoft Bing等,均采用web墨卡托投影进行投影。它以整个世界范围,赤道作为标准纬线,本初子午线作为中央经线,两者交点为坐标原点,向东向北为正,向西向南为负。
1.2 瓦片地图
经过上述方法投影后,球形地球变为一个平面地图。为了能够以不同的精度观察地图,就需要对地图的不同等级进行切分。在不同的切分等级,用不同像素的地图表示。例如在最高等级(zoom=0)时,用一张256256的图像就能表达所有信息,但是在下一级时,需要表达更多信息,就需要512512像素的图像,级别越低像素越高。而把每张图切分256*256的图片,就称之为瓦片。
1.3 瓦片编号
瓦片编号即对每一个瓦片的编号,不同厂商的编号方式不同。以谷歌XYZ为例,其z表示缩放等级, XY的原点在左上角,X从左向右,Y从上向下。如下图

二、搜索接口
1、搜索功能

在执行搜索操作时,其访问的url如下
示例:https://www.google.com.hk/maps/search/kfc/@22.2936158,114.1992499,12z
https://www.google.com.hk/maps/search/{搜索关键词}/@{x},{y},{zoom}z
- 关键词:在搜索框输入的关键词,即搜索内容
- x:纬度,取值范围为[-85.05112877980659, 85.05112877980659],其范围与谷歌地图采用的web墨卡托投影的表示范围有关
- y:经度,取值范围为[-180, 180]。该经度和纬度表示的是当前地图中心的坐标(猜测)。
- zoom:缩放级别,取值范围为[2,21]
2、搜索接口
执行搜索操作时,其访问的接口如下
https://www.google.com/search?tbm={}&hl={}&gl={}&pb={}&q={}&oq={}&gs_l={}&tch={}&ech={}&psi={}
其部分必要参数如下:
| 参数 | 含义 |
|---|---|
| tbm | 搜索类别,如map为谷歌地图,vid为谷歌视频等 |
| hl | 语言,中文为zh-CN |
| gl | 当前所在国家或地区缩写,可选 |
| pb | 里面还有很多参数 |
| q | 搜索关键词 |
| oq | 也是关键词?可选 |
| gs_l | 地理位置,可选 |
| tch | 含义未知,但会影响返回数据的格式,不影响结果内容 |
其中pb为payload,其格式为类似!XXxxx!XXxxxx。这个参数中比较重要的参数如下
| 参数 | 含义 |
|---|---|
| !1d{} | 地图比例尺,缩放倍数越大该值越小,详见下文说明。 |
| !2d{} | 当前屏幕地图中心点的经度,取值范围为[-180, 180](即web墨卡托投影的投影范围) |
| !3d{} | 当前屏幕地图中心点的纬度,取值范围为范围[-85.05112877980659, 85.05112877980659](即web墨卡托投影的投影范围);搜索会围绕这个点位置展开 |
| !8i{} | 该字段的实际含义为返回的搜索结果中的第一个结果的索引,从0开始,每次返回20个查询结果。即!8i0意为返回的第1个结果是所有搜索结果中的第1个,共计返回第1-20条结果,!8i20意为返回的第1个结果是所有搜索结果中的第21个,总计返回21-40条结果。当该字段值大小超过搜索的所有结果时,该接口会返回空数据或!8i0时的数据。当不存在该字段时,默认返回!8i0时的数据。 |
| !7i{} | 每次请求返回多少条数据。网页端默认情况下为20条,但是可以自行更改,控制每次请求返回多少条结果 |
在执行请求时,应当加入UA,否则返回的数据将会不全
2.1 关于!1d字段含义的说明
该字段的值会受到经纬度(即!2d和!3d)和缩放级别(即上述瓦片地图的缩放级别)的影响。在经纬度不变的情况下,缩放级别每增加1,该字段值就会减少1/2。缩放级别越大,该字段值越小。相同纬度和缩放等级下,越靠近两极,该字段值越小,两极对称纬度(例如50和-50)值相同。
不同相邻整数缩放级别的瓦片地图的比例尺相差刚好为1/2,如下图所示。且受到墨卡托投影地图形变的影响,越靠近两极,在地图上的表示就越大(例如在地图上格陵兰岛的大小和非洲差不多,实际上二者面积相差巨大)。所以在墨卡托投影的地图上,不同位置的比例尺是不一样的,越靠近两极,单位像素所代表的地理距离就越小。目前主流的谷歌地图爬虫对于该字段的使用方式都是将该字段设置为定值。

\frac {cos(\frac {latitude*pi}{180})*2*pi*6378137.0*dpi}{256*2^{zoom}*0.0254}
而实际上,地图比例尺的计算公式为:
\frac{GroundResolution*dpi}{0.0254}
而地面分辨率的计算公式为:
GroundResolution=\frac{cos(\frac{latitude*pi}{180})*2*pi*6378137.0}{256*2^{zoom}}
经验证,算法方法正确,但与谷歌地图的实际值有固定倍数的差别,目前猜测原因为计算使用的dpi不同。在真正爬取时,只要保持dpi和缩放级别就可以了,否则可能会对搜索结果造成一定影响。
三、谷歌地图爬取流程
流程图

流程解释
用户输入
为了爬取谷歌地图,用户需要输入地理位置和搜索关键词。随后脚本会根据用户输入的地理位置来搜索其所在的关键词的结果。
地点检索经纬度
根据上文对接口的描述,需要提交的参数有经纬度坐标,所以需要对这些参数进行生成计算。
目前根据地名检索获取经纬度有两种方案。
第一种方案为正常执行搜索操作,获取地点经纬度。根据用户输入的地理位置,在谷歌地图执行一次搜索操作以获取到其所在的经纬度坐标。因搜索时就需要经纬度坐标,但经验证,在较小缩放的情况下,执行常规搜索操作,任意一个经纬度坐标都可以搜索到唯一地名(如果地名确实存在的话),搜索的地名可以精确到镇级地名。但当用户输入的地名不存在或并非实际行政区划名称时,谷歌地图可能同样可以搜索到其他非相应地点的结果,此时就会无法判断用户输入的地名是否存在,无法判断获得的经纬度是否正确。
第二种方案为调用谷歌的地点接口获取经纬度,例如https://www.google.com.hk/maps/place/上海/ ,其中地点可以更改为其他地点。请求该接口会直接返回一个html页面,其中包含有该地区经纬度坐标的信息,如下图所示(但下图所示位置在搜索不到地名的情况下会出现根据当前位置的经纬度,只有能够搜索到地点才会返回对应地点的经纬度,可以根据下面其他字段联合判定)。这种搜索方式经测试用户给定非行政区划名称则无法搜索到相关地点,搜索结果只会出现唯一的一个地点。但经测试这种方式可能无法搜索到部分镇级地名。

目前发现第二种方案存在一些问题:
1、在请求某些同名地点时结果会不准确
2、某些地名在网页端可以搜索到但是通过这种方式并搜索不到
所以地点搜索采用如下方法:
使用上文介绍过的搜索接口进行搜索,但是paylaod中的!2m0需要修改为!2m3。此时搜索并不会使用payload中给定的经纬度和比例尺,故赋0即可。如果地点存在,会返回一个唯一的地点,和网页中搜索效果一致。
但这中方法的问题就是无法获取到谷歌提供的默认比例尺,需要再通过如下方式组合出该地点的url进而获取比例尺(和上述第二种方案相同)
https://www.google.com.hk/maps/place/{关键词}/@0,0,0z/data=!4m5!3m4!1s{地点代码}!8m2!3d{纬度}!4d{经度}
其他参数生成
根据上文对接口的描述,还有地图比例尺参数需要提供,而地图比例尺可以通过经纬度计算得到。
地图比例尺即上文中!1d参数。其需要通过纬度、缩放级别和屏幕dpi计算得到。因为缩放级别和屏幕dpi主要为网页端的参数,其会影响网页端地图的可视范围,所以在爬虫过程中并不需要刻意输入这两个字段,只需要取合适的定值,再根据上述公式使用纬度计算即可。(缩放级别会影响搜索结果,所以需要取一个合适的缩放级别来检索)
在地图比例尺也计算完成后,就可以使用经纬度、地图比例尺来带入组合payload了。payload可以使用真实pc设备访问网页,抓取请求得到,将对应位置的更换即可。
执行请求
在首次请求时,可以不考虑page参数,其会返回首页数据。但如果搜索有更多结果,就需要考虑翻页问题,就需要对page参数进行更改。page参数位于payload的!8i参数中,其实际含义是返回结果的第一条数据是结果总数的第page条。因每页返回的数据条数为!7i,所以请求下一页数据时,page应该为(!8i+1)*!7i。当请求的page超过结果总数时,其会返回第一页数据或是空数据。当所有的页面都遍历完后,就可以检索其他数据了。
结果解析
在搜索流程完成后,就可以对结果进行解析。通过接口得到的数据内容要多于网页端显示的数据。其返回值是多层list结构,其中较为有价值的数据有名称、地理位置、经纬度坐标、电话、评分等。其中数据位置在demo脚本中。
当前解析到的搜索结果数据字段如下:
| 数据 | 含义 | 备注 |
|---|---|---|
| name | 名称 | 每个地点都有 |
| address | 详细地址 | 每个地点都有 |
| url | 地点的url链接 | 无明显类别区分,是否有该字段取决于低地点和谷歌是否收录,海外收录多于大陆 |
| category | 地点类别 | 冷门地点没有,国内(大陆)收录较少,国外几乎都有 |
| rate | 评分 | 冷门地点没有,国内收录较少 |
| comment | 评论数 | 冷门地点没有,国内收录较少 |
| price | 价格等级(昂贵、不算贵、廉价等) | 大陆几乎没有收录,海外有购物功能(非收费性企业机构)的地点部分有收录(约40%-50%) |
| latlot | 经纬度 | 每个地点都有 |
| phone | 手机号(带国家码,如果不带直接用另一个字段) | 无明显类别区分,是否有该字段取决于低地点和谷歌是否收录,海外收录多于大陆 |
| workstatus | 当前营业状态 | 海外大部分有收录,大陆收录较少 |
| area | 该地点所在区域 | 每个地点都有 |
| zip | 邮编 | 海外大部分地点有,大陆少于海外 |
| worktime | 营业时间(周一到周日) | 海外大部分地点都有,大陆较少 |
| previewcode | 代码,可以用来请求预览信息接口 | 每个地点都有,但字段用于开发,而并非展示给用户 |
| introduce | 景点介绍 | 只有部分景点和少数其他地点(例如大学、酒店)才有 |
| coverimg | 搜索结果展示的图片url | 大部分地点都有,通过url可以改变图片大小,该字段目前返回最大图像。url为拼接而成,拼接规则为https://lh5.googleusercontent.com/p/{返回结果中的图片编码}=w{返回结果中图片的最大宽度}-h{返回结果中图片的最大高度}-k-no,通过修改w和h后的数字可以修改返回图片大小 |
| preview_url | 地点预览url | 每个地点都有,点击后会跳转到地点详情,也就是点击搜索结果出现的页面,拼接而成,拼接规则为https://www.google.com.hk/maps/preview/place/{搜索结果中带有详细地址的地点名称}/@{纬度},{经度},13.1y/data=!4m2!3m1!1s{previewcode} |
| show_img | 预览页面展示的封面图片 | 大部分地点有,类似搜索结果展示图,同样返回为最大图片,需要使用详情接口额外爬取。拼接而成,拼接规则同coverimg,只不过数据取自预览接口返回结果 |
| user_search | “用户还搜索了”中的地点代码 | 该字段作为开发用,用户使用不到该字段,且需要使用详情接口额外爬取 |
| comment_url | 评论url,点击后会跳转到谷歌搜索页面并弹出一个评论展示框 | 是否拥有取决于地点 |
| qa_url | 问答url,点击会跳转到一个单独的页面展示问答内容 | 每个地点都有,但并非每个地点中都有内容,是否有内容取决于地点。拼接而成,故每个地点都有。拼接规则为https://www.google.com.hk/local/place/qa/question/{previewcode}?&hl=zh-CN |
四、谷歌地图图片与视频爬取
照片获取
照片获取流程
1、直接请求接口即可
接口说明
请求方法: GET
参数:
hl:语言,中文为zh-CN,与谷歌地图搜索接口相同
gl:地区,例如台湾为tw,香港为hk等
!1s{}:通过谷歌地图搜索接口得到的地点代码,即 0x0000000:0x0000000 形式的字符串,需要url编码,需要把{}替换为地点代码
!2i{}:需要返回的图片数量,网页中默认为20,可以指定更多,需要把{}替换为需要返回的图片数量
返回值
返回结果形式类似谷歌地图搜索,需要先去掉前面的)]}’,然后解析为json。
其中json[0]为存储返回所有图片的列表,图片最佳分辨率url的构成规则如下(其中n指代图片列表的第n张图片)
https://lh5.googleusercontent.com/p/{json[0][n][0]}=w{json[0][n][6][2][0]}-h{json[0][n][6][2][1]}-k-no
其中json[0][n][0]为第n张图片的图片id,json[0][n][6][2][0]为第n张图片的图片宽度,json[0][n][6][2][1]为第n张图片的图片高度,这样可以的到最佳分辨率的图片。
如果需要求改图片分辨率,把url中=w和=h后的数字改为其他数值即可
视频获取
视频获取流程
1、调用图片获取接口,获取视频封面图片id
2、使用封面图片id调用photometa接口,获取图片信息
3、解析返回结果,获取视频各分辨率链接
4、处理链接重定向,得到视频真实地址
5、比较分辨率,去除最高分辨率视频
视频封面获取接口
与上文图片获取接口为同一接口,但部分参数不同
参数:
hl:语言,中文为zh-CN,与谷歌地图搜索接口相同
gl:地区,例如台湾为tw,香港为hk等
!1s{}:通过谷歌地图搜索接口得到的地点代码,即 0x0000000:0x0000000 形式的字符串,需要url编码,需要把{}替换为地点代码
!2i{}:需要返回的图片数量,网页中默认为20,可以指定更多,需要把{}替换为需要返回的图片数量
注意payload后面部分与上述图片获取接口不同
返回值:
需要先去掉前面的)]}’,然后解析为json。去除上述图片接口中解析出来的图片id——json[0][n][0]即可得到每一张图片的图片id,用于下面获取photometa。
demo脚本中使用的方法为更换了payload后正常调用图片获取接口,然后使用string.split("=")[0].split("/")[-1]分割图片接口返回的图片url,得到组合在其中的photoid,实际索取字段是相同的
photometa接口
接口url:www.google.com/maps/photometa/v1?authuser=0&hl={}&pb=!1m4!1smaps_sv.tactile!11m2!2m1!1b1!2m2!1sen!2s!3m3!1m2!1e10!2s{}!4m57!1e1!1e2!1e3!1e4!1e5!1e6!1e8!1e12!2m1!1e1!4m1!1i48!5m1!1e1!5m1!1e2!6m1!1e1!6m1!1e2!9m36!1m3!1e2!2b1!3e2!1m3!1e2!2b0!3e3!1m3!1e3!2b1!3e2!1m3!1e3!2b0!3e3!1m3!1e8!2b0!3e3!1m3!1e1!2b0!3e3!1m3!1e4!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e10!2b0!3e3
参数:
hl:语言,中文为zh-CN,与谷歌地图搜索接口相同
!2s:上述视频封面获取接口得到的图片id
返回值:
删除掉返回数据中的)]}’,然后解析为json。
json[1][0][2][10][1]为存放当前视频封面所对应视频各分辨率信息的list。其中
视频宽度为json[1][0][2][10][1][n][1]
视频高度为json[1][0][2][10][1][n][2]
视频url(需要重定位)为json[1][0][2][10][1][n][3]
其中n表示视频各分辨率信息list中的第n各元素项。
这样就能拿到视频封面所对应视频的各个分辨率
返回结果中一部分视频url可能为空字符串,需要过滤掉,只保留拥有视频url的部分
重定位处理
上述photometa接口获取到的视频url需要经过重定位才能拿到真正的视频url。
使用get请求请求该url,取返回报头中的Location字段值即为视频原始url
分辨率比较
根据视频获取接口所得到的视频长和宽,使用任意一种,比较去除最大的即为最佳分辨率的视频链接。
参考资料
- 瓦片地图原理 – SegmentFault 思否
- 墨卡托投影mygisforum的博客-CSDN博客墨卡托投影公式
- 墨卡托投影介绍Archie_java的博客-CSDN博客web墨卡托投影
- GitHub – iewoai/GoogleMap_Spider: [iewoai]爬取谷歌地图的商业信息(requests版)
- GitHub – BillyNI-OUO/smart-gym-crawler
- GitHub – cartier55/Google-Maps-Scrapper: Google maps scrapper for undocumented api
- GitHub – GooGrand/tasks-on-php: Several tasks
- (转)GIS理论(墨卡托投影、地理坐标系、地面分辨率、地图比例尺、Bing Maps Tile System) – 走看看
- Google Maps缩放级别对应什么比例比例?
- We need a Map.getScale() method


