Python 数据分析实战,揭秘国内顶尖医院分布现状!

作者 | AJ Gordon、June Alice

责编 | 屠敏

出品 | CSDN(ID:CSDNnews)

声明:本文仅作学习与交流。

在本文中,分析以“春雨医生”平台作为数据来源,通过Python抓取网站数据,结合“地市等级划分”数据,并再利用tableau制作地图和图表进行数据可视化。本文抓取的数据内容主要是全国范围内不同科室的TOP10医院名单,通过展示不同地区医院上榜的数量,以了解目前国内顶尖医疗水平的城市分布情况。由于本文采用的数据维度较少,结论仅供参考作用。

数据采集

首先,导入所需的库,再构建两个字典,一个存放地区编码,一个存放科室编码。利用两个嵌套for循环构建URL后,将全部URL存放到Redis数据库中,进行下一步操作。

import requests
from bs4 import BeautifulSoup
import pymysql
from concurrent.futures import ThreadPoolExecutor as Pool
from redis import ConnectionPool, Redis
import warnings
warnings.filterwarnings("ignore")
# 地区字典
area_dict = {
    '全国': '0',
    '黑龙江省': '230000',
    '吉林省': '220000',
    '辽宁省': '210000',
    '河南省': '410000',
    '湖北省': '420000',
    '湖南省': '430000',
    '四川省': '510000',
    '贵州省': '520000',
    '云南省': '530000',
    '重庆市': '500000',
    '西藏自治区': '540000',
    '陕西省': '610000',
    '甘肃省': '620000',
    '青海省': '630000',
    '宁夏回族自治区': '640000',
    '新疆维吾尔自治区': '650000',
    '上海市': '310000',
    '江苏省': '320000',
    '浙江省': '330000',
    '安徽省': '340000',
    '福建省': '350000',
    '江西省': '360000',
    '山东省': '370000',
    '台湾省': '710000',
    '北京市': '110000',
    '天津市': '120000',
    '山西省': '130000',
    '河北省': '140000',
    '内蒙古自治区': '150000',
    '广东省': '440000',
    '广西壮族自治区': '450000',
    '海南省': '460000',
    '香港特别行政区': '810000',
    '澳门特别行政区': '820000',
}
# 科室字典
department_dict = {
    '妇科':'1',
    '儿科-小儿科':'fa',
    '儿科-新小儿科':'fb',
    '皮肤性病科-皮肤科':'ha',
    '皮肤性病科-性病科':'hb',
    '内科-呼吸内科':'aa',
    '内科-心血管内科': 'ab',
    '内科-神经内科': 'ac',
    '内科-消化内科': 'ad',
    '内科-肾内科': 'ae',
    '内科-内分泌与代谢科': 'af',
    '内科-风湿免疫科': 'ag',
    '内科-血液病科': 'ah',
    '内科-感染科': 'ai',
    '男科':'8',
    '产科':'21',
    '外科-胸外科':'ba',
    '外科-心脏与血管外科': 'bb',
    '外科-神经外科': 'bc',
    '外科-肝胆外科': 'bd',
    '外科-烧伤科': 'be',
    '外科-康复科': 'bf',
    '外科-泌尿外科': 'bg',
    '外科-肛肠科': 'bh',
    '外科-普外科': 'bi',
    '外科-甲状腺乳腺外科': 'bj',
    '中医科-中医内科':'oa',
    '中医科-中医外科': 'ob',
    '中医科-中医妇科': 'oc',
    '中医科-中医男科': 'od',
    '中医科-中医儿科': 'oe',
    '骨伤科-脊柱科':'ca',
    '骨伤科-关节科': 'cb',
    '骨伤科-创伤科': 'cc',
    '精神心理科-精神科': 'na',
    '精神心理科-心理科':'nb',
    '口腔颌面科':'13',
    '眼科':'15',
    '耳鼻咽喉科-耳科':'ja',
    '耳鼻咽喉科-鼻科': 'jb',
    '耳鼻咽喉科-咽喉科': 'jc',
    '肿瘤及防治科-肿瘤内科': 'ma',
    '肿瘤及防治科-肿瘤外科': 'mb',
    '肿瘤及防治科-介入与放疗中心': 'mc',
    '肿瘤及防治科-肿瘤中医科':'md',
    '整形美容科':'16',
    '综合':'0',
}
# 收集url
def get_url():
    url = 'https://www.chunyuyisheng.com/pc/hospitallist/{}/{}/'
    for key1, value1 in area_dict.items():
        for key2, value2 in department_dict.items():
            url_new = url.format(value1, value2)
            redis_db.sadd('url_hospital', url_new)
    return None

然后,访问Redis数据库读取URL,利用ThreadPoolExecutor多线程抓取。抓取过程中有几点需要注意的:首先,访问频率过快的话,会出现503编码,解决办法是先跳过,稍后再访问;其次部分页面是无数据的,如果不进行剔除,会导致抓取过程卡住不动;此外有部分页面的医院所在城市标签为None,若不进行异常处理,也会导致卡住。最后,将抓取结果存入Mysql。

