1. 程式人生 > >教你如何從Google Map爬資料(切片)

教你如何從Google Map爬資料(切片)

轉:http://blog.csdn.net/JairusChan

在這篇博文中,筆者從實驗的角度,從爬資料的困難出發,闡述如何從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/[email protected]

&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 plain copy
  1. httpConnection.setRequestProperty("User-Agent""Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");  
[java]  view plain  copy
  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 plain copy
  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. }  
[java]  view plain  copy
  1.    


DownloadThread:

[java]  view plain copy
  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. 相關推薦

    no