[Python爬蟲] Google Maps 地標與地址轉經緯度
前言
大家好,今天想介紹我如何用Python去抓出Google地圖上的經緯度。這個方法不會使用到Google地圖的API,也就是Geocoding API的功能。這篇文章的示範將使用Jupyter Notebook。我個人是讀機械系的,對於程式、網頁架構以及相關概念可能不是這麼清楚,若有解釋錯誤還請見諒與指教,本篇教學將會從頭到尾展示我所經歷的過程,如有需要可以自行選擇篇章觀看。
目的
起因是我朋友需要在地圖上繪製某地區的某產業的聚集程度,必須要取得該產業最小營業單位(店家)的座標,才能得到比較好的顯示效果。我們一開始得到的資料只有一串地址的文字檔,目標是要得到每個地址所屬的經緯度。
程式碼
1. 讀取檔案
將地址從txt檔讀取出來。為避免個資洩漏這邊只用10筆台灣常見地標作範例,方便後續做對照的原因,我們使用一半地名一半地址。以下要實驗並確認無論是用地名、地址、中文或是英文都可以達成目的。記得將以下的記事本與.ipynb檔放在同一個資料夾裡。
淡水紅毛城
台北101
國立交通大學
National Taichung Theater
Tainan Anping Castle
251新北市淡水區中正路28巷1號
111台北市信義區信義路五段7號
30010新竹市東區大學路1001號
407025台中市西屯區惠來路二段101號
708台南市安平區國勝路82號
將以下程式碼打在Jupyter Notebook裡,記得以utf-8開啟文字檔才不會出現亂碼。
#放在第一個cell
import codecsf = codecs.open("test_data.txt", 'r', 'utf-8') #記得以utf-8開啟文字檔
location = f.read()
f.close()
2. 將文字轉成網址
仔細觀察Google Maps的網址規則可以發現尋找後的網址如下都是在maps/place後加上地名或地址
https://www.google.com.tw/maps/place/地名或是地址
直接放上地址還好,但若直接加上地名很大機率會出問題。原因是地名不是一個確切的參數,像是單純地名的話就有台北和臺北兩種差別,找出來的東西就會不一樣。平常在Google Maps搜尋就算打錯字還是幾乎能找出來就像是Google會預測你要尋找的東西,但在網址內就無法了。為了解決這個問題,我們把網址改成如下
https://www.google.com/maps/place?q=地名或地址
如此便不會出現找不到地名的情況,而地址依然可以使用
#放在第二個cell
places = tuple(location.split('\n'))
URL = []
for i in places:
URL.append("https://www.google.com/maps/place?q=" + i)
3. 設定輸出檔案與格式
這邊從openpyxl引用Workbook的功能,可以將最後得到的經緯度以Excel格式輸出。在Excel中"A1"的欄位填上經度longitude,"B1"的欄位填上緯度latitude。
#放在第三個cell
from openpyxl import Workbookwb = Workbook()
ws = wb.active
ws['A1'] = 'longitude'
ws['B1'] = 'latitude'
如果顯示無法引用Workbook的話要先安裝openpyxl,打開Anaconda輸入
pip install openpyxl
4. 將字串轉成數字
爬蟲的原理是讀取該網頁的html後尋找需要的資料,找到的資料是字串string的型態。就我們的目的來說輸出到Excel檔並不需要去管經緯度是字串還是數字,但為了之後擴充的方便性(可能會直接在程式中對經緯度做運算)還是先寫好,方便日後可以再使用。在下個部份我們會解釋註1。
#放在第四個cell
def STR_to_NUM(data):
line = tuple(data.split(',')) #註1
num1 = float(line[1])
num2 = float(line[2])
line = [num1, num2]
return line
5. 爬蟲
現在要透過Beautiful Soup去得到的第2步驟中10個網址的html,我們先對一個網址進行解析,剩下的網址丟進迴圈就可以了。我們先用以下的code來看看這個頁面(Google Maps搜尋淡水紅毛城的結果)的html長什麼樣子。
#先放在第五個cell,之後刪除
import requests
from bs4 import BeautifulSoupresponse = requests.get(URL[0])
soup = BeautifulSoup(response.text, "html.parser")
text = soup.prettify()
print(text)
如果無法引用requests和BeautifelSoup的話要先安裝requests和BeautifelSoup
pip install requests
pip install beautifulsoup4
印出的內容如下
以上為部分的截圖。這裡可以找到所有關於這個網頁的資訊,現在我們要找出包含經緯度的部分在哪裡。找的方法很簡單直覺,直接使用Ctrl+F去找跟經緯度有關的資料在那裡就好了。
首先注意到Google Maps的網址,在淡水紅毛城後@的數字便是地標的緯度和精度。
接著在Jupyter Notebook中尋找(Ctrl+F),輸入25.1753。此時可以看到這段html裡包含了許多含有緯度參數的片段,我們找到含有以下開頭的部分 。
;window.APP_INITIALIZATION_STATE
對此後的每個網頁,我們所需要的經緯度就包含在這裡面。首先找到 ;window.APP_INITIALIZATION_STATE所在位置,並將其後面所包含的三個參數抓下來。接著丟到第四步驟中將字串轉成數字,最後寫到Excel裡面。
#放在第五個cell,記得先將之前寫的刪除
def coordination(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
text = soup.prettify() #text 包含了html的內容
initial_pos = text.find(";window.APP_INITIALIZATION_STATE")
#尋找;window.APP_INITIALIZATION_STATE所在位置
data = text[initial_pos+36:initial_pos+85] #將其後的參數進行存取
num_data = STR_to_NUM(data)
ws.append(num_data) #將經緯度存到Excel裡
※註1
從第五個cell中的參數data,我們將3610.8245986264596,121.43077076500904,25.175396183906233
三個數字數存進,但我們只需要後面兩個數字。因此在第四個cell中首先用split的功能將資料以逗點分隔並存成tuple的型態,再選取第一和第二筆index存成number的型態,最後再回傳。
def STR_to_NUM(data):
line = tuple(data.split(','))
num1 = float(line[1])
num2 = float(line[2])
line = [num1, num2]
return line
6. 對所有資料進行上述步驟
最後我們使用迴圈,將所有資料做一遍上述步驟,並將結果存在Excel裡面即可。這邊要再引用一次之前刪除的request和BeautifulSoup。
#放在第六個cell
import requests
from bs4 import BeautifulSoupfor i in URL:
coordination(i)
wb.save('test.xlsx') #自行決定檔名
7. 完整程式碼
結果
此時資料夾內會出現名為test的Excel檔。點開來後應該會出現10列含有經緯度的資料,為了方便閱讀與檢測,我們將之前作為範例的test_data內容貼上作為比較並加上顏色。
可以看到1~5列可以對應到6~10列的經緯度,這是因為下面的地址正是上面的地名。無論是用中文、英文地名或是地址都可以成功取得經緯度,同一個地點,以地名或地址最大的誤差為0.0036%。
缺點
使用Google的Geocoding API也可得到上述的結果,不過每1000筆資料必須支付5美金的費用。使用爬蟲雖然可以避免支付費用,但超過一定request的量後便會被擋住,程式將出現如下的錯誤
利用之前print(text)的方法,找出錯誤後的網頁會變成什麼樣子(部分內容)
點選以上網址後出現的頁面
不用擔心自己的電腦被鎖住或是什麼的,點選我不是機器人後Google Maps還是能正常使用,只是透過Python開啟的方式會被暫時停止,時間大約為兩個小時,時間到後便可再次進行爬蟲(並且,爬到一定數量後再被停止)。依照多次的實驗我們發現一次最多能執行的資料量只有約為138筆。
討論與後續
幸運的是這次我朋友需要的資料量不多,影響不至於太大,但就長遠來講這段程式並不完美。網路上有很多針對爬蟲被封鎖進行討論的網站,目前我還在持續研究中。常見的方法有切換IP位址,設定請求表頭等方法,設定隨機延遲時間的方法我有試過但效果不好(總存取量更低)。
假設無法從程式下手(我們設定Google的工程師都非常厲害可以擋下各種爬蟲的方法),我認為可以解決的方法是使用虛擬機,將資料分成好幾筆,只要電腦被鎖便刪掉,重新建立一個虛擬機就好。但這只是短暫替代方案,若有發現更好的解決辦法未來會再上來這裡更新。