在这篇博文中,笔者从实验的角度,从爬数据的困难出发,阐述如何从Google Map上爬地图数据。本文的出发点为实验,而非商用。Google Map对其自己的数据具有其权益,希望读者以博文为学习实验之用,不要将自己所爬到的数据用于商用。如果因为此类事件所引起的纠纷,笔者概不负责。笔者也希望,大家在看到此博文后,能够进一步改进其数据的安全性。

笔者在实验室某个GIS项目中必须需要一定数据级的地图数据。在百般无奈下,笔者开始从Google Map爬数据。从Google Map上采集一定量的数据有作实验。

从Google Map爬数据的原理

Google Map所采用的是Mercator坐标系。何为Mercator坐标系?读者可以详见{链接}。在Google Map也是以金字塔模型的方式来组织切图文件的。至于,它的后端处理或者存储方式或者文件命名方式是怎么样,笔者不得而知。笔者只能从URL等方面进行分析,大概确定其地图文件的组织方式。在金字塔模型中,地图分成若干层,每一层数据的分辨率为上层的4倍(横向与纵向各2倍)。同时,每一层数据的分辨是极其巨大,而且成指数形式增加。如果一下子,将一层的数据作为一个文件返回给用户,无论从网络的传输能力、CPU处理能力还是内存的存储能力而言都是无法做到的。而且用户所观看的只是地图的某一层的某一块区域。因而,一般都会将地图数据进行切图,即进行切分,将地图数据切成分辨率相等的若干块。因而,我们可以得知,每一层数据集的文件数为上层的4倍。

笔者使用GoogleChrome来查看Google Map的Resources,图如下:

我们可以清楚地看到,在Google Map的地图文件并不是一次加载一整张,而是分成若干块,每一块的分辨为256*256。同时,我们也得到了每一块地图的地址,例如http://mt0.google.com/lyrs=m@176000000&hl=zh-CN&src=app&x=1&y=1&z=1&s=Ga.png。其中x、y是决定文件左上角坐标的参数,z为决定文件层次的参数。通过向Google Map服务器请求,我们可以得到第0层具有1块。从而第level层,具有2^level*2^level块,即x、y的取值范围为[0,2^level-1]。第level层每一块数据的横向经度差为360/2^level,纵向纬度差为180/2^level。

x=0&y=0&z=0
x=0&y=0&z=1 x=1&y=0&z=1
x=0&y=1&z=1 x=1&y=1&z=1

我们可以得知,x=xx,y=yy,z=zz的这块数据,所在的图层为zz层,该图层中每块数据的经度差为360/2^zz,纬度差为180/2^zz,左上角的经纬度为(360/2^zz*xx-180, 180/2^zz*yy-90)。同样,我们也可从一个数据块的左上角经纬度反推出这个文件在zz层的x与y。这也就是我们从Google Map爬数据的原理。

从Google Map爬数据有何难点?

1.    在国内由于政治等原因,连接Google服务器会有所中断。

2.    Google的Web服务器,或者Google防火墙,会对某一台客户端的请求进行统计。如果一段时间内,请求数超过一定的值,此后的请求会直接被忽略。据说,当一天中,来自某一个IP的请求数超过7000个时,此后的请求后直接被忽略。

3.    单线程操作的效率太低,多线程情况下,效率会有很大提升。

4.    Google服务器会对每个请求检查,判断是否来自浏览器还是来自爬虫。

5.    对于已下载的文件无须下载,即爬虫必须拥有“断点续传”的功能。不能由于网络的中断或者人为的中断,而导致之前的进度丢失。

对于这些难点有何解决方案

1.    对于第1点难点,我们可以使用国外的服务器作为我们的代理。这样,我们通过国外的服务器来请求Google Map。而对于大名鼎鼎的GFW而言,我们连接的并不是Google的服务器,而是其它的服务器。只要那台服务器没有被墙,我们就可以一直下载。

2.    对于第2个难点,我们依然可以使用代理。一旦,下载失败,这个代理ip可能已经被Google Map所阻拦,我们就需要更换代理。如果,代理的连接速度较慢,或者代理的下载文件时,超时较多,可能我们目前所使用的代理与我们的机器之间的网络连接状态不佳,或者代理服务负载较重。我们也需要更换代理。

3.    单线程操作的效率太低,我们需要使用多线程。但是,在使用多线程时,由于每一个文件的大小都很小,因而我们设计多线程机制时,每一个线程可以负责下载若干个文件。而不同的线程所下载的文件之间,没有交集。

4.    对于第4点,我们可以在建立http连接时,设置”User-Angent”,例如:

[java] view plaincopyprint?
  1. httpConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
