├── .gitattributes ├── README.md ├── flask ├── app.py └── templates │ ├── image.html │ ├── index.html │ ├── jupyter_lab.html │ ├── jupyter_notebook.html │ ├── macro │ ├── nteract.html │ ├── simple_chart.html │ ├── simple_page.html │ └── table.html └── spider └── dxy.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PythonSARI 2 | Python疫情监控系统源码 3 | 4 | 配套视频课程地址:https://edu.csdn.net/course/play/23753 5 | 6 | -------------------------------------------------------------------------------- /flask/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template,jsonify 2 | from pyecharts import options as opts 3 | from pyecharts.charts import Map 4 | from pyecharts.charts import Line 5 | from pyecharts.globals import ChartType, SymbolType 6 | from pyecharts.globals import ThemeType 7 | 8 | from pyecharts.components import Table 9 | from pyecharts.options import ComponentTitleOpts 10 | import json 11 | 12 | app = Flask(__name__, static_folder="templates",) 13 | 14 | 15 | # 第一部分:图表创建 16 | # 01.实时疫情地图 17 | def map_base()->Map: 18 | # 数据暂时写死:后续可以结合实时数据,进行更新 19 | 20 | # 省和直辖市 21 | province_distribution = {'河南': 566, '北京': 212, '河北': 21, '辽宁': 12, '江西': 391, '上海': 203, '安徽': 408, '江苏': 271, 22 | '湖南': 521, '浙江': 724, '海南': 2, '广东': 725, '湖北': 11177, '黑龙江': 121, '澳门': 1, '陕西': 128, '四川': 254, 23 | '内蒙古': 3, '重庆': 312, '云南': 6, '贵州': 2, '吉林': 3, '山西': 12, '山东': 259, '福建': 179, '青海': 1, 24 | '天津': 1, '其他': 1} 25 | provice = list(province_distribution.keys()) 26 | values = list(province_distribution.values()) 27 | 28 | c = ( 29 | Map() 30 | .add("确诊人数", [list(z) for z in zip(provice,values)], "china") 31 | .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) 32 | .set_global_opts( 33 | title_opts=opts.TitleOpts(title="全国实时疫情分布地图"), 34 | visualmap_opts=opts.VisualMapOpts(max_= 1000),) 35 | ) 36 | return c 37 | 38 | 39 | # 02. 疫情新增趋势图 40 | def conf_new_base() -> Line: 41 | # 静态数据 42 | dataY1 = [59, 77, 149, 131, 256, 444, 688, 769, 1771, 1459, 1737, 1982, 2102, 2590, 2829, 3235, 3887] 43 | dataY2 = [59, 27, 149, 131, 680,1118,1309, 3806, 2077, 3248, 4148, 4812, 5019, 4562, 5173, 5072, 3971] 44 | dataX = ['2020.01.18', '2020.01.19', '2020.01.20', '2020.01.21', '2020.01.22','2020.01.23','2020.01.24','2020.01.25', 45 | '2020.01.26', '2020.01.27', '2020.01.28', '2020.01.29', '2020.01.30','2020.01.31','2020.02.01','2020.02.02', 46 | '2020.02.03', '2020.02.04'] 47 | c = ( 48 | Line(init_opts=opts.InitOpts(theme=ThemeType.LIGHT)) 49 | .add_xaxis(dataX) 50 | .add_yaxis("新增确诊", dataY1, is_smooth=True) 51 | .add_yaxis("新增疑似", dataY2, is_smooth=True) 52 | .set_global_opts( 53 | title_opts=opts.TitleOpts(title="全国疫情新增确诊/疑似趋势图"), 54 | yaxis_opts=opts.AxisOpts( 55 | type_="value", 56 | axistick_opts=opts.AxisTickOpts(is_show=True), 57 | splitline_opts=opts.SplitLineOpts(is_show=True), 58 | ), 59 | xaxis_opts=opts.AxisOpts(type_="category", boundary_gap=False), 60 | ) 61 | ) 62 | return c 63 | 64 | 65 | # 03. 全国累计确诊/疑似趋势图 66 | def conf_total_base() -> Line: 67 | # 静态数据 68 | dataY1 = [291, 440, 571, 830, 1287, 1975, 2744, 4515, 5974, 7711, 9692, 11791, 14380, 17205, 20438, 24324] 69 | dataY2 = [54, 37, 393, 1072, 1965, 2684, 5794, 6973, 9239, 12167, 15238, 17988, 19544, 21558,23214, 23260] 70 | 71 | dataX = ['2020.01.20', '2020.01.21', '2020.01.22', '2020.01.23', '2020.01.24', '2020.01.25', '2020.01.26', 72 | '2020.01.27', '2020.01.28', '2020.01.29', '2020.01.30', '2020.01.31', '2020.02.01', '2020.02.02', 73 | '2020.02.03', '2020.02.04'] 74 | c = ( 75 | Line(init_opts=opts.InitOpts(theme=ThemeType.LIGHT)) 76 | .add_xaxis(dataX) 77 | .add_yaxis("累计确诊", dataY1, is_smooth=True) 78 | .add_yaxis("累计疑似", dataY2, is_smooth=True) 79 | .set_global_opts( 80 | title_opts=opts.TitleOpts(title="全国疫情累计确诊/疑似趋势图"), 81 | yaxis_opts=opts.AxisOpts( 82 | type_="value", 83 | axistick_opts=opts.AxisTickOpts(is_show=True), 84 | splitline_opts=opts.SplitLineOpts(is_show=True), 85 | ), 86 | xaxis_opts=opts.AxisOpts(type_="category", boundary_gap=False), 87 | ) 88 | ) 89 | return c 90 | 91 | 92 | # 04. 全国累计死亡/治愈趋势图 93 | def dead_total_base() -> Line: 94 | # 静态数据 95 | dataY1 = [0, 9, 17, 25, 41, 56, 80, 106, 132, 170, 213, 259, 304, 361, 425, 490] 96 | dataY2 = [0, 0, 0, 34, 38, 49, 51, 60, 103, 124, 171, 243, 328, 475, 632, 892] 97 | 98 | dataX = ['2020.01.20', '2020.01.21', '2020.01.22', '2020.01.23', '2020.01.24', '2020.01.25', '2020.01.26', 99 | '2020.01.27', '2020.01.28', '2020.01.29', '2020.01.30', '2020.01.31', '2020.02.01', '2020.02.02', 100 | '2020.02.03','2020.02.04'] 101 | c = ( 102 | Line(init_opts=opts.InitOpts(theme=ThemeType.LIGHT)) 103 | .add_xaxis(dataX) 104 | .add_yaxis("累计死亡", dataY1, is_smooth=True) 105 | .add_yaxis("累计治愈", dataY2, is_smooth=True) 106 | .set_global_opts( 107 | title_opts=opts.TitleOpts(title="全国疫情累计死亡/治愈趋势图"), 108 | yaxis_opts=opts.AxisOpts( 109 | type_="value", 110 | axistick_opts=opts.AxisTickOpts(is_show=True), 111 | splitline_opts=opts.SplitLineOpts(is_show=True), 112 | ), 113 | xaxis_opts=opts.AxisOpts(type_="category", boundary_gap=False), 114 | ) 115 | ) 116 | return c 117 | 118 | 119 | # 05. 全国各省市数据明细 120 | def table_base(): 121 | table = Table() 122 | 123 | headers = ["地区", "确诊", "死亡", "治愈"] 124 | rows = [ 125 | ["湖北", 13522, 414, 397], 126 | ["浙江", 829, 0, 60], 127 | ["广东", 813, 0, 24], 128 | ["河南", 675, 2, 20], 129 | ["湖南", 593, 0, 29], 130 | ["安徽", 480, 0, 20], 131 | ["江西", 476, 2, 19], 132 | ["重庆", 344, 2, 9] 133 | ] 134 | table.add(headers, rows).set_global_opts( 135 | title_opts=ComponentTitleOpts(title="Table", subtitle="副标题") 136 | ) 137 | # table.render("table_base.html") 138 | return rows 139 | 140 | 141 | # 第二部分:路由配置 142 | @app.route("/") 143 | def index(): 144 | content = table_base() 145 | return render_template("index.html", content=content) 146 | 147 | 148 | @app.route("/mapChart") 149 | def get_map_chart(): 150 | c = map_base() 151 | return c.dump_options_with_quotes() 152 | 153 | 154 | @app.route("/confChart") 155 | def get_conf_chart(): 156 | c = conf_new_base() 157 | return c.dump_options_with_quotes() 158 | 159 | 160 | @app.route("/confTotalChart") 161 | def get_conf_total_chart(): 162 | c = conf_total_base() 163 | return c.dump_options_with_quotes() 164 | 165 | 166 | @app.route("/deadTotalChart") 167 | def get_dead_total_chart(): 168 | c = dead_total_base() 169 | return c.dump_options_with_quotes() 170 | 171 | 172 | # 主函数 173 | if __name__ == "__main__": 174 | app.run() 175 | -------------------------------------------------------------------------------- /flask/templates/image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ chart.page_title }} 6 | 7 | 8 | 9 |

