教你如何從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]
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- 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. 架構
其中,分成三個模組:Downloader, DownloadThread, ProxyConfig。Downloader負責初化化執行緒池以存放DownloaderThread。每一個DownloadThread都會負責相應的若干個切圖資料的下載。DownloadThread從ProxyConfig那裡去獲取代理,並從檔案系統中檢查某一個檔案是否已經下載完成,並將下載完成檔案按一定的規則儲存到檔案系統中去。ProxyConfig會從www.18daili.com更新現有的代理,在筆者的系統,每取1024次代理,ProxyCofig就會更新一次。
原碼
Downloader:
[java] view plain copy- package ??;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class Downloader {
- private static int minLevel = 0;
- private static int maxLevel = 10;
- private static String dir = "D:\\data\\google_v\\";
- private static int maxRunningCount = 16;
- private static int maxRequestLength = 100;
- public static void download() {
- ExecutorService pool = Executors.newFixedThreadPool(maxRunningCount);
- for (int z = minLevel; z <= maxLevel; z++) {
- int curDt = 0;
- int requests[][] = null;
- int maxD = (int) (Math.pow(2, z));
- for (int x = 0; x < maxD; x++) {
- for (int y = 0; y < maxD; y++) {
- if (curDt % maxRequestLength == 0) {
- String threadName = "dt_" + z + "_" + curDt;
- DownloadThread dt = new DownloadThread(threadName, dir, requests);
- pool.execute(dt);
- curDt = 0;
- requests = new int[maxRequestLength][3];
- }
- requests[curDt][0] = y;
- requests[curDt][1] = x;
- requests[curDt][2] = z;
- curDt++;
- }
- }
- DownloadThread dt = new DownloadThread("", dir, requests);
- pool.execute(dt);
- }
- pool.shutdown();
- }
- public static void main(String[] strs) {
- download();
- }
- }
DownloadThread:
[java] view plain copy- package ??;
- import java.io.BufferedInputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.InputStream;
- import java.net.HttpURLConnection;
- import java.net.Proxy;
- import java.net.URL;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- public class DownloadThread extends Thread {
- private static int BUFFER_SIZE = 1024 * 8;// 緩衝區大小
- private static int MAX_TRY_DOWNLOAD_TIME = 128;
- private static int CURRENT_PROXY = 0;
- private String threadName = "";
- private String dir;
- // private int level;
- private String tmpDir;
- private Proxy proxy;
- private int[][] requests;
- private String ext = ".png";
- private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- public DownloadThread(String threadName, String dir, int[][] requests) {
- this.threadName = threadName;
- this.dir = dir;
- this.requests = requests;
- }
-
相關推薦
no