Python 数据分析实战,揭秘国内顶尖医院分布现状!
声明:本文仅作学习与交流。
在本文中,分析以“春雨医生”平台作为数据来源,通过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,在读研究僧一枚,跨行新手数据分析师。
即日起至 3月21日,千万流量支持原创作者,更有专属【勋章】等你来挑战
☞时间复杂度的表示、分析、计算方法……一文带你看懂时间复杂度!
☞闪电网络的 5 个优点和4 个缺点、本质、来源与工作原理……一文带你读懂闪电网络!