在本文中,分析以“春雨医生”平台作为数据来源,通过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家,或许是历史和地理上的原因,但也说明深圳的医疗水平在国内来讲算不上是顶尖的。
从整体上看,顶尖医院主要分布在一线及新一线城市,少部分在二线城市,而二线外的城市均没有分布。此外,几乎所有顶尖医院都是三级甲等医院。
最后,将维度细分到科室的话,可以看到北京医疗水平是最顶尖的,覆盖了所有科室。上海和广州的医疗水平也位列前茅。新一线城市中,成都、杭州、南京和武汉这四个城市科室覆盖情况也很丰富,单从这里看的话,可以说医疗水平远超深圳,因为深圳只有心理科一个科室上榜。济南市作为一个二线城市,顶尖科室水平也较为不错。
结尾
此次分析仅从顶尖医院的数量来判断城市的医疗水平,采用的数据维度较少,分析不够全面,但科室这一维度来看,结果有一定的实践意义。例如有一鼻炎患者在深圳,就可以选择过去临近城市广州去看鼻科,因为那边有一家国内顶尖的医院,如此类推。也希望此次分析结果能帮助到有需要的人。
案例源码加群获取哦:850591259