[java] view plaincopyprint?
  1. httpConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");

5.    对于第5点,我们可以在每下一个文件之间,事先判断文件是否已经完成。这有很多种解决方法,笔者在这里,采用file.exists()来进行判断。因为,对于下载一个文件而言,检查文件系统上某一个文件的代价会小很多。

改进与具体实现

1.    代理的获取

代理的获取有很多种方式。但如果一开始就配置所有的代理,那么,当这些代理都已经无法使用时,系统也将无法运行下去。当然,我们也不想那么麻烦地不断去更换代理。笔者是一个lazy man,所以还是由计算机自己来更换代理吧。笔者在此使用www.18daili.com。www.18daili.com会将其收集到代理已web的形式发布出来。因而,我们可以下载这张网页,对进行解析,便可以得最新可用的代理了。笔者在这里使用Dom4J来进行网页的解析。

2.    架构

其中,分成三个模块:Downloader, DownloadThread, ProxyConfig。Downloader负责初化化线程池以存放DownloaderThread。每一个DownloadThread都会负责相应的若干个切图数据的下载。DownloadThread从ProxyConfig那里去获取代理,并从文件系统中检查某一个文件是否已经下载完成,并将下载完成文件按一定的规则存储到文件系统中去。ProxyConfig会从www.18daili.com更新现有的代理,在笔者的系统,每取1024次代理,ProxyCofig就会更新一次。

原码

Downloader:

[java] view plaincopyprint?
  1. package ??;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. public class Downloader {
  5. private static int minLevel = 0;
  6. private static int maxLevel = 10;
  7. private static String dir = "D:\\data\\google_v\\";
  8. private static int maxRunningCount = 16;
  9. private static int maxRequestLength = 100;
  10. public static void download() {
  11. ExecutorService pool = Executors.newFixedThreadPool(maxRunningCount);
  12. for (int z = minLevel; z <= maxLevel; z++) {
  13. int curDt = 0;
  14. int requests[][] = null;
  15. int maxD = (int) (Math.pow(2, z));
  16. for (int x = 0; x < maxD; x++) {
  17. for (int y = 0; y < maxD; y++) {
  18. if (curDt % maxRequestLength == 0) {
  19. String threadName = "dt_" + z + "_" + curDt;
  20. DownloadThread dt = new DownloadThread(threadName, dir, requests);
  21. pool.execute(dt);
  22. curDt = 0;
  23. requests = new int[maxRequestLength][3];
  24. }
  25. requests[curDt][0] = y;
  26. requests[curDt][1] = x;
  27. requests[curDt][2] = z;
  28. curDt++;
  29. }
  30. }
  31. DownloadThread dt = new DownloadThread("", dir, requests);
  32. pool.execute(dt);
  33. }
  34. pool.shutdown();
  35. }
  36. public static void main(String[] strs) {
  37. download();
  38. }
  39. }
[java] view plaincopyprint?

DownloadThread:

[java] view plaincopyprint?
  1. package ??;
  2. import java.io.BufferedInputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileOutputStream;
  6. import java.io.InputStream;
  7. import java.net.HttpURLConnection;
  8. import java.net.Proxy;
  9. import java.net.URL;
  10. import java.text.SimpleDateFormat;
  11. import java.util.Date;
  12. public class DownloadThread extends Thread {
  13. private static int BUFFER_SIZE = 1024 * 8;// 缓冲区大小
  14. private static int MAX_TRY_DOWNLOAD_TIME = 128;
  15. private static int CURRENT_PROXY = 0;
  16. private String threadName = "";
  17. private String dir;
  18. // private int level;
  19. private String tmpDir;
  20. private Proxy proxy;
  21. private int[][] requests;
  22. private String ext = ".png";
  23. private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  24. public DownloadThread(String threadName, String dir, int[][] requests) {
  25. this.threadName = threadName;
  26. this.dir = dir;
  27. this.requests = requests;
  28. }
  29. @Override
  30. public void run() {
  31. Date now = new Date();
  32. System.out.println(dateFormat.format(now) + "\t" + threadName + ":\t开始运行");
  33. long t1 = System.currentTimeMillis();
  34. long totalLength = download();
  35. long t2 = System.currentTimeMillis();
  36. double speed = (double) totalLength / (t2 - t1);
  37. now = new Date();
  38. if (speed < 0.5) {
  39. CURRENT_PROXY++;
  40. }
  41. System.out.println(dateFormat.format(now) + "\t" + threadName + ":\t完成运行\t" + speed + "kB/s");
  42. }
  43. public long download() {
  44. long totalLength = 0;
  45. if (requests == null) {
  46. return 0;
  47. }
  48. //System.out.println(requests.length);
  49. for (int i = 0; i < requests.length; i++) {
  50. int yy = requests[i][0];
  51. int xx = requests[i][1];
  52. int zz = requests[i][2];
  53. int yyg = (int) (Math.pow(2, zz) - 1 - requests[i][0]);
  54. this.tmpDir = dir + "/tmp/" + zz + "/";
  55. File tmpDirFile = new File(tmpDir);
  56. if (tmpDirFile.exists() == false) {
  57. tmpDirFile.mkdirs();
  58. }
  59. String dirStr = dir + "/download/" + zz + "/" + yy + "/";
  60. File fileDir = new File(dirStr);
  61. if (fileDir.exists() == false) {
  62. fileDir.mkdirs();
  63. }
  64. String fileStr = dirStr + yy + "_" + xx + ext;
  65. File file = new File(fileStr);
  66. // double lat1 = (yy) * dDegree - 90;
  67. // double lat2 = (yy + 1) * dDegree - 90;
  68. String url = "http://mt0.google.com/vt/lyrs=m@174000000&hl=zh-CN&src=app&x=" + xx + "&y=" + yyg + "&z=" + zz
  69. + "&s=";
  70. // System.out.println(url);
  71. if (file.exists() == false) {
  72. String tmpFileStr = tmpDir + yy + "_" + xx + ext;
  73. boolean r = saveToFile(url, tmpFileStr);
  74. if (r == true) {
  75. totalLength += cut(tmpFileStr, fileStr);
  76. Date now = new Date();
  77. System.out.println(dateFormat.format(now) + "\t" + threadName + ":\t" + zz + "\\" + yy + "_" + xx + ext + "\t"+proxy+"\t完成!");
  78. } else {
  79. Date now = new Date();
  80. System.out.println(dateFormat.format(now) + "\t" + threadName + ":\t" + zz + "\\" + yy + "_" + xx + ext + "\t"+proxy+"\t失败!");
  81. }
  82. } else {
  83. Date now = new Date();
  84. System.out.println(dateFormat.format(now) + "\t" + threadName + ":\t" + zz + "\\" + yy + "_" + xx + ext + "已经下载!");
  85. }
  86. }
  87. return totalLength;
  88. }
  89. public static long cut(String srcFileStr, String descFileStr) {
  90. try {
  91. // int bytesum = 0;
  92. int byteread = 0;
  93. File srcFile = new File(srcFileStr);
  94. File descFile = new File(descFileStr);
  95. if (srcFile.exists()) { // 文件存在时
  96. InputStream is = new FileInputStream(srcFileStr); // 读入原文件
  97. FileOutputStream os = new FileOutputStream(descFileStr);
  98. byte[] buffer = new byte[1024 * 32];
  99. // int length;
  100. while ((byteread = is.read(buffer)) != -1) {
  101. // bytesum += byteread; //字节数 文件大小
  102. // System.out.println(bytesum);
  103. os.write(buffer, 0, byteread);
  104. }
  105. is.close();
  106. os.close();
  107. }
  108. srcFile.delete();
  109. return descFile.length();
  110. } catch (Exception e) {
  111. System.out.println("复制单个文件操作出错");
  112. e.printStackTrace();
  113. }
  114. return 0;
  115. }
  116. public boolean saveToFile(String destUrl, String fileName) {
  117. int currentTime = 0;
  118. while (currentTime < MAX_TRY_DOWNLOAD_TIME) {
  119. try {
  120. FileOutputStream fos = null;
  121. BufferedInputStream bis = null;
  122. HttpURLConnection httpConnection = null;
  123. URL url = null;
  124. byte[] buf = new byte[BUFFER_SIZE];
  125. int size = 0;
  126. // 建立链接
  127. url = new URL(destUrl);
  128. // url.openConnection(arg0)
  129. currentTime++;
  130. proxy = ProxyConfig.getProxy(CURRENT_PROXY);
  131. //if (proxy != null) {
  132. //  System.out.println(threadName + ":\t切换代理\t" + proxy.address().toString());
  133. //} else {
  134. //  System.out.println(threadName + ":\t使用本机IP");
  135. //}
  136. if (proxy == null) {
  137. httpConnection = (HttpURLConnection) url.openConnection();
  138. } else {
  139. httpConnection = (HttpURLConnection) url.openConnection(proxy);
  140. }
  141. httpConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
  142. httpConnection.setConnectTimeout(60000);
  143. httpConnection.setReadTimeout(60000);
  144. // 连接指定的资源
  145. httpConnection.connect();
  146. // 获取网络输入流
  147. bis = new BufferedInputStream(httpConnection.getInputStream());
  148. // 建立文件
  149. fos = new FileOutputStream(fileName);
  150. // System.out.println("正在获取链接[" + destUrl + "]的内容;将其保存为文件[" +
  151. // fileName + "]");
  152. // 保存文件
  153. while ((size = bis.read(buf)) != -1){
  154. //  System.out.println(size);
  155. fos.write(buf, 0, size);
  156. }
  157. fos.close();
  158. bis.close();
  159. httpConnection.disconnect();
  160. // currentTime = MAX_TRY_DOWNLOAD_TIME;
  161. break;
  162. } catch (Exception e) {
  163. //e.printStackTrace();
  164. CURRENT_PROXY++;
  165. }
  166. }
  167. if (currentTime < MAX_TRY_DOWNLOAD_TIME) {
  168. return true;
  169. } else {
  170. return false;
  171. }
  172. }
  173. }
