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

        在这篇博文中,笔者从实验的角度,从爬数据的困难出发,阐述如何从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爬数据(切片)_第1张图片

           我们可以清楚地看到,在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
教你如何从Google Map爬数据(切片)_第2张图片

x=0&y=0&z=1
x=1&y=0&z=1
教你如何从Google Map爬数据(切片)_第3张图片 教你如何从Google Map爬数据(切片)_第4张图片
x=0&y=1&z=1 x=1&y=1&z=1
教你如何从Google Map爬数据(切片)_第5张图片 教你如何从Google Map爬数据(切片)_第6张图片

           我们可以得知,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 plain copy print ?
  1. httpConnection.setRequestProperty("User-Agent""Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");  
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.    架构

教你如何从Google Map爬数据(切片)_第7张图片

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

原码

Downloader:

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


DownloadThread:

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

发布与运行效果

教你如何从Google Map爬数据(切片)_第8张图片

本博客中所有的博文都为笔者(Jairus Chan)原创。

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

你可能感兴趣的:(Google,WebGIS)