獲取高德和百度地圖的POI及邊界數據爬蟲-JAVA版本
做了什么
- 合理的利用高德地圖或者百度地圖的API開發者key,對地圖上的POI數據進行獲取,方便數據統計與整理分析
- 結合百度和高德的API,可以獲取到地點的關聯信息以及大部分的數據邊界信息,並進行了坐標轉換
- 對於數據可以進行分級操作,包括區分出地市、區縣、鄉鎮/街道、小區/工業園區/商務樓宇、樓棟等層級
如何使用
在 com.accright.plugins.spider.utils.DBFactory 中配置好數據庫與驅動,運行com.accright.plugins.spider.amap.main.CitySpider即可運行地市獲取,運行com.accright.plugins.spider.amap.main.AmapSpider即可獲取所有的POI數據及大部分的邊界信息。運行com.accright.plugins.spider.amap.main.AddressManager即可進行數據的按照級別分割並入庫,級別包括地市、區縣、鄉鎮/街道、路、門牌號、小區/工業園區/商務樓宇、樓棟七級,可以通過修改配置的方式來決定保存哪幾種分類和哪幾個級別。 數據庫模型及字段請按照SQL進行創建和整理,目前適用於Oracle,稍加改造即可適用於MySQL
原理及使用
流程總述
簡介
該文檔主要介紹了通過爬取高德地圖和百度地圖等互聯網地址數據,包括地址坐標、邊界范圍、地址詳情、地址名稱和地址類型等,經過去重和正則表達式分割,建立地址之間的層級關系,從而形成標准地址,錄入標准地址庫等流程。
流程綜述
- 高德數據爬取:通過高德地圖提供的API接口,根據類型和分塊經緯度范圍爬取高德的地址信息數據
- 百度數據爬取:根據高德API查詢的地址名稱,模糊匹配百度地圖搜索數據,通過該數據獲取地址范圍和標准六級地址分割等信息
- 數據結果去重:對地址中查詢出的高德或百度ID相同的數據進行去重
- 建立地址關系:對查詢出來的標准六級地址數據分割為標准地址前六級
- 標准地址入庫:將已建立關聯關系的標准地址插入數據庫
流程詳解
高德數據爬取
功能描述
高德數據爬取是根據高德地圖提供的多邊形查詢 API,使用 Http 請求獲取 JSON 數據並解 析,其請求的參數主要如下:
參數名 | 含義 | 規則說明 | 是否必填 | 缺省值 |
---|---|---|---|---|
key | 請求服務權限標識 | 用戶在高德地圖官網申請 Web服務API類型KEY | 必填 | 無 |
polygon | 經緯度坐標對 | 規則:經度和緯度用","分割,經度在前,緯 度在后,坐標對用豎線分割。經緯度小數點后 不得超過 6 位。 多邊形為矩形時,可 傳入左上右下兩頂點坐標對;其他情況下首 尾坐標對需相同。 | 必填 | 無 |
keywords | 查詢關鍵字 | 規則: 多個關鍵字用豎線分割 | 可選 | 無 |
types | 查詢POI類型 | 可選值:分類代碼或漢字(若用漢字,請 嚴格按照附件之中的漢字填寫) 分類代碼由六位數字組成,一共分為三個部分,前兩個數字代表大類;中間兩個數字代 表中類;最后兩個數字代表小類。 若指定了某個大類,則所屬的中類、小類都 會被顯示。例如:010000 為汽車服務(大類)010100 為加油站(中類) 010101 為中國石化(小類)010900 為汽車租賃(中類) 010901 為汽車租賃還車(小類) 當指定 010000,則 010100 等中類、010101 等小類都會被包含。當指定 010900,則 010901 等小類都會被包 含載 POI 分類編碼和城市編碼表當 keywords 和 types 為空的時候, 我們會 默認指定 types 為 120000(商務住宅)&15 0000(交通設施服務) | 可選 | 無 |
offset | 每頁記錄數據 | 用戶在高德地圖官網申請 Web 服務 API 類 型 KEY | 可選 | 20 |
page | 當前頁數 | 最大翻頁數 100 | 可選 | 1 |
extensions | 返回結果控制 | 此項默認返回基本地址信息;取值為all返回地址信息、附近POI、道路以及道路交叉口信息。 | 可選 | base |
output | 返回數據 | 可選值:JSON,XML | 可選 | JSON |
由於高德地圖有數據保護,每次返回數據不會超過 1000 條,實際上最多到 900 條左右便不 會再返回數據,而且數據超過 300 條有極大概率出現翻頁不准確的問題,所以需要嚴格划分 查詢的區塊,然后如果區塊內的數據超過 200 條,則進行遞歸查詢,將區塊一分為四,保證 每個區塊內的數據不會超過 200 條。其中的類型參數,使用 | 分割,根據高德地圖 POI 分 類列表,采用多線程的方式進行獲取。其獲取的數據字段主要有:
將獲取的以上字段中的 id,parent,name,types,location,cityname,adname,address 存入數據庫,以 上信息不包括邊界數據,而高德的邊界數據請求有破解難度非常高的反爬蟲機制,因此地址 的邊界數據需要通過高德地圖的名稱模糊匹配百度地圖的數據,從而獲取百度米制坐標系的 邊界信息數據。
關鍵功能點及解決辦法
地圖數據數量爬取限制
高德地圖數據爬取最多返回1000條,實際每次請求如果超過 300 條便會發生分頁異常,咨詢高德客服表明這是一個bug,也是為了處於數據保護考慮,因此對於大的區域,例 如地市和省份需要進行畫區塊分割的方式進行范圍查詢,同時對於每個區塊,如果數據 超過 200 條,再次進行遞歸分割,其示例圖如下:
由於 200 條數據會導致查詢數量增多,速度變慢,因此對於不同的分類,采用多線程的方式進行查詢。
遞歸次數過多導致StackOverFlow
如果選取的范圍太大,多次遞歸會導致StackOverFLow,因此對於大區域,需要先進行區塊分割,然后再進行遞歸查詢。
百度數據爬取
功能描述
為了解決高德地圖查詢的數據沒有邊界信息和地址分割信息的問題,通過高德地圖查詢的數據名稱模糊匹配百度地圖查詢的數據,然后根據百度地圖查詢的數據獲取地址的邊界信息和 地址分割數據,其中邊界信息是采用的百度米制坐標的數據,需要進行坐標轉換將其轉換為 火星坐標系的點坐標,獲取的地址信息則需要通過正則表達式分割之后存入臨時表。百度地 圖沒有提供現成的 API 做邊界查詢使用,通過抓包獲取到可以查詢邊界信息的地址為: https://api.map.baidu.com/?qt=s&c=288&wd=%E6%B5%AA%E6%BD%AE%E7%A7%91%E6%8A%80%E5%9B %AD&rn=10&ie=utf- 8&oue=1&fromproduct=jsapi&res=api&callback=BMap._rd._cbk48424&ak=8QiGVpCMtmzYxWSeYC MhzQvmjH8laVql 其中 wd 參數即為模糊查詢的關鍵字參數,c參數即為地市參數,貴陽市的參數為146 callback 參數可以為空。返回的數據為 str 數據,需要解析為 JSONObject 數據進行解析,其 返回的數據列表字段位為:
將以上數據存入數據庫,用於后續的去重及篩選操作。
關鍵功能點及解決辦法
一條高德數據對應多條百度數據:因為查詢百度的數據是根據模糊匹配的,所以查詢的數據會出現多條,默認第一條是最 准確的數據,因此如果一條高德數據對應多條百度數據,取百度的第一條數據為准。
在百度地圖中查詢不出數據:如果在百度地圖中查詢不出數據,說明在百度中沒有該點數據,則該點數據不保存百度 的地址信息,直接存入數據庫。由於該接口不是百度地圖的標准接口,所以可能存在部分地址更新不全的問題,這個問題需要在后續通過抓包找出更適合的鏈接來替換。
地址分割方法:百度地址查詢出來的地址信息比較多,且可以進行正則表達式的分割,其主要的地址類 型有以下幾種:
-
比較標准的地址數據,分為省、市、區、街道或路、門牌號,示例如下: [ 貴 州 省 (520000)|PROV|0|][ 貴 陽 市 (520100)|CITY|1|][ 南 明 區 (520102)|AREA|1|][花果園大街()|ROAD|1|1 號$]
-
比較標准但是無門牌號的數據,示例如下: [貴州省(520000)|PROV|1|][貴陽市(520100)|CITY|1|][烏當區(520112)|AREA|1|] 新添寨[新添大道北段()|ROAD|1|]仁恆別墅
-
帶有附加地址的數據,例如某某公園附近,示例如下: [貴州省(520000)|PROV|0|][貴陽市(520100)|CITY|0|][南明區(520102)|AREA|0|] 太慈橋[車水路()|ROAD|1|]第十五中學北
-
帶有附加地址和 POI_PARENT 字段的數據,示例如下: [ 貴 州 省 (520000)|PROV|0|][ 貴 陽 市 (520100)|CITY|0|][ 觀 山 湖 區 (520115)|AREA|0|][ 商 城 東 路 ()|ROAD|0|] 杭 州 路 [ 貴 陽 西 南 國 際 商 貿 城 (4665258296544379272)|POI_PARENT|1|]
-
帶有附加地址的數據,同時路或街道數據被附加地址數據所分割: [ 貴 州 省 (520000)|PROV|0|][ 貴 陽 市 (520100)|CITY|0|][ 觀 山 湖 區 (520115)|AREA|0|]金嶺社區服務中心高新區[陽關大道()|ROAD|1|110 號$]
以上數據中,1、2、3、4、5 可以按照以下正則表達式分割為省、市、區、路(可能為空)、門牌號(可能為空),附加地址信息(可能為空):
String xareg = "\\|\\d*\\|\\]";//截取到]
對於第五類數據,分割出來的路數據可能會包含附加信息數據,需要使用程序再次分割。 多條高德數據指向同一百度數據 對於有邊界的情況,出現多條高德數據指向同一百度的數據時,將地址名稱以百度地圖的為准,對於沒有邊界的地址,如果該地址數據屬於需要入庫的分類,則其地址信息也 以百度的地址數據為准。
數據結果去重
功能描述
數據去重主要分為高德地圖數據去重和百度地圖數據去重,同時要篩選掉明顯不符合結果詳 情的數據,例如根據高德的名稱從百度地圖中查詢出來的數據地市明顯不符合要求,這些數 據需要篩選掉。
關鍵功能點及解決辦法
高德地圖數據去重
高德地圖數據去重可以使用 SQL 直接去重,因為高德數據中 ID 數據是唯一的(如果爬 取數據量過大,高德會處於數據保護的原因返回錯誤數據,會導致 ID 不唯一,如果出 現 ID 不唯一,解決方案是重新申請一個 Key 然后替換),可以使用如下 SQL 進行 ID 的數據去重:
select t.id,count(id) from t_amap_addr_temp t group by id having count(t.id) > 1; select min(rowid) from t_amap_addr_temp t group by id having count(t.id) > 1; delete from t_amap_addr_temp a where a.id in (select t.id from t_amap_addr_temp t group by id having count(t.id) > 1) and rowid not in (select min(rowid) from t_amap_addr_temp t group by id having count(t.id) > 1);
如果數據量過大,直接執行可能會導致崩潰,可用創建臨時表的方式去重:
select t.id,count(id) from t_amap_cus_temp t group by id having count(t.id) > 1; select min(rowid) from t_amap_cus_temp t group by id having count(t.id) > 1; delete from t_amap_cus_temp a where a.id in (select t.id from t_amap_cus_temp t group by id having count(t.id) > 1) and rowid not in (select min(rowid) from t_amap_cus_temp t group by id having count(t.id) > 1); select t.*,t.rowid from t_amap_cus_temp t where t.id = 'B035302WL1'; create table t_temp_cus_del as select t.id,count(id) counts from t_amap_cus_temp t group by id having count(t.id) > 1; create table t_temp_cus_rowid as select min(rowid) minrowid from t_amap_cus_temp t group by id having count(t.id) > 1; select t.*,t.rowid from t_temp_cus_del t; select t.*,t.rowid from t_temp_cus_rowid t; delete from t_amap_cus_temp a where a.id in (select t.id from t_temp_cus_del t) and rowid not in (select b.minrowid from t_temp_cus_rowid b );
百度地圖數據去重
對於多條高德數據,查詢出同一條百度數據來的情況,使用百度地圖查詢出來的地址信息數據。這些數據有一些明顯不符合要求,例如查詢出來的省份或者地市不正確,可以直接在數據庫中執行刪除。刪除之后,還會出現百度地圖UID相同的數據,這些數據可以直接使用 SQL去重,其 SQL如下所示:
create table t_cus_bmap_rowid_del as select min(rowid) minrow from t_amap_cus_temp t group by t.bmap_uid having count(t.bmap_uid) > 1; create table t_cus_bmap_uid_del as select t.bmap_uid from t_amap_cus_temp t group by t.bmap_uid having count(t.bmap_uid) > 1; delete from t_amap_cus_temp t where t.bmap_uid in (select b.bmap_uid from t_cus_bmap_uid_del b)
由於要使用百度地圖的數據為准,需要將該數據的分類賦值為百度地圖的分類,並將地址的名稱賦值為百度地圖獲取的地址名稱。
update t_amap_addr_temp t set t.zh_label = t.bmap_zhlabel,t.types = concat('BMAP_',t.bmap_types) where t.bmap_uid in (select a.bmap_uid from t_cus_bmap_uid_del a);
建立地址關系
功能描述
建立地址關系主要是通過程序對標准地址的分割數據進行篩選,需要注意的是目前地址只能分割到前六級,即小區/公司/園區一級,標准地址的附加信息中可能會包含 7-9 級地址數據, 但是目前沒有很好的方式分割。對於標准地址的分割數據中,街道和鎮等可能會與附加信息 處於同一等級,例如: [貴州省(520000)|PROV|0|][貴陽市(520100)|CITY|0|][修文縣(520123)|AREA|0|]扎佐鎮[襄 陽南路()|ROAD|1|308 號$] 和[貴州省(520000)|PROV|0|][貴陽市(520100)|CITY|1|][雲岩區(520103)|AREA|1|]貴烏社區服務中心[百花山路()|ROAD|1|123 號$] 也可能沒有鄉鎮/街道信息,目前解決辦法是將路/街道/鄉鎮信息全部匯入附加信息一級,街 道和鄉鎮一級為空。對於標准地址分割中不包括路等級的(路一級可能會在附加信息中出現, 但是沒有規律,例如:[貴州省(520000)|PROV|0|][貴陽市(520100)|CITY|0|][白雲區 (520113)|AREA|0|]誠信南路)直接采取舍棄改地址的方式處理。
關鍵功能點及解決辦法
- OOM異常:由於采用程序批量處理地址數據,將地址數據入庫時采用的方法為直接加載到內存處理,如果數據量過大時很可能出現 OOM 異常,目前通過加大程序內存解決,后續需要將程 序更改為分批處理的方案。
- 地址信息不全:對於百度的地址分割數據不是標准地址的信息,例如沒有路信息的地址,目前的處理方案為直接舍棄,對於沒有門牌號信息的地址,目前的解決方案為將門牌號關聯置空,對 於有附加信息的數據,將數據保存到六級地址的附加信息字段中,用於在以后再次提取地址信息數據使用。對於有邊界信息的地址數據,將邊界信息直接保存到六級地址的 Clob 類型的字段中。
標准地址入庫
功能描述
標准地址入庫功能主要是將分割和建立好地址關系的數據錄入標准地址數據庫,主要為前六級數據:省、地市、區縣、街道(目前為空)、路、門牌號(可能為空)、小區/園區/公司級, 各地址之間使用唯一的 INT_ID 作為關聯。