[java] view plaincopyprint?

ProxyConfig:

[java] view plaincopyprint?
  1. package org.gfg.downloader.google.vctor;
  2. import java.net.InetSocketAddress;
  3. import java.net.Proxy;
  4. import java.net.Proxy.Type;
  5. import java.net.URL;
  6. import java.net.URLConnection;
  7. import java.util.ArrayList;
  8. import java.util.Iterator;
  9. import java.util.List;
  10. import org.dom4j.Document;
  11. import org.dom4j.Element;
  12. import org.dom4j.io.SAXReader;
  13. public class ProxyConfig {
  14. private static List<Proxy> proxies;
  15. private static int getTime = 0;
  16. @SuppressWarnings("unchecked")
  17. public static void inital() {
  18. // if (proxies == null) {
  19. proxies = null;
  20. proxies = new ArrayList<Proxy>();
  21. // } else {
  22. // proxies.clear();
  23. // }
  24. try {
  25. URL url = new URL("http://www.18daili.com/");
  26. URLConnection urlConnection = url.openConnection();
  27. urlConnection.setConnectTimeout(30000);
  28. urlConnection.setReadTimeout(30000);
  29. SAXReader reader = new SAXReader();
  30. // System.out.println(url);
  31. reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
  32. Document doc = reader.read(urlConnection.getInputStream());
  33. if (doc != null) {
  34. Element root = doc.getRootElement();
  35. Element proxyListTable = getElementById(root, "proxyListTable");
  36. // System.out.println(proxyListTable.asXML());
  37. Iterator<Element> trs = proxyListTable.elementIterator();
  38. trs.next();
  39. while (trs.hasNext()) {
  40. Element tr = trs.next();
  41. Iterator<Element> tds = tr.elementIterator();
  42. String ip = tds.next().getText();
  43. String port = tds.next().getText();
  44. // System.out.println(ip+":"+port);
  45. Proxy proxy = new Proxy(Type.HTTP, new InetSocketAddress(ip, Integer.valueOf(port)));
  46. proxies.add(proxy);
  47. System.out.println("添加代理\t" + proxy);
  48. }
  49. }
  50. } catch (Exception e) {
  51. // e.printStackTrace();
  52. }
  53. }
  54. private static Element getElementById(Element element, String id) {
  55. Element needElement = null;
  56. Iterator<Element> subElements = element.elementIterator();
  57. while (subElements.hasNext()) {
  58. Element subElement = subElements.next();
  59. String getId = subElement.attributeValue("id");
  60. if (getId != null && getId.equals(id)) {
  61. needElement = subElement;
  62. break;
  63. } else {
  64. needElement = getElementById(subElement, id);
  65. if (needElement != null) {
  66. break;
  67. }
  68. }
  69. }
  70. return needElement;
  71. }
  72. synchronized public static Proxy getProxy(int i) {
  73. getTime++;
  74. if (getTime % 1024 == 0 || proxies == null) {
  75. inital();
  76. getTime = 0;
  77. System.out.println("重新生成代理列表!");
  78. System.out.println("当前共有" + proxies.size() + "个代理!");
  79. }
  80. if (i % 8 == 0) {
  81. return null;
  82. }
  83. int index = i % proxies.size();
  84. index = Math.abs(index);
  85. return proxies.get(index);
  86. }
  87. public static void main(String... str) {
  88. inital();
  89. }
  90. }
[java] view plaincopyprint?

如需转载,请标明出处:http://blog.csdn.net/JairusChan

