使用Python获取上海详细疫情数据(完结篇)

抽空之余,写个小脚本,获取下上海详细的疫情数据,以作后续的详实数据分析(纯爱好),或者仅仅作为对历史的一种数据样本式的保存也未尝不可,顺便吧,缓解或者平复下情绪。

 本文主要是文章 https://blog.csdn.net/yifengchaoran/article/details/124361581 的完结篇,主要用来展示和说明核心的tools文件(文件如何命名其实随意),核心是对文章内的数据进行初步解析、清洗、分组,最终按照固定格式提取。

文本内贴的所有函数,都是在tools.py文件内,因为比较长,所以采用一个一个函数贴,并进行说明。如果读者能一步一步跟着分析(建议打开文章链接并查看源代码,分析具体文章格式,结合本文内的XPATH源代码进行分析,才能对代码有更好的理解),相信对Python知识会有更深的理解。

最后,如果读者不想对代码实现做详细的了解,可以直接下载源代码,在本地直接运行main.py即可,并且里面也包含了笔者搞好的数据(但是需要会shelve才能用)。下载链接:上海疫情数据及源代码实现-Python文档类资源-CSDN下载

开始正题

一、清洗各区上报的社区数据

from lxml import etree
import requests,re,shelve,pathlib

def wash_region_data_item(item):
    '''
    将文章中提取的每个区上报的数据,格式进行清洗及统一,因为发现16个区上报的数据格式并不太一致
    '''
    #首先将返回的每个区内的每个上报数据,统一替换符号
    if '居住于' in item:
        item=item.replace('和',',')
    else:
        item=item.replace('。',',').replace('、',',').replace(',',',')
        item=item.replace('\xa0','').strip()
        
        #然后item内,如果没有,,则程序加上这个符号,便于后面统一处理(使用,对每个区上报的社区数据切割成列表)
        if ',' not in item:
            item=item+','
            
    return item 


#下面这个小不点,主要是用来对4月5日之前的数据进行初步处理

def wash_item(item):
    item=item.xpath('span//text()')
    item=''.join(item)
    return item

在实际分析的时候,发现文章内,每日每个区上报的社区数据,有些包含居住于,有些没有,还有些竟然包含非ascii码的东西,另外。有些有有些没有,所以以上函数主要是对这些乱七八糟的数据进行统一清洗、格式化,便于后面切割。

二、清洗并提取各区上报社区列表


def wash_region_data_list(region_data_list):
    
    #首先将列表内的每项字符串统一清洗格式化
    region_data_list=[wash_region_data_item(item) for item in region_data_list]
    
    #然后再对数据列表连接起来,并重新使用,分割成新的列表
    if ':' in ''.join(region_data_list):
        data_list=''.join(region_data_list).split(':')
        region_data_list=[data_list[0]]+data_list[1].split(',')
    elif '于'  in ''.join(region_data_list):
        data_list=''.join(region_data_list).split('于')
        region_data_list=[data_list[0]]+data_list[1].split(',')
    else:
        region_data_list=[''.join(region_data_list)]
    
    #然后再去除新数据列表内不需要的列表项,因为使用的是pop,为了避免清洗遗漏,故使用了循环
    need_wash_flag=True
    while need_wash_flag:
        for index,data in enumerate(region_data_list):
            count=0
            #如果为空或包含消毒措施,则移除该列表项
            if data == '' or '消毒措施' in data  or '来源' in data:
                region_data_list.pop(index)
                count+=1
        if count==0:
            need_wash_flag=False
    #返回每个区上报的社区名称组成的列表        
    return region_data_list

同样的,本来提取每个区上报的社区名称然后组成列表返回,对于常规的爬取任务来说,会非常简单,但是因为上海各区数据格式不统一(尤其是在4月5日之前的数据,表现尤为明显,从某种程度上,也反应了这之前管理的混乱和不严谨) ,在对每个社区的名称清洗后再切割后,因为有些区上报社区时有包含居住于,且后面跟着:符号,有些没有,有些甚至直接没有居住于文案,所以以上代码做了一定程度的适配。