{{ chart.title_opts.title }}

10 |

{{ chart.title_opts.subtitle }}

11 | {% for c in chart %} 12 | 13 | {% endfor %} 14 | 15 | 16 | -------------------------------------------------------------------------------- /flask/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 全国疫情监控完整项目实战 6 | 7 | 8 | 9 | 10 | 11 |
12 | 27 | 28 |
29 | 44 | 45 |
46 | 61 |
62 | 77 | 78 | 131 |
132 |

全国疫情数据分布

133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | {% for i in content %} 141 | 142 | {% for j in i %} 143 | 144 | {% endfor %} 145 | 146 | {% endfor %} 147 |
地区确诊死亡治愈
{{ j }}
148 |
149 | 150 | -------------------------------------------------------------------------------- /flask/templates/jupyter_lab.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% for c in charts %} 9 | {{ macro.render_chart_content(c) }} 10 | {% endfor %} 11 | 12 | 13 | -------------------------------------------------------------------------------- /flask/templates/jupyter_notebook.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | {% for chart in charts %} 10 |
11 | {% endfor %} 12 | 13 | 14 | 31 | -------------------------------------------------------------------------------- /flask/templates/macro: -------------------------------------------------------------------------------- 1 | {%- macro render_chart_content(c) -%} 2 |
3 | 20 | {%- endmacro %} 21 | 22 | {%- macro render_chart_dependencies(c) -%} 23 | {% for dep in c.dependencies %} 24 | 25 | {% endfor %} 26 | {%- endmacro %} 27 | -------------------------------------------------------------------------------- /flask/templates/nteract.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ macro.render_chart_dependencies(chart) }} 7 | 8 | 9 | {% for c in chart %} 10 | {{ macro.render_chart_content(c) }} 11 | {% endfor %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /flask/templates/simple_chart.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ chart.page_title }} 7 | {{ macro.render_chart_dependencies(chart) }} 8 | 9 | 10 | {{ macro.render_chart_content(chart) }} 11 | 12 | 13 | -------------------------------------------------------------------------------- /flask/templates/simple_page.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ chart.page_title }} 7 | {{ macro.render_chart_dependencies(chart) }} 8 | 9 | 10 | 11 |
12 | {% for c in chart %} 13 | {{ macro.render_chart_content(c) }} 14 | {% for _ in range(chart.page_interval) %}
{% endfor %} 15 | {% endfor %} 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /flask/templates/table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ chart.page_title }} 6 | 7 | 8 | 60 | 61 |

