本文将通过一个有趣的数据实例教大家如何通过python调用百度api 然后用pyecharts进行绘制全国影院密度分布图,
绘图效果如下:
由于本文篇幅较长,本文分上下两文。
上文主要讲解如何对已有数据通过Baidu Api进行经纬度及城市名查询, 下文讲解如何通过百度探索进行网络爬虫获取城市面积及最后绘图。在使用本文代码时,你的python环境需要支持numpy, pymysql(pymysql可不需要。本文用了数据库,读者也可以从csv文件提取数据,本文提供csv数据源), urllib, pyecharts, lxml , pandas库。
如下链接提供了2016年到2017年影院及院线票房数据,请读者注册下载:
影院分布及票房数据
在本文中,只需用到影院数据,其他数据读者感兴趣也可以用来实现自己的想法。
因为博主在写代码的时候同时也在接触mysql,所以上述的数据已经使用数据库管理。我们大致可以看下影院分布的数据如下所示
通过上面的数据视图可以发现, 数据共有cinema_id, cinema_name, address, telephoe(修正:telephone), court_count
seat_count, longtitude, latitude 7个属性。 其中court_count 与seat_count代表放映厅数与座位数这里暂时不用。
要想绘制上述这样的影院密度分布图, 需要知道每个城市的影院统计数。但是很遗憾, 在这个列表里并没有单独一个属性
来表述城市。 虽然部分影院名或者地址里面有,但是从这两个属性来获取影院所在城市名着实有点困难。但庆幸数据里面提供了经纬度信息, 所以可以调用百度api查询城市名,但是当数据量很大时,这是很耗时的,一种方法是第一次查询是将相应经纬度信息保存下来。当然还有一个离线的方法就是,藉由电话号码区号查询对应城市。这样就需要区号与城市对应表,我已经将相应数据放到csdn下载源, 链接如下:
全国主要城市对应电话区号
数据内容如下图所示
但是电话区号查询的城市并不是惟一的, 比如四川成都、眉山、资阳共用了区号028 . 遗憾的是我未能找到完全版区号大全。因此上述的数据在028这个区号只记录了成都。所以对部分几个城市共用的区号不能用区号来查询城市只能通过经纬度查询。当然也有另外一个办法就是爬百度探索的结果, 比如下图
我们可以通过pandas\numpy\ csv\ open来获取数据到一个字典或者dataframe, 这里仅介绍一种方法如下:
def get_city_code(path):
"""指定路径获取城市区号对应字典"""
city_code_df = pd.read_csv(path, sep=',', encoding="gbk")
city_code_dict = {}
for icode in city_code_df.index:
code_key = city_code_df.get_value(icode, "code")
code_city = city_code_df.get_value(icode, "city")
city_code_dict[code_key] = code_city
return city_code_dict
按照影院的数据, 调用mysql获取列名与列值转成字典或dataframe(非数据库可以用文件操作获取),代码操作如下:
def get_cinema_dict():
"""获取影院数据"""
#连接数据库, 需要提供host, port, user, password 及db, 选择charset
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='your password', db="test",charset="utf8")
cursor = conn.cursor()
sql = "select * from cinema"
cursor.execute(sql)
row_count = cursor.rowcount
print("cursor.description", cursor.description)
print("cursor.rowcount", row_count)
for idescrip in cursor.description:
key_list.append(idescrip[0])
# rs = cursor.fetchall()
fetch_row = row_count
rs = cursor.fetchmany(fetch_row)
cursor.close()
cinema_dict = {}
cinema_dict = {}
#初始化字典, 每个列名对应一个空表
for col_id in range(len(key_list)):
cinema_dict[key_list[col_id]] = []
#获取列内容填充相应列表
for each in rs:
for col_id in range(len(key_list)):
cinema_dict[key_list[col_id]].append(each[col_id])
return cinema_dict, fetch_row
在实现调用百度api前你需要申请百度api的密钥,这里如何申请本文不涉及,网上有相应文章你可自行搜索,以下提供
百度经验里的一个申请方法链接:
如何申请百度api密钥
获取了密钥后,通过百度的api接口无论是通过城市查询经纬度或者经纬度查询城市都是很容易的呢。
def getCityNameByBaiduApi(lat,lng):
"""调用baidu api通过纬度经度获取城市名"""
url = "http://api.map.baidu.com/geocoder/v2/"
#下面的ak填你从百度申请的ak
ak = "************"
uri = url + '?location=' + str(lat) + ',' + str(lng)\
+ '&output=json&ak=' + ak
req = urlopen(uri)
res = req.read()
res = res.decode()
res_dict = json.loads(res)
city_name = res_dict['result']['addressComponent']['city']
return city_name
def get_lnglatByBaiduApi(city):
"""通过百度api用城市名获取经纬度"""
url = "http://api.map.baidu.com/geocoder/v2/"
#下面的ak填你从百度申请的ak
ak = "***************"
uri = url + '?address=' + quote(city) + '&output=json&ak=' + ak
req = urlopen(uri)
res = req.read()
res = res.decode()
res_dict = json.loads(res)
lng = res_dict['result']['location']['lng']
lat = res_dict['result']['location']['lat']
return lng, lat
另外没有申请百度api, 也可以通过geopy库进行查询(这个库查太多次后被限制貌似)。 由于楼主使用的时候, 发现还是百度api相对比较好用, 所以只写了个通过城市名获取经纬度的,代码如下实现如下:
def getCityNameByGeo(lat, lng):
"""调用geo api通过经度纬度获取城市"""
lat_lng = str(np.round(lat, 6)) + "," + str(np.round(lng, 6))
geolocator = Nominatim()
location = geolocator.reverse(lat_lng, timeout=10)
addr = location.address
re_match = re.compile(", (\w+)市")
cityname = re_match.search(addr).group(1)
return addr, cityname
以下是geopy的使用参考,感兴趣的读者可以去看下官方文档:
geopy documentation
这里我们用jupyter lab测试下上面的三个函数, 结果如下:
实现了上述的查询接口函数后, 接下来就是通过已有影院数据,通过api获取城市,统计各个城市影院数据及地理坐标。
因为数据量的关系,多次调用百度api查城市会很耗时间,因此最开始通过区号查询城市,如果区号查不到或者存在多个城市共用一个区号的情况则调用百度api,记录了city信息后,就通过pandas的groupby来统计城市对应影院数, 同时用用同一城市所有影院的经纬度平均表示该城市地理坐标
def get_city_count(fetch_row, cinema_dict, city_code_dict):
"""通过已有影院数据,通过api获取城市,统计各个城市影院数据及地理坐标"""
city_list = []
for rid in range(fetch_row):
print("rid:\t%d" % rid)
# 从类似0755-dddd 字符内容获取电话区号
re_match = re.compile("(\d+)-\w+")
city_code_match = re_match.match(cinema_dict["telephoe"][rid])
if city_code_match is not None:
city_code = int(city_code_match.group(1))
try:
if city_code in [24, 28, 29]: #这三个区号存在不同城市共用情况,不能用区号查询城市
city = getCityNameByBaiduApi(cinema_dict["latitude"][rid], cinema_dict["longtitude"][rid])
else:
city = city_code_dict[city_code]
except:
try:
city = getCityNameByBaiduApi(cinema_dict["latitude"][rid], cinema_dict["longtitude"][rid])
#多次调用百度api可能会碰到网络故障,因此故障时需要再调用一下
except:
city = getCityNameByBaiduApi(cinema_dict["latitude"][rid], cinema_dict["longtitude"][rid])
# city = getCityNameByGeo(cinema_dict["latitude"][rid], cinema_dict["longtitude"][rid])[1]
city = city.split("市")[0]
city_list.append(city)
cinema_dict["city"] = city_list
cinema_df = pd.DataFrame(cinema_dict)
#用dataframe的groupby获取city影院统计, 用同一城市所有影院的经纬度平均表示该城市地理坐标
cinema_groupby_count = dict(cinema_df.groupby("city").apply(lambda x: x.count())["cinema_id"])
cinema_groupby_latlng = cinema_df.groupby("city")[["latitude", "longtitude"]].mean()
return cinema_groupby_count, cinema_groupby_latlng
至此已经完成了城市影院的统计。 其实不考虑城市面积的话,这里已经可以完成全国各城市影院数量分布图。
下一文将会介绍如何通过爬百度搜索获取城市面积及如何用pyecharts绘图