下面的while循环,主要是对清洗和切割后的列表,循环去除空项(即'')以及包含消毒措施文案或来源文案的列表项,确保最终返回的列表内,都是上报的社区名称,如果当天某区没有上报的社区数据,则返回的列表为空。

三、从文章头部提取全市当天数据


def fetch_date_data(top_title):
    #用于从文章顶部整体信息区域,提取当日日期、总确认、总无症状数据
    top_title=top_title
    current_date=re.search('[0-9]{4,4}年[0-9]{1,2}月[0-9]{1,2}日',top_title).group(0)
    
    total_confirm_count=re.search('确诊病例([0-9]*)例',top_title).group(1)
    total_confirm_count=int(total_confirm_count) if total_confirm_count else 0
    
    total_asymptomatic_count=re.search('感染者([0-9]*)例',top_title).group(1)
    total_asymptomatic_count=int(total_asymptomatic_count) if total_asymptomatic_count else 0
    
    return (current_date,total_confirm_count,total_asymptomatic_count)

 这部分比较简单,结合数据发布文章的内容,然后有一定基础的正则知识,就能比较轻松的看懂,以上函数主要是提取当日发布的全市数据,包括日期信息

四、提取区整体数据


def fetch_region_data(region_data_list):
    region_data_list=region_data_list    
    #开始提取该区当日上报的数据,包括该区当日新增确认、新增无症状、新上报的社区名称列表
    if '区' in  region_data_list[0]:
        region_name=re.search('[,,](.*区)',region_data_list[0]).group(1)
        
    #有些区的数据就是不规范,有时候有区有时候没有,烦人    
    elif '青浦' in region_data_list[0]:
        region_name='青浦区'
    elif '奉贤' in region_data_list[0]: 
        #专门为2022年3月28日的奉贤区数据进行适配
        region_name='奉贤区'
        

    region_total_confirm=re.search('([0-9]*)例(本土)?(新冠肺炎)?确诊',region_data_list[0])
    region_total_confirm=int(region_total_confirm.group(1)) if region_total_confirm else 0
    
    region_total_asymptomatic=re.search('([0-9]*)例(本土)?无症状',region_data_list[0])
    region_total_asymptomatic=int(region_total_asymptomatic.group(1)) if region_total_asymptomatic else 0
    
    #只有该区有上报数据时,才记录,否则,为空
    if len(region_data_list) >1:
        region_community_list=region_data_list[1:]
    else:
        region_community_list=''
    
    #返回该区的名称、当日总数据、上报的社区名称列表    
    return(region_name,region_total_confirm,region_total_asymptomatic,region_community_list)

 以上函数对4月5日之前和之后的文章数据,做了适配,可以提取某区当日的整体确认数据以及上报的社区名称列表。

其中因为在实际运行中发现,青浦和奉贤区,在上报数据时,有时候带区,有时候不带区,所以单独做了匹配(提取区名称)

下面的代码,同样的,有一定的正则基础知识,即可比较容易的看懂,主要是基于一定的格式,提取每个区当日的整体数据,为了方便理解,每个区上报的整体数据原格式如下:

2022年3月19日,浦东新区新增8例本土确诊病例、127例本土无症状感染者,分别居住于:

五、提取4月5日之前完整数据代码