{{ chart.title_opts.title }}

62 |

{{ chart.title_opts.subtitle }}

63 | {% for c in chart %} 64 | {{ c }} 65 | {% endfor %} 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /spider/dxy.py: -------------------------------------------------------------------------------- 1 | from requests_html import HTMLSession 2 | import random 3 | import csv 4 | import json 5 | import datetime 6 | 7 | session = HTMLSession() 8 | USER_AGENTS = [ 9 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20", 10 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER", 11 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER", 12 | "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", 13 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)", 14 | "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)", 15 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)", 16 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1", 17 | "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5", 18 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre", 19 | "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0", 20 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11", 21 | "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10" 22 | ] 23 | 24 | 25 | def getTime(): 26 | curtime = datetime.datetime.now() 27 | strtime = curtime.strftime('_%Y_%m_%d_%H_%M_%S') 28 | return strtime 29 | 30 | 31 | class DxySARI(object): 32 | def __init__(self): 33 | self.start = 0 34 | # self.headers = {'User-Agent': 'Mozilla/5.0(Windows NT 6.1; WOW64)'} 35 | self.headers = {"User-Agent": random.choice(USER_AGENTS)} 36 | self.dxyurl = 'https://3g.dxy.cn/newh5/view/pneumonia' 37 | self.items = [] 38 | self.province_list = [] 39 | self.city_list = [] 40 | self.city_csv = '../data/city.csv' 41 | self.province_csv = '../data/province' 42 | 43 | # 页面数据 + 目标字符串 44 | def get_html_page(self): 45 | response = session.get(self.dxyurl) 46 | page = response.html.html 47 | # print(page) 48 | start = page.find("window.getAreaStat = [") 49 | # print(start) 50 | temp = page[start+22:] 51 | end = temp.find("]}catch(e){}") 52 | temp = temp[0:end] 53 | items = temp.split("]},") 54 | # 最后一个元素,特殊处理(尾部没有“,”号) 55 | last = items[-1] 56 | items.pop() 57 | newitems = [] 58 | for item in items: 59 | item = item + "]}" 60 | newitems.append(item) 61 | # print(item) 62 | newitems.append(last) 63 | 64 | self.items = newitems 65 | return newitems 66 | 67 | # 数据结构化 68 | def get_detail_info(self,items): 69 | # 格式转换:字符串->JSON 70 | for item in items: 71 | js = json.loads(item) 72 | # print(js) 73 | # 格式转换:JSON->字典 74 | dc = {} 75 | dc = dict(js) 76 | # 省份数据(含城市) 77 | self.province_list.append(dc) 78 | # 城市数据(前缀增加省份) 79 | # print(self.province_list) 80 | cities = dc["cities"] 81 | # 需要增加判断,处理列表为空的情况(直辖市和特区的问题) 82 | if cities: 83 | for city in cities: 84 | city["provinceName"] = dc["provinceName"] 85 | self.city_list.append(city) 86 | # print(city.keys()) 87 | # 保存至文件 88 | # print(self.province_list) 89 | # print(self.city_list) 90 | 91 | # 省份数据写入CSV:城市数据作为整体存储,不使用 92 | def write_province_csv(self): 93 | filename = self.province_csv + getTime() + '.csv' 94 | # print(filename) 95 | header = ['provinceName', 'provinceShortName', 'confirmedCount', 'suspectedCount', 'curedCount', 'deadCount', 'comment','cities'] 96 | with open(filename, 'w', newline='',encoding='utf-8-sig') as csvfile: 97 | file_pro = csv.writer(csvfile) 98 | file_pro.writerow(header) 99 | try: 100 | for item in self.province_list: 101 | print(item.values()) 102 | file_pro.writerow(item.values()) 103 | except Exception as e: 104 | print('Province数据写入异常' + e + '异常') 105 | 106 | # 城市数据写入CSV 107 | def write_city_csv(self): 108 | filename = self.city_csv + getTime() + '.csv' 109 | print(filename) 110 | header = [['cityName', 'confirmedCount', 'suspectedCount', 'curedCount', 'deadCount', 'provinceName']] 111 | with open(filename, 'w', newline='',encoding='utf-8-sig') as csvfile: 112 | file_city = csv.writer(csvfile) 113 | file_city.writerow(header) 114 | try: 115 | for item in self.city_list: 116 | print(item.values()) 117 | file_city.writerow(item.values()) 118 | except Exception as e: 119 | print('City数据写入异常' + e + '异常') 120 | 121 | 122 | # 主函数 123 | if __name__ == '__main__': 124 | dxy = DxySARI() 125 | page = dxy.get_html_page() 126 | dxy.get_detail_info(page) 127 | #dxy.write_province_csv() 128 | #dxy.write_city_csv() 129 | 130 | 131 | --------------------------------------------------------------------------------