从Google Map爬数据相关推荐

  1. 教你如何从Google Map爬数据

    在这篇博文中,笔者从实验的角度,从爬数据的困难出发,阐述如何从Google Map上爬地图数据.本文的出发点为实验,而非商用.Google Map对其自己的数据具有其权益,希望读者以博文为学习实验之用 ...

  2. 教你如何从Google Map爬数据(切片)

    转:http://blog.csdn.net/JairusChan 在这篇博文中,笔者从实验的角度,从爬数据的困难出发,阐述如何从Google Map上爬地图数据.本文的出发点为实验,而非商用.Goo ...

  3. google map 离线数据

    1.固件必须是14948及以上版本5 2. 支持离线地图 的googlemap 5.0  版本下载地址 : 网盘下载: http://u.115.com/file/t678d5b927 3.地图软件 ...

  4. Python 爬取 Google Map POI

    Google Map 的 API文档:https://developers.google.com/maps/documentation/places/web-service/search#PlaceS ...

  5. AMap + echarts、google map + d3.js分别实现数据可视化中的飞线图(迁徙图)

    首先肯定是给出demo啦: 演示demo 直接到左侧选择框中选择View taxi flow里面随便选个日期 总体介绍 最近由于工作室项目需要做一个数据可视化平台,这个平台最终是交由国外人使用的.而国 ...

  6. Android特色开发之Google MAP

    本文节选于机械工业出版社推出的<Android应用开发揭秘>一书,作者为杨丰盛.本书内容全面,详细讲解了Android框架.Android组件.用户界面开发.游戏开发.数据存储.多媒体开发 ...

  7. python爬虫怎么爬同一个网站的多页数据-如何用Python爬数据?(一)网页抓取

    如何用Python爬数据?(一)网页抓取 你期待已久的Python网络数据爬虫教程来了.本文为你演示如何从网页里找到感兴趣的链接和说明文字,抓取并存储到Excel. 需求 我在公众号后台,经常可以收到 ...

  8. Google Map API 的基础使用

    因为公司业务由国内市场到国际市场,有一些国际性业务的项目需要用到Google Map.项目完成后,把一些常用的方法写出来,供大家参考. 一.google地图基础显示 (1)引用google map j ...

  9. Google Map API V3开发(5)

    Google Map API V3开发(1) Google Map API V3开发(2) Google Map API V3开发(3) Google Map API V3开发(4) Google M ...

最新文章

  1. UNIX下C语言的图形编程-curses.h函数库
  2. 下c语言按q退出_Linux vim编辑器怎么退出?第一次用都会问的问题
  3. memcpy和strcpy的区别及memcmp和strcmp的区别
  4. 用Linux命令行生成随机密码的十种方法
  5. 端口隔离配置命令、端口镜像(抓包配置)详解(附图,建议PC观看)
  6. CentOS:linux开放指定端口命令
  7. XStream 反序列化漏洞 (CVE-2020-26258 26259) 的复现与分析
  8. python装饰器详解-python装饰器详解
  9. 百度指数邀请序列号_腾讯第8款社交AppHood 上线;抖音内测云游戏、直播回放等新功能;百度推出多人视频社交App一起吧| 产品挖掘机...
  10. 反编译exe软件_挖洞经验 | Panda反病毒软件本地提权漏洞分析
  11. TopJui 我踩的那些坑
  12. 网上支付跨行清算系统与大小额支付系统有什么区别?
  13. [Shader2D]浮雕效果
  14. 图像处理 图像识别 模式识别 分类检测
  15. 第六天 黑马十次方 用户注册、用户登陆掌握js-cookie、微信扫码登陆、nuxt嵌套路由
  16. 使用ASMap构建高密度遗传图谱
  17. 广电网络电视怎么服务器连接中断,怎么解决广电网络看电视卡
  18. spd耗材管理流程图_医用耗材SPD管理模式详解
  19. SATA协议OOB随笔
  20. win10计算机跑分,Win11对比Win10系统性能测试,看完跑分结果还等发布会吗?

热门文章

  1. Axure源rp文件-线上电竞竞技赛事游戏直播比赛平台web
  2. DirectShow摄像头采集
  3. 内燃机汽车发动机工作原理-飞轮的作用
  4. python的knn算法list_机器学习实战学习笔记1——KNN算法
  5. 进入游戏后如何回到计算机界面,玩LOL胜利后点击“继续游戏”要等个将近10秒才能回到结算界面,请问是怎么回事?...
  6. 简单的excel表格格式整理
  7. Oracle表空间的管理方式(LMT、DMT)--本地和字典管理
  8. html中怎么让字自动变颜色,html如何设置单个字体颜色
  9. 国内银行应用软件项目外包模式探讨(转)_manok_新浪博客
  10. 【算法讲18:二次剩余】勒让德符号 | 欧拉判别法 | Cipolla 算法