用Python抓新型冠状病毒肺炎实时数据,绘制市内疫情地图

运行结果(2020-2-4日通报数据,福州市)

  • web请求用request
  • 网页内容解析用pyquery、beautifulsoup和正则表达式
  • 地图用Basemap(墨卡托横轴投影)

在这里插入图片描述

数据来源和分析

数据来源于福建省疾病预防控制中心官网
这是2020-02-04的情况通报

  • 首先进入官网搜索出肺炎疫情情况通报列表
    在这里插入图片描述
  • 分析该响应,发现其没有返回xml或者json之类的数据,直接干回来html;咱们dom解析一下,把最新一篇通告的链接找出来
    在这里插入图片描述
  • 请求最新一篇疫情情况页面,一篇普通的文本页面;简单对比该站的几篇通报,可知这个页面是标准化的;那咱们就找出需要的节点,然后析取出地区和数值
    在这里插入图片描述
    在这里插入图片描述

地图数据

gadm地图数据下载地址,这个数据包括国家、省、市三级行政区域,但是市级数据不准确,长乐名字为“长乐市”(现在已名为“长乐区”);最恼火的是,地图数据上平潭归为福清下(现实是平潭为省辖地区),本文不收集平潭的疫情数据,未处理地图错误

代码实现

#%%

import time, json, requests
from datetime import datetime
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.font_manager import FontProperties
from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon
import numpy as np

from pyquery import PyQuery as pq
from bs4 import BeautifulSoup
import datetime
import re

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

#%%

# 获取福建省疾病预防控制中心官网疫情通告列表
session = requests.session()
crawl_timestamp = int(datetime.datetime.timestamp(datetime.datetime.now()) * 1000)
keyword = {'txtkeyword':'福建省新增新型冠状病毒感染的肺炎疫情情况'}
html = '' 
while True:
    try:
        rsp = session.get('http://www.fjcdc.com.cn/search', params=keyword)
    except requests.exceptions.ChunkedEncodingError:
        continue
    
    rsp.raise_for_status()  # 非200则抛出异常(rsp.status_code != 200)
    html = rsp.content
    # soup = BeautifulSoup(rsp.content, 'lxml')
    # print(soup)
    break 

#%%

# 获取最新一期的疫情通告链接地址
doc = pq(html)
# 方法一:第一条数据,doc('.list li a').attr.href即可得到所要链接
# 方法二:指定日期, doc('.list li:contains("2020-02-02") a').attr.href
# 但是这里咱们多写点,练习嘛,乱写
news = doc('.list li').items()
dates = []
for item in news:
    date_str = item('span').text().strip() 
    date = datetime.datetime.strptime(date_str,'%Y-%m-%d')
    dates.append(date)

temp = np.array(dates)
latest_date = temp.max()
latest_date_str = latest_date.strftime('%Y-%m-%d')
latest_date_url = doc('.list li:contains("{0}") a'.format(latest_date_str)).attr.href
latest_date_url = 'http://www.fjcdc.com.cn' + latest_date_url
print(latest_date_url)   

#%%

# 解析网页,获取确诊和疑似病例数据文本
# 福州市47例(仓山区2例、晋安区6例、长乐区4例、闽侯县2例、连江县8例、罗源县1例、闽清县5例、永泰县2例、福清市14例、宁德市古田县1例、湖北省武汉市2例);
# 上面最后两条数据啥意思?
soup = ''
while True:
    try:
        rsp = session.get(latest_date_url)
    except requests.exceptions.ChunkedEncodingError:
        continue
    
    rsp.raise_for_status()  # 非200则抛出异常(rsp.status_code != 200)
    
    soup = BeautifulSoup(rsp.content, 'lxml')
    # print(soup)
    break 

reg = re.compile('.*福州市.*')
soup = soup.find('div', class_='showCon')
tag = soup.find_all(text=reg)
if len(tag) != 4:
    raise Exception('查找到值的次数必须等于 4. 实际值为: {}'.format(len(tag))) 

area_data = {}
# area_data.update({'confirm_added':tag[0]})      
# area_data.update({'suspend_added':tag[1]})
area_data.update({'confirm':tag[2]})      
area_data.update({'suspend':tag[3]})
print(area_data)

#%%

# 解析各区县数据
import re
pattern = re.compile('(?<=、|()\D+[市|县|区]\d+例')
town_list = pattern.findall(area_data['confirm'])
# town_list = area_data['confirm'].split('(|(')[1].split('))')[0].split('、')

town_data = {'福州市区':0} 
for town in town_list:
    match_num = re.search(r'\d+(?=例)', town)
    match_town_name = re.search(r'\D+[市|县|区]', town)
    if match_num and town:
        match_num = int(match_num.group())
        match_town_name = match_town_name.group()
    else:
        continue
    
    if match_town_name == '长乐区':  # 地图中长乐为市
        match_town_name = '长乐市'   
        
    town_data.update({match_town_name: match_num})  
    
    # 晋安、鼓楼、、马尾、仓山
    if match_town_name[-1] == '区' :  # 地图数据没有各个区的数据
        town_data['福州市区'] += match_num
       
print(town_data)    

#%%

# 用Basemap画疫情图(这是从gadm下载地形数据,行政区域不准确)

def get_color(town_data, town_name_zh):
    color = '#7FFFAA' 
    if town_data[town_name_zh] == 0:
        color = '#f0f0f0'
    elif town_data[town_name_zh] <= 5:
        color = '#ffaa85'
    elif town_data[town_name_zh] <= 10:
        color = '#ff7b69'
    elif town_data[town_name_zh] <= 20:
        color = '#bf2121'
    else:
        pass
    return color 