# 读取URL_1
def load_url_1():
    crawl_url_list = redis_db.smembers('url_hospital')
    while len(crawl_url_list) > 0:
        with Pool() as executor:
            futures = [executor.submit(get_content_1, crawl_url) for crawl_url in crawl_url_list]
        crawl_url_list = redis_db.smembers('url_hospital')
# 爬取Content_1
def get_content_1(crawl_url):
    conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='XX', db='analysis_data',
                           charset='utf8', cursorclass=pymysql.cursors.DictCursor)
    r = requests.get(crawl_url, headers=headers)
    # 由于访问频率过高会导致服务器报错503,可先跳过,稍后再访问
    if r.status_code==503:
        print(r.status_code)
    # 由于部分网址的医院显示为无数据,需剔除这部分网址
    elif '暂无数据' in r.text:
        print('此页无数据。。。')
        redis_db.srem('url_hospital', crawl_url)
    else:
        soup = BeautifulSoup(r.text, 'lxml')
        all_content = soup.find_all('div', {'class': 'content'})[-1]
        # 获取医院详情页url
        all_href = all_content.find_all('a')
        detail_url = ['https://www.chunyuyisheng.com/' + i['href'] for i in all_href]
        # 获取医院其它信息
        content_item = all_content.find_all('div', {'class': 'content-item'})
        for number, content in enumerate(content_item):
            hospital = content.find('div', {'class': 'top-title'}).get_text().strip()
            position_province = content.find('div', {'class': 'right-position'}).get_text().strip().split(' ')[0]
            # 由于部分医院的城市标签为None,可设置为None
            try:
                position_city = content.find('div', {'class': 'right-position'}).get_text().strip().split(' ')[1]
            except:
                position_city = ''
            # 通过值找键,返回区域和科室
            area = crawl_url.split('/')[-3]
            department = crawl_url.split('/')[-2]
            def get_key(dict, value):
                return [k for k, v in dict.items() if v == value]
            area = get_key(area_dict, area)[0]
            department = get_key(department_dict, department)[0]
            # 由于部分医院的排名标签显示为None,且排名存在规律,可手工构建医院排名,保证排名字段不为空
            rank = area + department + '排名第' + str(number + 1)
            # 构建存储列表
            data = [area, department, hospital, rank, position_province, position_city,detail_url[number]]
            # 存入Mysql
            with conn.cursor() as cursor:
                sql1 = "create table if not exists `%s`(区域 varchar(255),科室 varchar(255),医院 varchar(255),排名 varchar(255),省份 varchar(255),城市 varchar(255),详细URL varchar(255)) character set utf8" % (
                    'hospital_rank')
                cursor.execute(sql1)
                sql = "insert into hospital_rank(区域,科室,医院,排名,省份,城市,详细URL) values(%s,%s,%s,%s,%s,%s,%s)"
                cursor.execute(sql, data)
                conn.commit()
            print(data)
            redis_db.sadd('detail_url', detail_url[number])
            redis_db.srem('url_hospital', crawl_url)
    conn.close()

最后,抓取详情页中的医院等级资质,步骤与上面一样,结果存入Mysql中。

# 读取URL_2
def load_url_2():
    crawl_url_list = redis_db.smembers('detail_url')
    while len(crawl_url_list) > 0:
        with Pool() as executor:
            futures = [executor.submit(get_content_2, crawl_url) for crawl_url in crawl_url_list]
        crawl_url_list = redis_db.smembers('detail_url')
# 爬取Content_2
def get_content_2(crawl_url):
    conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='xx', db='analysis_data',
                           charset='utf8', cursorclass=pymysql.cursors.DictCursor)
    r = requests.get(crawl_url, headers=headers)
    # 由于访问频率过高会导致服务器报错503,可先跳过,稍后再访问
    if r.status_code==503:
        print(r.status_code)
    else:
        soup = BeautifulSoup(r.text, 'lxml')
        hospital = soup.find('div', {'class': 'content-title'}).find('h3', {'class': 'title'}).get_text().strip()
        hospital_qualification = soup.find('div', {'class': 'content-title'}).find('span', {'class': 'label'}).get_text().strip()
        # 构建存储列表
        data = [hospital, hospital_qualification]
        # 存入Mysql
        with conn.cursor() as cursor:
            sql1 = "create table if not exists `%s`(医院 varchar(255),医院资质 varchar(255)) character set utf8" % ('hospital_qualification')
            cursor.execute(sql1)
            sql = "insert into hospital_qualification(医院,医院资质) values(%s,%s)"
            cursor.execute(sql, data)
            conn.commit()
        print(data)
        redis_db.srem('detail_url', crawl_url)
    conn.close()

数据处理

