作为菜鸟分析师一枚,日常工作中需要处理大量地理位置相关(如城市、辖区、街道、商场、楼宇等)数据。分析报告中总是用吐了的柱形图、条形图,不仅自己看着辣眼睛,老板也审美疲劳。
想画个地图来装点一下报告视觉水平,可是又不会处理经纬度,知道地址不会批量获取经纬度,给出经纬度也不会获取对应的地址。求隔壁的开发小哥哥又远水解不了近渴(开发小哥哥又不是你家的)。
还好我早有准备,偷偷学习了Python和R,处理经纬度这点儿小事儿怎能难住我,自己动手丰衣足食,说不定哪天你就抢了隔壁开发的饭碗。
经纬度获取与处理是空间数据处理的基本功,当前各行业(特别是互联网行业)能叫的上号的头部公司,他们的主营业务也大多涉及复杂的地理区域分析工作,如用户的空间行为轨迹、O2O业务的配送轨迹、传统电商的物流轨迹、经营B端业务的商户地址、旅游&交通用户出行轨迹、摩拜单车行车轨迹、滴滴打车乘车轨迹等……
扯了这么多,没错我只是想说,学会今天要分享的经纬度获取知识,你在日常数据分析工作中会更加游刃有余,会拥有与众不同的视角和切入点。
好吧以下是学习时间:
本篇分为两个技能模块,四个小节,分别会用R语言和Python介绍并实现如何通过具体地址从高德地图api接口中调取地理经纬度,以及如何通过具体经纬度调用具体地理地址。
在开始介绍过程之前,你需要先申请一个高德地图开发者秘钥:
https://lbs.amap.com/api/webservice/guide/create-project/get-key
然后最好读一下高德地图经纬度解析的官方文档
https://lbs.amap.com/api/webservice/guide/api/georegeo
前年的时候分享过一篇百度地图经纬度解析的内容,但是因为两者经纬度编码规则不同,无法直接兼容,所以这里给出高德版方案,以后有时间详细介绍两者如何进行转换的算法。
高德地图开放了经纬度解析的api服务,但是对于个人开发者有日配额,作为非开发人员,完全可以满足日常需要。这些api是通用接口,大多数语言都具备调用能力,其中可提供的返回信息非常多,你可以参考开发api开发文档说明酌情考虑接收你需要的数据。
以下我只提供了精简版的代码,如果你需要更丰富的数据,可以适当修改代码即可。
一、R语言实现方案
1)地址转换经纬度
代码语言:javascript复制## !/user/bin/env RStudio 1.1.423
## -*- coding: utf-8 -*-
## 高德地图经纬度编码解析
library("httr")
library("magrittr")
library("jsonlite")
library('stringr')
library('dplyr')
library('rlist')
dizhi = c('北京市朝阳区望京东路4号横店大厦','北京市海淀区上地信息路9号奎科科技大厦','aaa',NA)
#将地址解析为具体的经纬度:
GetJD <- function(address){
url = "https://restapi.amap.com/v3/geocode/geo?parameters"
# 这里是地址转经纬度的api服务调用地址
header <- c("User-Agent"="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36")
# 这个是请求的报头,用来把你的开发平台伪装成一个真实的浏览器
payload = list(
'output' = 'json',
'key' = '***********************'
# 各位小伙伴儿一定要自己去申请秘钥,我自己的我就匿了
) # 这里是请求的核心参数,一个都不能少,秘钥要自己去申请
addinfo <- list()
for (i in dizhi){
payload[["address"]] = i
tryCatch({
web <- GET(url,add_headers(.headers = header),query = payload) %>% content(as="text",encoding="UTF-8") %>% fromJSON(flatten = TRUE) %>% .$geocodes
if(length(web) > 0){
content <- web %>% .$location %>% str_split(',') %>% `[[`(1)
print(sprintf("正在抓取【%s】的经纬度",i))
}else{
content <- c(NA,NA)
cat(sprintf("任务【%s】请求失败!",i),sep = "n")
}
addinfo <- rbind(addinfo,content)
},error = function(e){
cat(sprintf("任务【%s】处理失败!",i),sep = "n")
addinfo <- rbind(addinfo,c(NA,NA))
})
Sys.sleep(runif(1.5))
}
result_data <- addinfo %>% matrix(ncol=2) %>% data.frame(adress = dizhi,stringsAsFactors = F) %>% rename(lng =X1,lat = X2 )
result_data$lng <- as.numeric(result_data$lng)
result_data$lat <- as.numeric(result_data$lat)
print("所有数据全部抓取完毕!!!")
return(result_data)
}
system.time(myresult<-GetJD(dizhi))
存放地址的向量中加入了NA和aaa这样的无效值仅仅是为了保证程序容错能力,整体上不涉及太复杂的逻辑实现。
2)经纬度转地址
代码语言:javascript复制#地址逆解析——将经纬度还原为具体地址
lddata = data.frame(
lat = c(39.934,40.013,40.047,NA,4444),
lon = c(116.329,116.495,116.313,NA,6666)
)
GetAddress <- function(lddata){
url = "https://restapi.amap.com/v3/geocode/regeo?parameters"
header = c('User-Agent'= 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36')
payload = list(
'output' = 'json',
'key' = '*******************'
# 匿了匿了
)
addinfo = c()
for (i in 1:nrow(lddata)){
payload[['location']] = sprintf('%.3f,%.3f',lddata[i,'lon'],lddata[i,'lat'])
tryCatch({
web <- GET(url,add_headers(.headers = header),query = payload) %>% content(as="text",encoding="UTF-8") %>% fromJSON(flatten = TRUE)
if(length(web$regeocode$formatted_address) > 0 ){
content <- web %>% .$regeocode %>% .$formatted_address
cat(sprintf("正在处理第【%d】个任务!",i),sep = "n")
} else {
content <-NA
cat(sprintf("第【%d】个任务请求失败!",i),sep = "n")
}
addinfo <- content %>% c(addinfo,.)
},error = function(e){
cat(sprintf("第【%d】个任务处理错误!",i),sep = "n")
addinfo <- c(addinfo,NA)
})
Sys.sleep(runif(2))
}
print("所有任务处理完毕!!!")
cbind(addinfo,lddata) %>% return()
}
system.time(MyAddress <- GetAddress(lddata))
测试的经纬度中加入了NA和444这样的非法经纬度,同样是为了在代码中植入容错模块,提高容错性。在构思代码之前,要提前思考可能遇到哪些错误,不同的错误应该在那个阶段进行容错,不同的错误会导致哪些不同的结果,出现了错误应该如何替补默认返回值。
二、Python实现方案
3)地址转经纬度
代码语言:javascript复制#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import requests
import re,random
import numpy as np
import pandas as pd
import
dizhi = ['北京市朝阳区望京东路6号望京国际研发园三期','北京市海淀区上地信息路9号奎科科技大厦',None,'aaa']
def getid(dizhi):
url = "https://restapi.amap.com/v3/geocode/geo?parameters"
header = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}
payload = {
'output':'json',
'key':'*******************'
# 匿了哦
}
addinfo = []
for i in dizhi:
payload['address'] = i
try:
content = requests.get(url,params=payload,headers=header).json()
if content['status'] == '1':
addinfo.append(re.split(',',content['geocodes'][0]['location']))
print("任务【{}】请求成功!".format(i))
else:
addinfo.append([np.NaN,np.NaN])
print("任务【{}】请求失败!".format(i))
except:
addinfo.append([np.NaN,np.NaN])
print("任务【{}】处理失败!".format(i))
pass
time.sleep(random.choice(range(2)))
temp = pd.DataFrame(addinfo,columns= ['lng','lat'])
temp['address'] = dizhi
return( temp)
if __name__ == "__main__":
#计时开始:
t0 = time.time()
myaddress = getid(dizhi)
t1 = time.time()
total = t1 - t0
print("消耗时间:{}".format(total))
此类api的调用方案,Python的可获取资料网上会比较多一些,毕竟这几年Python火起来之后,Python网络爬虫成了好多小伙伴儿更换职业赛道的重要突破口。
4)经纬度转地址
代码语言:javascript复制lat = [39.934,40.013,40.047,None,7000]
long = [116.329,116.495,116.313,None,40000]
lddata = [(j,w) for j,w in zip(long,lat)]
def GetAddress(lddata):
url = "https://restapi.amap.com/v3/geocode/regeo?parameters"
header = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}
payload = {
'output':'json',
'key':'**********************'
}
addinfo = []
j = 0
for lon,lat in lddata:
payload['location'] = '{0:s},{1:s}'.format(str(lon),str(lat))
try:
content = requests.get(url,params=payload,headers=header).json()
if content['status'] == '1':
addinfo.append(content['regeocode']['formatted_address'])
print("任务【{}】请求成功!".format(j 1))
else:
addinfo.append(np.NaN)
print("任务【{}】请求失败!".format(j 1))
except:
addinfo.append(np.NaN)
print("任务【{}】处理失败!".format(j 1))
time.sleep(random.choice(range(2)))
j = 1
temp = pd.DataFrame(np.array(lddata),columns = ['long','lat'])
print(addinfo)
temp['address'] = addinfo
return(temp)
if __name__ == "__main__":
#计时开始:
t0 = time.time()
mylonlat = GetAddress(lddata)
t1 = time.time()
total = t1 - t0
print("消耗时间:{}".format(total))
针对高德地图API的调用总结几点:
1)api开放接口是很规范的数据获取渠道,调用成本低,效率高(在接口时限内)相比你从html或者xml里面解析数据要高效的多,核心只需要了解有那些必须提供的请求参数(如想要的返回值格式、使用服务的合法秘钥等)。
2)拿到返回值之后就可以在各平台进行结构化处理,一般都会选择json进行返回,因为这种格式比较主流,各平台处理工具也比较多,比如R语言中的jsonlite、python中则因为字典天然与json格式高度兼容,甚至都不怎么需要特殊处理,使用字典的基本函数就可以很好的提取其中有效信息。
3)关于容错,R语言需要重点复习trycach else的用法,Python中测试需要掌握try …… except等异常捕获处理机制。这里最重要的是,你要判断哪些是错误,哪些可以合法通过请求,但是却不一定能拿到想要的数据,这时需要想办法进行替补或跳出循环(即直接忽略)。
本文真的是一篇满满的干货,也就是这段疫情不能出门的时间才有足够的心力、时间去写,简直可以称作是疫情期间的学习日记。