# 绘制福建省福州市确诊分布图
def plot_fz_dis_by_basemap(): 
    
    # area_cfm_data = catch_fj_area_distribution()
    
    # 标签颜色和文本    
    legend_handles = [
                matplotlib.patches.Patch(color='#7FFFAA', alpha=1, linewidth=0),
                matplotlib.patches.Patch(color='#ffaa85', alpha=1, linewidth=0),
                matplotlib.patches.Patch(color='#ff7b69', alpha=1, linewidth=0),
                matplotlib.patches.Patch(color='#bf2121', alpha=1, linewidth=0),
                matplotlib.patches.Patch(color='#7f1818', alpha=1, linewidth=0)
    ]
    legend_labels = ['0人', '1-5人', '5-10人', '10-20人', '>20人']
    
    fig = plt.figure(facecolor='#f4f4f4', figsize=(10, 8))    
    axes = fig.add_axes((0.1, 0.1, 0.8, 0.8))
    axes.set_title('福州市新型冠状病毒疫情地图(不含平潭)', fontsize=14)  
    axes.legend(legend_handles, legend_labels, bbox_to_anchor=(0.5, -0.11), loc='lower center', ncol=5)  
    
    # 横轴墨卡托投影  
    china_map = Basemap(llcrnrlon = 118.3, llcrnrlat = 25.2, urcrnrlon = 120.1, urcrnrlat = 26.8,
                        resolution = 'i', projection = 'tmerc', 
                        lat_0 = 26, lon_0 = 119,
                        ax=axes)
    # gadm36_CHN_1 省一级;gadm36_CHN_2 市一级;gadm36_CHN_3 县一级
    '''    
    {'GID_0': 'CHN', 'NAME_0': 'China', 'GID_1': 'CHN.1_1', 'NAME_1': 'Anhui', 
    'NL_NAME_1': '安徽|安徽', 'GID_2': 'CHN.1.1_1', 
    'NAME_2': 'Anqing', 'NL_NAME_2': '安庆市', 'GID_3':
     'CHN.1.1.1_1', 'NAME_3': 'Anqing', 'VARNAME_3': '', 
     'NL_NAME_3': '', 'TYPE_3': 'Xiànjíshì', 'ENGTYPE_3': 
     'County City', 'CC_3': '', 'HASC_3': '', 'RINGNUM': 1, 'SHAPENUM': 1}    
    '''
    china_map.readshapefile('res/gadm36_CHN_shp/gadm36_CHN_3', 'states', drawbounds=True)   
    china_map.drawmapboundary(fill_color='aqua')
    china_map.fillcontinents(color='white', lake_color='aqua')
    china_map.drawcoastlines()
    
    for info, shape in zip(china_map.states_info, china_map.states):
        prov_name = info['NAME_1'].strip()    
        city_name = info['NAME_2'].strip()
         
        if prov_name != 'Fujian' or city_name != 'Fuzhou' :
            continue
            
        color = '#7FFFAA'     
        town_name_zh = info['NL_NAME_3'].strip()
        town_name_py = info['NAME_3'].strip()        
        if town_name_zh in town_data.keys() :    
            color = get_color(town_data, town_name_zh)
        elif town_name_py == 'Fuzhou':  # 福州市区NAME_3_NL为空值
            color = get_color(town_data, '福州市区')
            
        # print(prov_name, city_name, town_name_zh, town_name_py)
        poly = Polygon(shape, facecolor=color, edgecolor=color, linewidth=1)
        axes.add_patch(poly) 
    
    # 绘制各区县的确诊数    
    # 将经纬度转换为笛卡尔坐标
    town_loc_list = {'福州市区': china_map(119.3, 26.08), 
                     # '鼓楼区': china_map(119.3, 26.08), 
                     # '台江区': china_map(119.3, 26.07),
                     # '仓山区': china_map(119.32, 26.05), 
                     # '马尾区': china_map(119.45, 26.0),  
                     # '晋安区': china_map(119.32, 26.08),
                     '闽侯县': china_map(119.13-0.2, 26.15),  
                     '连江县': china_map(119.53-0.1, 26.20+0.02),
                     '罗源县': china_map(119.55-0.2, 26.48),
                     '闽清县': china_map(118.85-0.2, 26.22), 
                     '永泰县': china_map(118.93-0.2, 25.87),
                     '福清市': china_map(119.38-0.2, 25.72),
                     '长乐市': china_map(119.52, 25.97)}   

    for key in town_loc_list.keys():  
        plt.text(town_loc_list[key][0], town_loc_list[key][1],
                 '{0}\n确诊{1}例'.format(key, town_data[key]), fontsize=12, fontweight='bold',
                        ha='left',va='center',color='black')
        
  
    plt.annotate('地图数据有错\n平潭不属福州|福清\n无数据不应着色', xy=china_map(119.78, 25.52),
                  xycoords='data',  arrowprops=dict(arrowstyle='->', color='black'), 
                  xytext=china_map(119.68, 25.80),  # 平潭经纬度 
                  fontsize=12, fontweight='bold',
                  ha='left',va='center',color='black')
plot_fz_dis_by_basemap()

遗留问题和作业

  1. 如果页面中的数据需要多个异步请求(ajax)才能拼接完全,应该怎么做?
  2. 封装个简易爬虫类(要求处理访问异常)。
  3. 用教材中的Pyechart来画地图会不会更美更森破,动手盘它。(下一节案例)

上一篇:用Python抓新型冠状病毒肺炎疫情数据,绘制全国疫情分布图,数据来源腾讯
上上篇:用Python抓新型冠状病毒肺炎实时数据,绘制省级疫情分布图,以福建为例,数据来源腾讯

发布了46 篇原创文章 · 获赞 13 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/shineych/article/details/104177997