def fetch_region_data_before(html):
    #用于提取4月5日之前的数据
    html=html
    regions_covid_data={} #初始化某天的数据字典
    
    #获取当前整体数据,包括日期、总确认、总无症状
    if len(html.xpath('//div[@id="ivs_content"]/p[1]/strong')):
        top_title=''.join(html.xpath('//div[@id="ivs_content"]/p')[0].xpath('strong//text()'))
    else:
        top_title=''.join(html.xpath('//div[@id="ivs_content"]/p')[0].xpath('span//text()'))
    date_datas=fetch_date_data(top_title)
    regions_covid_data['current_date']=date_datas[0]
    regions_covid_data['total_confirm_count']=date_datas[1]
    regions_covid_data['total_asymptomatic_count']=date_datas[2]
    
    #初始化对应各区的数据字典
    regions_covid_data['region_coviods_data']={}
    
    #获取当日每个区的信息,包括区名称、总确诊、总无症状、上报社区列表
    
    #先对文章内 的数据提取并按区分割
    total_content_list=[wash_item(item) for item in html.xpath('//div[@id="ivs_content"]/p')]
    end_index=None
    #为了防止文章中出现各区重复内容,故进行去重判断
    for index,item in enumerate(total_content_list[1:]):
        if '市卫健委' in item:
            end_index=index
    regions_content=total_content_list[1:end_index] if end_index  else total_content_list[1:]
    
    split_index=[]
    for index,item in enumerate(regions_content):
        #只是判断月日是否在文本内,因为部分区里面的数据不一定包含年
        if date_datas[0].replace('2022年','') in item:
            split_index.append(index)
            
    #切割后并按各区存储为列表        
    region_datas=[]
    for i in range(len(split_index)-1):
        region_datas.append(regions_content[split_index[i]:split_index[i+1]])
    region_datas.append(regions_content[split_index[-1]:])
    
    datas={}    
    for region_data in region_datas:
        region_data_list=wash_region_data_list(region_data)
        
        fetch_datas=fetch_region_data(region_data_list)
        region_name=fetch_datas[0]
        datas[region_name]={}
        datas[region_name]['region_name']=region_name
        datas[region_name]['region_total_confirm']=fetch_datas[1]
        datas[region_name]['region_total_asymptomatic']=fetch_datas[2]
        datas[region_name]['region_community_list']=fetch_datas[3]
        
    #将各区的数据,存储到区数据字段内    
    regions_covid_data['region_coviods_data']=datas
    return regions_covid_data
  

 以上函数用来提取4月5日之前的数据,因为在代码内注释已经表详细了,故此处不再赘述,代码比较长,主要是因为4月5日之前的数据实在有点难洗,如对代码有疑问,可以私信或评论区内询问。

六、提取4月5日之后完整数据代码

  
def fetch_region_data_after(html):
    html=html
    regions_covid_data={} #初始化某天的数据字典
    
    #获取当前整体数据,包括日期、总确认、总无症状
    top_title=html.xpath('//section[@data-id="106156"]//span/text()')[0]
    date_datas=fetch_date_data(top_title)
    regions_covid_data['current_date']=date_datas[0]
    regions_covid_data['total_confirm_count']=date_datas[1]
    regions_covid_data['total_asymptomatic_count']=date_datas[2]

    #初始化对应各区的数据字典
    regions_covid_data['region_coviods_data']={}
    
    #然后再提取每个区的具体数据   
    region_datas=html.xpath('//section[@data-id="72469"]') or html.xpath('//section[@data-id="97598"]')
    datas={}
    for region_data in region_datas:
        #对每个区的数据列表进行清洗
        region_data_list=region_data.xpath('section//text()')
        region_data_list=wash_region_data_list(region_data_list)
        
        fetch_datas=fetch_region_data(region_data_list)
        region_name=fetch_datas[0]
        datas[region_name]={}
        datas[region_name]['region_name']=region_name
        datas[region_name]['region_total_confirm']=fetch_datas[1]
        datas[region_name]['region_total_asymptomatic']=fetch_datas[2]
        datas[region_name]['region_community_list']=fetch_datas[3]
        
    #将各区的数据,存储到区数据字段内    
    regions_covid_data['region_coviods_data']=datas
    return regions_covid_data

以上是提取4月5日之后的每日各区的完整数据,因为这日之后的数据都是通过微信发布,格式相对统一,代码相对比较简介。

至此,完整代码介绍完毕。

你可能感兴趣的:(python,python,爬虫,selenium)