用python调用百度api及pyecharts绘制全国影院密度分布图(下)

通过爬取百度搜索获取城市面积

上文介绍了如何调用百度api,获取城市名及经纬度, 本文完成上文后续工作。
现在已经知道了每个城市的影院数量,接下来怎么获取城市面积呢。截止目前,我未在网上找到这
样的一个数据库(不过现在有了哈哈)。那么还有一个可以获取城市面积的渠道是,百度搜索。
只要一点简单的地爬虫基础,我们就可以爬取百度搜索的内容了,如在百度搜索搜索深圳城市面积如下图
page1
可以发现,在响应网页相应位置可以获得城市面积。 这里需要用到一个python库lxml, 关于lxml如何使用,可参见官方文档
lxml
在谷歌浏览器安装一个扩展插件,可以直接到该内容对应的xpath如下:
//*[@id=”1”]/div/div[1]/div/div[2]/div/text()
通过lxml库的xpath函数就可以直接从上厕所xpath string获取到城市面积,
如对xpath不是很了解的,有关xpath教程可以参考如下链接:
xpath 语法
解决了内容路径问题,那么就是怎么爬取网页。 这是一个相对很简单的爬虫, 对百度搜索而言可以发现查询城市面积
的URL形式如下:
http://www.baidu.com/s?wd= + XX市城市面积
从如下网页爬取内容可以用python自带的 urllib.Requests(也可以用requests库的get方法,依个人喜好), 代码如下:

def get_city_area(city):
    """get city area through baidu search engine
    #param city
    city: city name without tile '市'"""
    url = 'http://www.baidu.com/s?wd='
    user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
    headers = {'User-Agent': user_agent}


    try:
        line = city + "市城市面积"
        uri = Request(url + quote(line.strip().encode('utf-8')), headers=headers)
        page = urlopen(uri).read()
        # page = response.read()
        page = page.decode("utf-8")
        # soup = BeautifulSoup(page, "html.parser")
        res_html = etree.HTML(page)
        result = res_html.xpath('//*[@id="1"]/div/div[1]/div/div[2]/div/text()')[0]
        pass
    # res_html = etree.tostring(res_html)
    except:   # 一些城市如自治区自治县不能加市进行搜索
        line = city + "城市面积"
        uri = Request(url + quote(line.strip().encode('utf-8')), headers=headers)
        page = urlopen(uri).read()
        # page = response.read()
        page = page.decode("utf-8")
        # soup = BeautifulSoup(page, "html.parser")
        res_html = etree.HTML(page)
        result = res_html.xpath('//*[@id="1"]/div/div[1]/div/div[2]/div/text()')[0]

    result = result.replace(' ','')  # replace white space
    re_match = re.compile('(\d+\.*\d*)万')  # 匹配dddd\dddd.dd,\dddd.万获取数值
    area_match = re_match.search(result)
    if area_match is not None:
        area = np.round(float(area_match.group(1))*10000, 2)
    else:
        try:    # 匹配ddd,ddd\ddd,ddd.dd\ddd,ddd.\ 获取数值
            re_match = re.compile(r'(\d+[,,]\d+\.*\d*)')
            area_match = re_match.search(result).group(1)
            area_s = re.split(',|,', area_match)
            area = float(area_s[0] + area_s[1])
        except: # 匹配dddd(.dd) , 获取数值
            re_match = re.compile('(\d+\.*\d*)')
            area_match = re_match.search(result)
            area = float(area_match.group(1))

注意上述代码有几个要注意的地方,一是部分城市如自治县是不能加市进行搜索的因此要把市去掉, 从html提取的内容需要用正则进一步处理, 百度搜索不同城市的城市面积xpath位置是固定的,但内容相对会有差异(读者可以亲自去实践下,这里不做过多笔墨)。比如有些城市是平方公里,有些是万平方公里,还有一些数字间分别用了中英文逗号分隔(中文逗号估计是不规范),这些情况均要考虑周全才可以获取到准确的面积,上面相对较长的正则匹配就是覆盖这些情况。
上文已经获取到了每个城市的影院数量及相应经纬度,为了与百度API 保持一致,会将获取的城市与百度API进行自校,同时获取城市面积及影院密度,对应代码如下:

def get_city_density(cinema_groupby_latlng, cinema_groupby_count):
    """获取城市影院计量与城市影院密度"""
    city_area = {}
    geo_city_coords = {}
    cinema_city_count = {}
    cinema_city_density = {}
    sum = 0
    for idx in cinema_groupby_latlng.index:
        cnt = cinema_groupby_count[idx]
        sum += cnt
        idxs = idx + "市"
        try:   # 用已有城市名通过百度api获取权威的经纬度,这里主要与是api保持一致
            lng, lat = get_lnglatByBaiduApi(idxs)
        except: # 获取失败,直接用已有的均值代替
            lng = cinema_groupby_latlng.get_value(idx, "longtitude")
            lat = cinema_groupby_latlng.get_value(idx, "latitude")
        try:
            if idxs in cinema_city_count.keys(): #因原先经纬度的记录并不一定完全准确,
                                                 #通过经纬度再查询的城市可能原先已经存在即与电话薄之间的城市出现了偏差
                                                 #这个小概率出错,合并城市名处理
                print("corrupt key")
                cnt = cinema_city_count[idxs] + cnt
            idxs = idxs.split("市")[0]
            city_area[idxs] = get_city_area(idxs)
            cinema_city_count[idxs] = cnt
            print(cnt)
            cinema_city_density[idxs] = np.round((10000 * cnt / city_area[idxs]), 2)
            geo_city_coords[idxs] = [np.float("%.2f" % lng), np.float("%.2f" % lat)]
        except: #查询失败,有可能是城市名不标准, 通过现有的经纬度可以反向查询规范城市名
            city = getCityNameByBaiduApi(lat, lng)
            city = city.split("市")[0]
        # try:
            if city in cinema_city_count.keys():
                print("corrupt key")
                cnt = cinema_city_count[city] + cnt
            city_area[city] = get_city_area(city)
            cinema_city_count[city] = cnt
            print(cnt)
            cinema_city_density[city] = np.round((10000 * cnt / city_area[city]),2)
            geo_city_coords[city] = [np.float("%.2f" % lng), np.float("%.2f" % lat)]
        print("sum: ", sum)
        return cinema_city_count, cinema_city_density, city_area, geo_city_coords

获取了城市影院数量与城市面积后,就剩最后一步了,这里需要用到pyecharts, 说明下pyecharts是ecahrts的一个python接口,其可视化相对echarts的界面会少许多。如果相绘制更高大上的GEO MAP建议直接调用 echarts,需要点精力。
关于pyecharts,可查阅官方文档: pyecharts documentation
本文直接调用pyecharts接口代码实现如下:

    cinema_map = list(zip(cinema_city_count.keys(), cinema_city_count.values()))
    geo = Geo(u"全国主要城市影院数量分布", "data from dydata", title_color="#fff",
                title_pos="center", width= 1200, height=600, background_color='#404a59')
    attr, value = geo.cast(cinema_map)
    geo.add("cinema_distribution", attr, value, type="heatmap", visual_range=[0, int(max(value))+1],
                visual_text_color="#fff", symbol_size=12, is_visualmap=True, geo_city_coords=geo_city_coords)
    geo.render("cinema_distribution.html")

同样密度分布的话,直接将上述的cinema_city_count改成cinema_city_density即可。
影院数量分布如下:
count
密度分布效果如下:
cinema_density
可以发现就影院分布数量而言,排在前五的分别是上海(206),重庆(175),北京(160),深圳(153),广州(121)。 上述结果与全国一线城市一致。而重庆是因为面积大(接近一个省的面积)所以影院数量较大。
另外看密度分布图而言,可以发现深圳的影院密度是最高的,达到766.21每万平方公里,其次是东莞,中山与上海。

猜你喜欢

转载自blog.csdn.net/huang_shiyang/article/details/80399820
今日推荐