在Github仓库中新建一个学号为名的文件夹,同时在博客正文首行给出作业Github链接,并登录软工在线平台完善信息。
Github:[https://github.com/Elisa044/2022softerwear_work]
一、PSP表格
1.1 在开始实现程序之前,在附录提供的PSP表格记录下你估计将在程序的各个模块的开发上耗费的时间。
1.2 在你实现完程序之后,在附录提供的PSP表格记录下你在程序的各个模块上实际花费的时间。
PSP1.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | 30 | 25 |
·Estimate | 估计这个任务需要多少时间 | 520 | 815 |
Development | 开发 | 480 | 500 |
· Analysis | 需求分析 (包括学习新技术) | 300 | 380 |
· Design Spec | 生成设计文档 | 120 | 100 |
· Design Review | 设计复审 | 180 | 100 |
· Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 120 | 80 |
· Design | 具体设计 | 180 | 150 |
· Coding | 具体编码 | 180 | 360 |
· Code Review | 代码复审 | 120 | 60 |
· Test | 测试(自我测试,修改代码,提交修改) | 240 | 300 |
Reporting | 报告 | 150 | 150 |
· Test Repor | 测试报告 | 100 | 150 |
· Size Measurement | 计算工作量 | 15 | 20 |
· Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 2765 | 3220 |
二、任务要求的实现
2.1 项目设计与技术栈。从阅读完题目到完成作业,这一次的任务被你拆分成了几个环节?你分别通过什么渠道、使用什么方式方法完成了各个环节?列出你完成本次任务所使用的技术栈。
答:本次任务我分为四个环节,主要为规划任务、具体编码、测试编码、总结报告。规划任务我主要借鉴了ppt上的PSP表格,初步估计了每个任务所需时间。具体编码我主要通过哔哩哔哩、慕课、csdn等网站学习了爬虫的相关知识,了解了爬虫的基本流程和方法,参考了爬虫的经典案例。初步了解爬虫后,我开始编写代码。在确定要实现获取网页数据、导入Excel表、数据可视化、省份搜索四个功能后,我开始对其一个一个编写改错。测试编码时,我主要通过查询网页资料、查询函数、方法的使用方法改正编码错误,规范编码准确性。总结报告我主要通过撰写博客的方式,通过文字、图片、代码的方式总结整个实践过程,总结爬虫的流程、学习数据可视化的方法、反思自身能力的不足。
技术栈:request获取数据、matplotlib数据可视化、pyecharts数据可视化、json转换数据格式。
2.2 爬虫与数据处理。说明业务逻辑,简述代码的设计过程(例如可介绍有几个类,几个函数,他们之间的关系),并对关键的函数或算法进行说明。
答:具体编码我主要按照四个功能进行编写,分别为爬取数据、导入Excel、数据可视化、查询省份数据。
2.2.1 爬取数据
1)创建函数get_data_html获取网页数据
2)利用requests请求网址页面
3)对获取到的数据进行重新编码
4)返回数据
def get_data_html():
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 '
'Safari/535.1'}
# 请求网址页面
response = requests.get('https://ncov.dxy.cn/ncovh5/view/pneumonia?from=timeline&isappinstalled=0', headers=headers,
timeout=3)
# 重新编码
response = str(response.content, 'utf-8')
# 返回数据
return response
5)get_data_dictype函数利用json转换数据格式
def get_data_dictype():
areas_type_dic_raw = re.findall('try { window.getAreaStat = (.*?)}catch\(e\)', get_data_html())
areas_type_dic = json.loads(areas_type_dic_raw[0])
# 返回json转换过后的数据
return areas_type_dic
6)创建make_dir函数检查数据保存路径,若目标文件夹不存在,则创建目标文件夹。
#检查数据目标路径
def make_dir():
# 检查数据路径
file_path = 'C:/Users/Dell/PycharmProjects/pythonProject/中国疫情最新信息'
# 若数据路径不存在
if not os.path.exists(file_path):
print('数据目标文件夹不存在')
#创建数据路径
os.makedirs(file_path)
print('数据目标文件夹创建成功,文件夹为%s' % (file_path))
else:
#数据路径已存在
print('数据目标文件夹为:%s ' % (file_path))
7)通过get_data_html、get_data_dictype、make_dir三个函数成功爬取并保存网页数据。
2.2.2 导入Excel
- 创建函数createxcelsheet实现Excel表的创建,并写入数据列标签。
def createxcelsheet(workbook, name):
worksheet = workbook.add_sheet(name, cell_overwrite_ok=True)
for i in range(0, 9):
worksheet.col(i).width = 256 * 15
al = xlwt.Alignment()
al.horz = 0x02 # 设置水平居中
style = xlwt.XFStyle()
style.alignment = al
# 写入数据列标签
worksheet.write(0, 0, '城市名称', style)
worksheet.write(0, 1, '现存确诊人数', style)
worksheet.write(0, 2, '累计确诊人数', style)
worksheet.write(0, 3, '疑似人数', style)
worksheet.write(0, 4, '治愈人数', style)
worksheet.write(0, 5, '死亡人数', style)
worksheet.write(0, 6, '地区ID编码', style)
return worksheet, style
2)定义label和values两个变量为全局变量,表示为中国省份名称和累计确诊人数。
# 获得中国省份名称和累计确诊人数
global label, values
label = []
values = []
3)创建函数save_data_to_excel保存excel数据,通过for循环保存中国各个省份疫情数据。
#将数据导入在Excel中
def save_data_to_excel():
#创建文件夹
make_dir()
# 打开工作簿 创建工作表
newworkbook = xlwt.Workbook()
sheet, style = createxcelsheet(newworkbook, '中国各省')
count = 1 # 中国各省的计数器
for province_data in get_data_dictype():
c_count = 1 # 某省的计数器
provincename = province_data['provinceName']
provinceshortName = province_data['provinceShortName']
p_currentconfirmedCount = province_data['currentConfirmedCount']
p_confirmedcount = province_data['confirmedCount']
p_suspectedcount = province_data['suspectedCount']
p_curedcount = province_data['curedCount']
p_deadcount = province_data['deadCount']
p_locationid = province_data['locationId']
# 用循环获取省级以及该省以下城市的数据
label.append(provincename)
values.append(p_confirmedcount)
sheet.write(count, 0, provincename, style)
sheet.write(count, 1, p_currentconfirmedCount, style)
sheet.write(count, 2, p_confirmedcount, style)
sheet.write(count, 3, p_suspectedcount, style)
sheet.write(count, 4, p_curedcount, style)
sheet.write(count, 5, p_deadcount, style)
sheet.write(count, 6, p_locationid, style)
count += 1
worksheet, style = createxcelsheet(newworkbook, provincename)
worksheet.write(c_count, 0, provinceshortName, style)
worksheet.write(c_count, 1, p_currentconfirmedCount, style)
worksheet.write(c_count, 2, p_confirmedcount, style)
worksheet.write(c_count, 3, p_suspectedcount, style)
worksheet.write(c_count, 4, p_curedcount, style)
worksheet.write(c_count, 5, p_deadcount, style)
worksheet.write(c_count, 6, p_locationid, style)
# 在工作表里写入省级数据
c_count += 2 # 省与省下各城市之间空一行
for citiy_data in province_data['cities']:
# 该部分获取某个省下某城市的数据
cityname = citiy_data['cityName']
c_currentconfirmedCount = citiy_data['currentConfirmedCount']
c_confirmedcount = citiy_data['confirmedCount']
c_suspectedcount = citiy_data['suspectedCount']
c_curedcount = citiy_data['curedCount']
c_deadcount = citiy_data['deadCount']
c_locationid = citiy_data['locationId']
# 向Excel对应列标写入数据
worksheet.write(c_count, 0, cityname, style)
worksheet.write(c_count, 1, c_currentconfirmedCount, style)
worksheet.write(c_count, 2, c_confirmedcount, style)
worksheet.write(c_count, 3, c_suspectedcount, style)
worksheet.write(c_count, 4, c_curedcount, style)
worksheet.write(c_count, 5, c_deadcount, style)
worksheet.write(c_count, 6, c_locationid, style)
c_count += 1 # 此处为写入行数累加,在cities部分循环
4)结果展示:
2.2.3 数据可视化
1)通过工具对数据进行可视化,做出中国疫情确诊人数前5省的省份饼状图和中国疫情感染人数分布图。
2)创建函数sjvisual实现中国疫情确诊人数前5省饼状图
def sjvisual():
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(7, 6))
z = zip(label, values)
s = sorted(z, key=lambda x: x[1], reverse=True)
top5 = s[0:5:]
labels = (dict(top5)).keys()
values1 = (dict(top5)).values()
# 绘制饼图
plt.pie(values1, labels=labels, radius=1.2, pctdistance=0.8, autopct='%1.2f%%')
# 保存饼图
plt.savefig('C:/Users/Dell/PycharmProjects/pythonProject/中国疫情信息/中国疫情确诊人数前五省饼状图.png')
plt.show()
3)创建函数Map实现中国疫情累计感染人数分布图
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(7, 6))
label = []
values= []
x = [] # 把各省感染人数与各省对应
for i in range(len()):
label.append()
values.append()
for z in zip(list(label), list(values)):
list(z)
x.append(z)
area_map = Map()
area_map.add("中国疫情感染人数分布图", x, "china", is_map_symbol_show=False)
area_map.set_global_opts(title_opts=opts.TitleOpts(title="中国疫情累计感染人数分布地图"),
visualmap_opts=opts.VisualMapOpts(is_piecewise=True,
pieces=[
{"min": 1500, "label": '>10000人',
"color": "#6F171F"},
{"min": 500, "max": 15000, "label": '500-1000人',
"color": "#C92C34"},
{"min": 100, "max": 499, "label": '100-499人',
"color": "#E35B52"},
{"min": 10, "max": 99, "label": '10-99人',
"color": "#F39E86"},
{"min": 1, "max": 9, "label": '1-9人',
"color": "#FDEBD0"}]))
area_map.render_notebook()
area_map.savefig('C:/Users/Dell/PycharmProjects/pythonProject/中国疫情信息/中国疫情累计感染人数分布图.png')
area_map.show()
2.2.4 每日热点
我查询的是每个省份疫情情况的GUI界面
1)创建函数GUI制作GUI界面
# 制作GUI界面
def GUI():
global win
win = tkinter.Tk()
win.minsize(800, 660)
win.title('中国各省疫情情况查询')
tkinter.Label(win, text='请选择省份:', height=1, width=10).place(x=200, y=0)
tkinter.Label(win, text='省份:').place(x=240, y=40)
tkinter.Label(win, text='现存确诊人数:').place(x=240, y=70)
tkinter.Label(win, text='累计确诊人数:').place(x=240, y=100)
tkinter.Label(win, text='疑似人数:').place(x=240, y=130)
tkinter.Label(win, text='治愈人数:').place(x=240, y=160)
tkinter.Label(win, text='死亡人数:').place(x=240, y=190)
# 将下拉列表框定义为全局变量
global comboxlist
comvalue = tkinter.StringVar()
# 初始化
comboxlist = ttk.Combobox(win, textvariable=comvalue)
comboxlist["values"] = label
comboxlist.current(0)
comboxlist.bind("<<ComboboxSelected>>", go)
comboxlist.pack()
# 进入消息循环
win.mainloop()
2)创建函数go,利用if语句和for循环设置界面功能。
# 处理事件,*args表示可变参数
def go(*args):
for i in tst:
i.place_forget()
# 被选中的省份
c = comboxlist.get()
for province_data in get_data_dictype():
provincename = province_data['provinceName']
if c == provincename:
tkinter.Label(win, text=provincename, height=1, width=15).place(x=400, y=40)
tkinter.Label(win, text=province_data['currentConfirmedCount'], height=1, width=10).place(x=400, y=70)
tkinter.Label(win, text=province_data['confirmedCount'], height=1, width=10).place(x=400, y=100)
tkinter.Label(win, text=province_data['suspectedCount'], height=1, width=10).place(x=400, y=130)
tkinter.Label(win, text=province_data['curedCount'], height=1, width=10).place(x=400, y=160)
tkinter.Label(win, text=province_data['deadCount'], height=1, width=10).place(x=400, y=190)
tkinter.Label(win, text='\t请选择' + provincename + '下地区:', height=1, width=20).place(x=50, y=220)
# 设置城市按钮的位置
lt = [(15, 240), (115, 240), (215, 240), (315, 240), (415, 240), (515, 240), (615, 240), (715, 240),
(15, 280), (115, 280), (215, 280), (315, 280), (415, 280), (515, 280), (615, 280), (715, 280),
(15, 320), (115, 320), (215, 320), (315, 320), (415, 320), (515, 320), (615, 320), (715, 320),
(15, 360), (115, 360), (215, 360), (315, 360), (415, 360), (515, 360), (615, 360), (715, 360),
(15, 400), (115, 400), (215, 400), (315, 400), (415, 400), (515, 400), (615, 400), (715, 400)]
# 按钮位置计数器
ct = 0
for city_data in province_data['cities']:
while ct < len(province_data['cities']):
b = Button(win, text=city_data['cityName'], height=1, width=10)
# 装入按钮
tst.append(b)
b.place(x=lt[ct][0], y=lt[ct][1])
# 当按钮被选中,绑定show函数
b.bind('<Button-1>', show)
ct += 1
break
tkinter.Label(win, text='地区:').place(x=240, y=435)
tkinter.Label(win, text='现存确诊人数:').place(x=240, y=465)
tkinter.Label(win, text='累计确诊人数:').place(x=240, y=495)
tkinter.Label(win, text='疑似人数:').place(x=240, y=525)
tkinter.Label(win, text='治愈人数:').place(x=240, y=555)
tkinter.Label(win, text='死亡人数:').place(x=240, y=585)
3)创建函数show,显示GUI界面数据
# 显示对应城市数据
def show(event):
# comboxlist.get()得到查询的省份
c = comboxlist.get()
# 获得被选中城市(按钮)名称
for province_data in get_data_dictype():
provincename = province_data['provinceName']
if c == provincename:
for city_data in province_data['cities']:
# event.widget[‘text’]能得到查询的省下地区
if city_data['cityName'] == event.widget['text']:
tkinter.Label(win, text=city_data['cityName'], height=1, width=15).place(x=400, y=435)
tkinter.Label(win, text=city_data['currentConfirmedCount'], height=1, width=15).place(x=400, y=465)
tkinter.Label(win, text=city_data['confirmedCount'], height=1, width=15).place(x=400, y=495)
tkinter.Label(win, text=city_data['suspectedCount'], height=1, width=15).place(x=400, y=525)
tkinter.Label(win, text=city_data['curedCount'], height=1, width=15).place(x=400, y=555)
tkinter.Label(win, text=city_data['deadCount'], height=1, width=15).place(x=400, y=585)
2.3 数据统计接口部分的性能改进。记录在数据统计接口的性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(例如可通过VS 2019/JProfiler的性能分析工具自动生成),并展示你程序中消耗最大的函数。
答:程序中消耗最大的函数:go()
def go(*args):
for i in tst:
i.place_forget()
# 被选中的省份
c = comboxlist.get()
for province_data in get_data_dictype():
provincename = province_data['provinceName']
if c == provincename:
tkinter.Label(win, text=provincename, height=1, width=15).place(x=400, y=40)
tkinter.Label(win, text=province_data['currentConfirmedCount'], height=1, width=10).place(x=400, y=70)
tkinter.Label(win, text=province_data['confirmedCount'], height=1, width=10).place(x=400, y=100)
tkinter.Label(win, text=province_data['suspectedCount'], height=1, width=10).place(x=400, y=130)
tkinter.Label(win, text=province_data['curedCount'], height=1, width=10).place(x=400, y=160)
tkinter.Label(win, text=province_data['deadCount'], height=1, width=10).place(x=400, y=190)
tkinter.Label(win, text='\t请选择' + provincename + '下地区:', height=1, width=20).place(x=50, y=220)
# 设置城市按钮的位置
lt = [(15, 240), (115, 240), (215, 240), (315, 240), (415, 240), (515, 240), (615, 240), (715, 240),
(15, 280), (115, 280), (215, 280), (315, 280), (415, 280), (515, 280), (615, 280), (715, 280),
(15, 320), (115, 320), (215, 320), (315, 320), (415, 320), (515, 320), (615, 320), (715, 320),
(15, 360), (115, 360), (215, 360), (315, 360), (415, 360), (515, 360), (615, 360), (715, 360),
(15, 400), (115, 400), (215, 400), (315, 400), (415, 400), (515, 400), (615, 400), (715, 400)]
# 按钮位置计数器
ct = 0
for city_data in province_data['cities']:
while ct < len(province_data['cities']):
b = Button(win, text=city_data['cityName'], height=1, width=10)
# 装入按钮
tst.append(b)
b.place(x=lt[ct][0], y=lt[ct][1])
# 当按钮被选中,绑定show函数
b.bind('<Button-1>', show)
ct += 1
break
tkinter.Label(win, text='地区:').place(x=240, y=435)
tkinter.Label(win, text='现存确诊人数:').place(x=240, y=465)
tkinter.Label(win, text='累计确诊人数:').place(x=240, y=495)
tkinter.Label(win, text='疑似人数:').place(x=240, y=525)
tkinter.Label(win, text='治愈人数:').place(x=240, y=555)
tkinter.Label(win, text='死亡人数:').place(x=240, y=585)
2.4 每日热点的实现思路。简要介绍实现该功能的算法原理,可给出必要的步骤流程图、数学公式推导和核心代码实现,并简要谈谈所采用算法的优缺点与可能的改进方案。
答:1.创建函数GUI,制作GUI界面
def GUI():
global win
win = tkinter.Tk()
win.minsize(800, 660)
win.title('中国各省疫情情况查询')
tkinter.Label(win, text='请选择省份:', height=1, width=10).place(x=200, y=0)
tkinter.Label(win, text='省份:').place(x=240, y=40)
tkinter.Label(win, text='现存确诊人数:').place(x=240, y=70)
tkinter.Label(win, text='累计确诊人数:').place(x=240, y=100)
tkinter.Label(win, text='疑似人数:').place(x=240, y=130)
tkinter.Label(win, text='治愈人数:').place(x=240, y=160)
tkinter.Label(win, text='死亡人数:').place(x=240, y=190)
2.定义全局变量,初始化对象。
# 将下拉列表框定义为全局变量
global comboxlist
comvalue = tkinter.StringVar()
# 初始化
comboxlist = ttk.Combobox(win, textvariable=comvalue)
comboxlist["values"] = label
comboxlist.current(0)
comboxlist.bind("<<ComboboxSelected>>", go)
comboxlist.pack()
# 进入消息循环
win.mainloop()
3.定义函数go,编写代码逻辑。
def go(*args):
for i in tst:
i.place_forget()
# 被选中的省份
c = comboxlist.get()
for province_data in get_data_dictype():
provincename = province_data['provinceName']
if c == provincename:
tkinter.Label(win, text=provincename, height=1, width=15).place(x=400, y=40)
tkinter.Label(win, text=province_data['currentConfirmedCount'], height=1, width=10).place(x=400, y=70)
tkinter.Label(win, text=province_data['confirmedCount'], height=1, width=10).place(x=400, y=100)
tkinter.Label(win, text=province_data['suspectedCount'], height=1, width=10).place(x=400, y=130)
tkinter.Label(win, text=province_data['curedCount'], height=1, width=10).place(x=400, y=160)
tkinter.Label(win, text=province_data['deadCount'], height=1, width=10).place(x=400, y=190)
tkinter.Label(win, text='\t请选择' + provincename + '下地区:', height=1, width=20).place(x=50, y=220)
# 设置城市按钮的位置
lt = [(15, 240), (115, 240), (215, 240), (315, 240), (415, 240), (515, 240), (615, 240), (715, 240),
(15, 280), (115, 280), (215, 280), (315, 280), (415, 280), (515, 280), (615, 280), (715, 280),
(15, 320), (115, 320), (215, 320), (315, 320), (415, 320), (515, 320), (615, 320), (715, 320),
(15, 360), (115, 360), (215, 360), (315, 360), (415, 360), (515, 360), (615, 360), (715, 360),
(15, 400), (115, 400), (215, 400), (315, 400), (415, 400), (515, 400), (615, 400), (715, 400)]
# 按钮位置计数器
ct = 0
for city_data in province_data['cities']:
while ct < len(province_data['cities']):
b = Button(win, text=city_data['cityName'], height=1, width=10)
# 装入按钮
tst.append(b)
b.place(x=lt[ct][0], y=lt[ct][1])
# 当按钮被选中,绑定show函数
b.bind('<Button-1>', show)
ct += 1
break
tkinter.Label(win, text='地区:').place(x=240, y=435)
tkinter.Label(win, text='现存确诊人数:').place(x=240, y=465)
tkinter.Label(win, text='累计确诊人数:').place(x=240, y=495)
tkinter.Label(win, text='疑似人数:').place(x=240, y=525)
tkinter.Label(win, text='治愈人数:').place(x=240, y=555)
tkinter.Label(win, text='死亡人数:').place(x=240, y=585)
4.显示对应城市数据
# 显示对应城市数据
def show(event):
# comboxlist.get()得到查询的省份
c = comboxlist.get()
# 获得被选中城市(按钮)名称
for province_data in get_data_dictype():
provincename = province_data['provinceName']
if c == provincename:
for city_data in province_data['cities']:
# event.widget[‘text’]能得到查询的省下地区
if city_data['cityName'] == event.widget['text']:
tkinter.Label(win, text=city_data['cityName'], height=1, width=15).place(x=400, y=435)
tkinter.Label(win, text=city_data['currentConfirmedCount'], height=1, width=15).place(x=400, y=465)
tkinter.Label(win, text=city_data['confirmedCount'], height=1, width=15).place(x=400, y=495)
tkinter.Label(win, text=city_data['suspectedCount'], height=1, width=15).place(x=400, y=525)
tkinter.Label(win, text=city_data['curedCount'], height=1, width=15).place(x=400, y=555)
tkinter.Label(win, text=city_data['deadCount'], height=1, width=15).place(x=400, y=585)
5.实现效果如下:
优点:数据全面,整体性强、完整度高。
缺点:循环次数多,比较次数不一定。
2.5 数据可视化界面的展示。在博客中介绍数据可视化界面的组件和设计的思路。
1.中国疫情确诊人数前五省饼状图
1)在函数save_data_to_excle中向label列表中顺序添加中国省份名称,向values列表中顺序添加各省累计确诊人数。
2)在字典z中以Label当键,values当值,按值逆序排序,取前五个省份。
3)利用matplotlib.pyplot中的pie()绘制,title()设置标题,savefig()保存图片,show()展示图片。
可视化界面如下:
2.中国疫情感染人数分布图
1)通过列表将各省感染人数与各省份相对应
2)利用for循环追加数据
3)通过pyecharts工具绘制地图
可视化界面如下:
三、心得体会
3.1在这儿写下你完成本次作业的心得体会,当然,如果你还有想表达的东西但在上面两个板块没有体现,也可以写在这儿~
本次编程作业对我来说是一次挑战,自己之前从未接触过此方面。本次编程,我一边查阅资料学习一边自己编码练习,我为之努力过,但因为时间、能力有限,还有功能未能实现,这也是我此次作业的遗憾。通过本次作业,我了解了一个软件成功使用背后的不易,清楚了软件从计划到设计开发到测试以至最后总结的过程。通过对中国疫情数据的爬取与处理,我学习到了一些有关数据爬取、处理、分析、可视化的方法。通过对函数的调用与运用,我巩固了函数方法的知识,一次又一次的编码改错,让我规范了代码的整体性、准确性,也锻炼了我的耐心。本次作业对我的身心都是一个挑战,但我不后悔完成这个作业。从此次作业中,我巩固加深了对书本理论知识的理解,感受到了做项目的实践体验。虽然结果未能达到预期目标,但我为之努力过,付出过,我不会后悔。在此次作业中,我也清楚认识到自身编程能力的不足,在以后的学习中,我会加强学习。