一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 10 |
Development | 开发 | 2000 |
· Analysis | · 需求分析 (包括学习新技术) | 540 |
· Design Spec | · 生成设计文档 | 60 |
· Design Review | · 设计复审 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 15 |
· Design | · 具体设计 | 90 |
· Coding | · 具体编码 | 1000 |
· Code Review | · 代码复审 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 |
Reporting | · 计算工作量 | 30 |
·· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 |
· 合计 | 4105 |
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 20 |
Development | 开发 | 1500 |
· Analysis | · 需求分析 (包括学习新技术) | 640 |
· Design Spec | · 生成设计文档 | 60 |
· Design Review | · 设计复审 | 15 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 |
· Design | · 具体设计 | 180 |
· Coding | · 具体编码 | 1200 |
· Code Review | · 代码复审 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 |
Reporting | · 计算工作量 | 20 |
·· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 |
· 合计 | 3995 |
二、任务要求的实现
我将本次个人编程作业分为四个环节,分别为计划、学习、开发、总结。
所用技术栈为 selenium+pyecharts+BeautifulSoup+time+re+xlwt
(1) 计划环节中,我先仔细阅读了发布的作业要求,提取出“疫情数据获取”、“exce表格”、“数据可视化”等关键点,确定了使用python来进行疫情数据的爬取,并上网查询了“疫情可视化”相关案例,学习他们的整体思路,并构思出程序的框架,用PSP表格记录下在各个模块上开发的预估耗时。
(2) 学习环节中,通过优快云、博客园等平台学习了request、selenium、beautifulSoup等各种工具的使用方法。
(3) 开发环节中,我使用Pycharm软件来进行程序开发。利用构思好的程序框架,先将程序的主要结构(函数)写出来,再通过不断调试与完善,逐步地编写代码,记录下开发过程中所遇到的问题。
(4) 总结环节中,回顾整体代码,详细列出实现代码的整体思路以及解决问题的思路,通过几个案例的测试,也总结出了代码的局限性与不足性。
本代码一共设计了如下几个函数
def getpage()
def getTime(url_text)
def getLocal_add(url_text)
def getNoSymptom_add(url_text)
def getHMT_people(url_text)
def add_data(a,b)
def write_to_excel(filename, dict1,dict2)
def getMap(fileName,a,b)
(1) 页面获取getpage()
由于卫健委的反爬机制,用谷歌浏览器与request等方法较难爬取到数据,后来在优快云看到了一位博主的案例后,我使用了selenium+火狐浏览器来爬取网页。
在疫情通报列表页面,我们可以看到第一页网址为:http://www.nhc.gov.cn/xcs/yqtb/list_gzbd.shtml
第二页网址为:http://www.nhc.gov.cn/xcs/yqtb/list_gzbd_2.shtml
第三页http://www.nhc.gov.cn/xcs/yqtb/list_gzbd_3.shtml……以此类推
于是我们可以得出它的页面除了了第一页,其他的构成为http://www.nhc.gov.cn/xcs/yqtb/list_gzbd_(数字).shtml
因此,我们使用一个getPage()函数来顺序获取并返回页面的链接
def getpage(): #页数函数
for page in range(1,42):
if page == 1:
yield'http://www.nhc.gov.cn/xcs/yqtb/list_gzbd.shtml'
else:
url = 'http://www.nhc.gov.cn/xcs/yqtb/list_gzbd_'+str(page)+'.shtml'
yield url
在获取了疫情通报的页码后,我们要做的就是爬取到该页上所有新闻链接,在主函数中,我们通过for …in…来进入每个页码的网站,然后使用selenium以及火狐浏览器爬取到该页码上所有新闻链接,再使用for…in…进入每一个新闻链接进行内容爬取
def main():
for url_main in getpage(): #从第一页开始爬取该页的所有新闻链接
option = FirefoxOptions()
option.add_argument("--headless") # 隐藏浏览器
browser = Firefox(executable_path='geckodriver', options=option)
browser.get(url_main)
time.sleep(3) #设置等待时间
data = browser.page_source
soup = BeautifulSoup(data, "lxml") #创建对象
url_list = soup.find('div', class_='list').find_all('li') #找到list中的所有li标签
for i in url_list:
url = 'http://www.nhc.gov.cn/' + i.find('a')['href'] #爬取a标签下href的内容,需要加上网址头
try:
#接下来爬取单独的网页
browser.get(url)
time.sleep(3)
##中间为按顺序调用的子函数
except Exception:
contine
time.sleep(3)
time.sleep(3)
browser.quit()
main()
(2)时间获取getTime()
在详细疫情通报的页面源代码中,我们可以看到发布新闻的年月日,以及新冠肺炎疫情情况的截止月日,各差了一天。
我们需要的是疫情情况的截止年月日,使用正则表达式,可以爬取class=source里的第三个span标签获取年,爬取class = tit里的月日,再进行特殊情况的处理,并获得我们文件的名字。
def getTime(url_text):
....#正则表达式部分太多,不列出
#Title_Get为一个列表,存储截止日期[月,日],release_Get 存储[年,月,日 ]
if int(Title_Get[0])<int(release_Get[1]): #如果发布新闻的月份比通报的疫情日期还小,则年份要减一
Title_year_Get= str(int(release_Get[0])-1)
else:
Title_year_Get = str(int(release_Get[0]))
Title_month_Get = Title_Get[0]
Title_day_Get = Title_Get[1]
Time=(Title_year_Get+'年'+Title_month_Get+'月'+Title_day_Get+'日新型冠状病毒肺炎疫情')
return Time #返回一个文件标题
(3)内陆省份以及新增确诊人数获取 getLocal_add()、内陆新增无症状感染人数获取getNoSymptom_add()
这两个函数的功能实现都相同,都是在主函数里面调用,传入的参数为主函数里爬取到的页面数据,然后在子函数里创建一个soup,然后用正则表达式爬取出来省份、确诊人数或者感染人数,组合成一个字典,并返回给主函数里的变量。下面为函数的核心算法部分
...正则表达式部分太多就不展出了,下同
province_list 为爬取数据后,存储有疫情的省份的列表
province_people为存储对应人数的列表
ll = [0]*len(province) #创建一个跟省份数目相同的全为‘0’的列表
在getLocal_add()中
nubmtuple = tuple(zip( map(int, province_people),ll)) #将确诊人数设置为(x,0)的元组
在getNoSymptom_add()中
nubmtuple = tuple(zip( ll,map(int, province_people))) #将无症状感染设置为(0,x)的元组
a=dict(zip(province,nubmtuple))
return a
下图为getLocal_add()函数得到的结果
(4)港澳台累计确诊统计getHMT_people()
原理和(3)中差不多,港澳台地区由于没有找到历史的当日新增感染人数 的新闻数据,就只统计了截至日期的累计确诊人数
people_num 列表用来存储爬取到的每个地方的累计确诊人数
province = ["香港","澳门","台湾"]
add = dict(zip(province,map(int,people_num))) #组合成一个列表
(6)数据合并add_data(a,b)
在上述步骤中,我们获得了例如{‘云南’,(0,4)}的新增确诊字典以及{‘福州’,(0,12)}的新增无症状感染人数的字典,接下来关键的一步就是将两个字典合并。在主函数中,我们调用add_data()函数,并传入上述步骤获得的两个字典的参数。算法如下
for i in b.keys(): #嵌套两个循环,从第一个字典的键开始查找是否跟第二个字典的键相匹配,匹配则将感染人数的两个信息合并,没有匹配项则增加到字典中
flag =0
for j in a.keys():
if i == j:
a[j]=(a[j][0],b[i][1])
flag=1
break
if flag==0: #如果a字典的键中没有b的某一个键,则增加到a中
a[i]=(b[i][0],b[i][1])
return a
这样就得到了一个用字典存储的完整的疫情信息,字典的value值存储一个元组,元组第一个值为确诊人数,第二个值为无症状感染人数
(7)数据导入exceldef write_to_excel(filename, dict1,dict2)
该函数中,传入的第一个参数为getTime()返回给给主函数的标题,第二个参数为数据合并(add_data(a,b))后的内地疫情字典,第三个为港澳台疫情情况的字典。
我利用了xlwt工具来进行excel导数据,并创建了两张工作表,一张为内地疫情明细,一张为港澳台疫情明细,保存成名为filename的xls文件。核心算法如下所示
j=1
for content1 in dict1.items():#写入内地疫情,用concent迭代字典的每一个键值对,并在j行的0、1、2列写入省份、确诊人数、无症状感染人数的数据
sheet1.write(j, 0,content1[0])
sheet1.write(j, 1, content1[1][0])
sheet1.write(j, 2, content1[1][1])
j=j+1
得到的结果如下图
(8)可视化地图制作getMap(fileName,a,b)
详情看下(3.5)
(9)程序运行结果展示
最后就是爬虫后数据处理结果的展示啦。程序运行过程中,爬取以及数据处理成功后会输出提醒语句,excel以及可视化地图页面都自动存入了文件夹中。
我在数据统计接口上所花费的时间非常大,主要时间都花费在了页面中省份以及人数的数据检索。一开始打算直接用request或者soup来直接在整个url的文本上抓取省份名称以及人数,但是发现卫健委页面上的人数是在span和/span中间,导致我很难直接将数字单独检索出来。在最后就只能对应到本土病例以及无症状感染的标签上,然后再用正则表达式检索出来。
我使用了profile和pstats来作为性能分析工具,由于将所有页面爬出来的程序过慢,所以在这边展示爬取一次页面的,按时间排序的一部分结果。
profile.run('main()', "result")
pstats.Stats('result').sort_stats('time').print_stats()
同样,我们在主函数中调用可视化界面制作的函数getMap(fileName,a,b),第一个参数作为标题,第二个参数为内地疫情的字典,第三个参数为港澳台疫情的字典。
在过程中,我使用了pyecharts工具来制作一个地图类的可视化界面,一个地图中有三个元素,分别为内地新增确诊人数,内地新增无症状感染者,港澳台累计确诊人数。
由于Map().add()函数中,第二个参数为中国省份以及数据的一个或多个列表,所以我们首先需要通过设置一个迭代来获得我们字典里面的内容,并转换成list列表。
迭代的z是一个省份和人数的元组,如(‘浙江’,(14,5)),14代表确诊,5代表无症状,这时候我们要将z转换成list形式,并分别取出省份名称和确诊人数(或无症状感染人数、累计确诊人数),又讲其转换为list列表,如[‘浙江’,‘14’],[‘浙江’,‘5’].
最后生成一份本地html。
实现的算法如下
map1 = (
Map(init_opts=opts.InitOpts(width="1500px",height="600px"))
.add("新增确诊人数", [list((list(z)[0], str(list(z)[1][0]))) #在此只展示新增确诊人数的设计,其余二者都类似操作,只是选择的数据不同
for z in zip(list(a.keys()), list(a.values()))], "china")
.set_global_opts(
title_opts=opts.TitleOpts(title=fileName),
visualmap_opts=opts.VisualMapOpts(max_=300, split_number=20, is_piecewise=True, ),
)
)
map1.render(fileName + '.html')# 生成到本地网页形式打开
地图可以实现放大缩小、鼠标互动展示的功能,我们可以通过需求查看不同类型的疫情情况
三、心得体会
这部分怎么写呢,百感交集。
这大概是我入学以来最奋斗的两个周,从任务的最终发布到开始上网收集材料制定计划,从第一行代码到完成运行整个程序,整个开发过程基本上靠自己学习,每一个步骤、每一行代码都是用心血苦苦熬出来的。也有参考网上的各种案例,看着他们的方法自己去学习如何搭建程序,json、Pyppeteer、request等各种方法一一比对,在不断的错误与尝试中最终定下了我易于理解与掌握的方法。程序中我用到的方法最多的就是正则表达式了吧,爬取到网页信息后,先筛选出我所需要的标签内容,再用正则表达式测试器一遍一遍琢磨如何匹配出我所想要的内容,再到构思如何将数据合并、转换、修改,这过程确实很痛苦。
但是由于我选择的方法以及整体代码框架的局限性,我目前无法将所获得的单独的数据结合起来,导致没有方向去编写每日数据对比的算法,只能静态地展示每天的疫情数据,缺乏互通性。
但收获也颇多,虽然我曾经上过pyhton编程课,但我们主要学习了基础语法,并没有教学爬虫相关的内容。在本次个人作业中,我自己学习了丰富的爬虫内容,正则表达式、beautifulsoup等等相关的语法都在编程中逐渐掌握熟练,最终能够做出疫情信息爬取与可视化展示的程序。
我真的很不戳😎 😎