数据采集后,需要对数据进行清洗。先读取采集后的医院排名和资质数据,以及地市等级划分数据,然后无用字段可以剔除掉,可拆字段需进行拆分,合并多表后的结果,需检查字段的缺失值情况,并判断是否可进行填充。最后,由于展示结果中涉及到中国地图,由于tableau将台湾归为“国家”地理编码角色,需要手工追加一行记录,以便后期作图时能显示台湾部分。

import pandas as pd
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
# 加载数据
open_filepath = 'D:\pythondata\春雨医生\excel\\{}'
hospital_rank = pd.read_excel(open_filepath.format('hospital_rank.xlsx'),sheet_name='hospital_rank')
hospital_qualification = pd.read_excel(open_filepath.format('hospital_qualification.xlsx'),sheet_name='hospital_qualification')
city_grading = pd.read_excel(open_filepath.format('city_grading.xlsx'),sheet_name='city_grading')
province_area = pd.read_excel(open_filepath.format('city_grading.xlsx'),sheet_name='province_area')
# 处理数据
## 剔除无用字段
hospital_rank = hospital_rank.drop('详细URL',axis=1)
city_grading = city_grading.drop('排名',axis=1)
## 拆分字段
hospital_rank['科室1'] = hospital_rank.apply(lambda x:x['科室'].split('-')[0] if '-' in x['科室'] else x['科室'],axis=1)
hospital_rank['科室2'] = hospital_rank.apply(lambda x:x['科室'].split('-')[1] if '-' in x['科室'] else x['科室'],axis=1)
## 合并多表
result = pd.merge(hospital_rank,hospital_qualification,on='医院',how='left')
result = pd.merge(result,city_grading,on='城市',how='left')
result = pd.merge(result,province_area,on='省份',how='left')
## 填充字段
result['等级'] = result.apply(lambda x: '四/五线城市' if pd.isnull(x['等级']) else x['等级'],axis=1)
##
additional = pd.DataFrame({'区域':['全国'],'省份':['台湾省'],'城市':['台湾'],'地区':['华东']})
result = pd.concat([additional,result])
## 保存结果
result.to_csv(open_filepath.format('result.csv'),index=0, encoding="utf_8_sig")

数据可视化

首先,从地区维度去看,全国不同科室TOP10医院主要集中分布在华东和华北地区,华南和华中数量也不少。再从省份维度展示,主要分布在北京和上海,广东、天津、江苏和浙江分布数量也较多。

接着,从城市维度展示,可以看出大部分顶尖医院集中分布在北京、上海和广州这三座一线城市,南京、杭州和天津等新一线城市也占比不少。值得关注的是深圳作为一线城市,上榜的医院仅有1家,或许是历史和地理上的原因,但也说明深圳的医疗水平在国内来讲算不上是顶尖的。

从整体上看,顶尖医院主要分布在一线及新一线城市,少部分在二线城市,而二线外的城市均没有分布。此外,几乎所有顶尖医院都是三级甲等医院。

最后,将维度细分到科室的话,可以看到北京医疗水平是最顶尖的,覆盖了所有科室。上海和广州的医疗水平也位列前茅。新一线城市中,成都、杭州、南京和武汉这四个城市科室覆盖情况也很丰富,单从这里看的话,可以说医疗水平远超深圳,因为深圳只有心理科一个科室上榜。济南市作为一个二线城市,顶尖科室水平也较为不错。

结尾

此次分析仅从顶尖医院的数量来判断城市的医疗水平,采用的数据维度较少,分析不够全面,但科室这一维度来看,结果有一定的实践意义。例如有一鼻炎患者在深圳,就可以选择过去临近城市广州去看鼻科,因为那边有一家国内顶尖的医院,如此类推。也希望此次分析结果能帮助到有需要的人。

作者:AJ Gordon,对爬虫/机器学习/数据建模/可视化均有所涉猎的数据分析师;June Alice,在读研究僧一枚,跨行新手数据分析师。

【End】

《原力计划【第二季】- 学习力挑战》正式开始!
即日起至 3月21日,千万流量支持原创作者,更有专属【勋章】等你来挑战

推荐阅读 

6 岁学编程,9 岁给电脑杂志社撰稿,19 岁收月薪 2 万 的 Offer | 程序人生 2020

技术大佬:我去,你写的 switch 语句也太老土了吧!

赔偿谷歌1.8亿美元!前Uber自动驾驶主管被告到破产

乔布斯遗孀裸捐 250 亿美元财产:没兴趣累积财富

时间复杂度的表示、分析、计算方法……一文带你看懂时间复杂度!

闪电网络的 5 个优点和4 个缺点、本质、来源与工作原理……一文带你读懂闪电网络!

你点的每一个在看,我认真当成了喜欢

发布了1802 篇原创文章 · 获赞 4万+ · 访问量 1627万+

猜你喜欢

转载自blog.csdn.net/csdnnews/article/details/104726086