寫了個自動化腳本,每日更新疫情數據

2020,努力做一個無可替代的人!

寫在前面的話

先說明一下,這是一篇爬蟲+分析+自動化的文章,并不是上節說到的 NumPy 系列文章,NumPy 系列請期待下節內容。

這篇實戰文章也屬于心血來潮吧,簡單說一下:

小一我自從疫情發生了之后,每天早上第一件事就是關注微博熱搜里面關于各地確診人數的新聞,不得不說,確實很牽動人心,前幾天的突增1w+,有點害怕,還好這幾天降下來了。

最近幾天和往常一樣去看熱搜的時候,卻發現好像確診人數的新聞并不在熱搜里面,有時候還需要折騰一會才能搜到相關數據。

好吧,既然這樣,那咱們就自己寫一個程序,自己更新數據。

大概這篇文章的起源就是這樣,就一個心血來潮的沖動,就有了。

ok,該介紹的背景都說完了,再來說下這篇文章:

技術方面:會用到 爬蟲+數據庫+數據處理+繪圖+郵件 相關技術

咋一看,發現技術點還挺多,如果你經常讀公眾號的文章,會發現大部分知識點都有專門寫過。

我都一一列出來,文章哪一塊看不明白了回來查一下再繼續

爬蟲:動態獲取數據BeautifulSoup詳解

數據庫:數據庫存儲

郵件:郵件發送

正文

我們要做一個自動化的程序,當然就不只是爬蟲那么簡單了。

先明確一下需求:

  • 爬蟲獲取最新疫情數據
  • 數據簡單清洗,保存數據庫
  • 繪制熱力地圖,與前一日數據進行比較
  • 將結果以郵件形式發送
  • 每日定時執行程序

大概就上面五個步驟,也不是很難嘛。畫熱力地圖是個新知識,可能需要花一些時間

準備好了,我們就開始吧!

爬取數據

首先我們需要確定數據源,這個簡單。

說個題外話,這次疫情期間,我感覺官方媒體還是很給力的,數據都能在第一時間公開公布,讓大眾知道,還是很給力的

其中包括衛健委、人民日報、丁香園、百度地圖等,都有最新數據。

就不一一列舉了,網上都能搜得到。

本次爬蟲我用的是丁香園的數據。

再說個題外話,別整那些惡意爬蟲去搞這些網站,特別是最近一段時間。慎之慎之

看一下丁香園的疫情官網,可以看到有這樣一些(國內)數據

一個是地區累積確診人數的熱力分布圖,一個是當前的最新數據,當然還有很多折線圖,我沒有截

我們需要的是每日的各個省、地市的相關數據。

檢查源代碼,可以看到:

其中有三個 div 需要注意:

  • class=’fold___xVOZX‘ 的 div:每個省的所有數據(總)
  • class=’areaBlock1___3V3UU‘ 的 div:每個省的匯總數據(分)
  • class=’areaBlock2___27vn7‘ 的 div:每個省下的所有地市數據(分)

我們需要的數據就在這三個 div 里面,再看看 div 里面有什么:

紅色的是省份匯總數據,黃色的是地市的數據,黑色的是具體數據標簽。

省份匯總數據的 div 和地市的數據的 div 下面都有5個 p 標簽存放數據,基本一致

5個 p 標簽分別是:

  • class=’subBlock1___j0DGa‘ 的 p 標簽:表示省份/城市名稱
  • class=’subBlock2___E7-fW‘ 的 p 標簽:表示現存確診人數
  • class=’subBlock4___ANk6l‘ 的 p 標簽:表示累計確診人數
  • class=’subBlock3___3mcDz‘ 的 p 標簽:表示死亡人數
  • class=’subBlock5___2EkOU‘ 的 p 標簽:表示治愈人數

數據就這些了,選擇一種爬蟲方式爬下來吧

打開頁面,我第一感覺就是動態數據,不信你也可以試試

選用 selenium 進行數據爬取,我盡量貼一下核心代碼,文末也有源碼獲取方式

# 初始化 seleniumexecutable_path = "你本機的chromedriver.exe路徑"# 設置不彈窗顯示chrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--disable-gpu')browser = webdriver.Chrome(chrome_options=chrome_options,executable_path=executable_path)

你也可以選擇 selenium 的彈窗顯示,源碼里面也有寫。

browser.get(url)# 輸出網頁源碼content = browser.page_sourcesoup = BeautifulSoup(content, 'html.parser')# 獲取中國城市疫情人數soup_city_class = soup.find('div', class_='areaBox___3jZkr').find_all('div',class_='areaBlock2___27vn7')# 獲取每一個地市的數據# 循環省略resolve_info(per_city, 'city')# 獲取中國省份疫情人數soup_province_class = soup.find('div', class_='areaBox___3jZkr').find_all('div',class_='areaBlock1___3V3UU')# 獲取每一個省的數據# 循環省略resolve_info(per_province, 'province')    

循環拿到每一個省份和每一個城市的代碼我沒寫,你知道這里面的 per_city 和 per_province 代表每一個城市和省份就行了。

解析函數里面,直接獲取我們需要的幾個數據

# 解析省份和地市詳細數據if tag == 'city':    # 城市    data_name = data.find('p', class_='subBlock1___j0DGa').find('span').stringelse:    # 省份    data_name = [string for string in data.find('p', class_='subBlock1___j0DGa').strings][0]# 現存確診人數data_curr_diagnose = data.find('p', class_='subBlock2___E7-fW').string# 累計確診人數data_sum_diagnose = data.find('p', class_='subBlock4___ANk6l').string# 死亡人數data_death = data.find('p', class_='subBlock3___3mcDz').string# 治愈人數data_cure = data.find('p', class_='subBlock5___2EkOU').string

當然會存在一些特殊情況

比如:有的省份最下面有特殊注釋,有的數據是空缺的等等,合理處理就行了

[圖片上傳失敗...(image-958b84-1581743457799)]

好了,數據已經全部拿到了,爬蟲就算結束了。

數據清洗

拿到數據以后,大致看了一眼,還算比較規整的。

在數據中,我發現了兩處需要處理的地方

  • 數據存在空值
  • 部分地市名稱其實并不是地市名稱

就拿北京來說,看一下數據:

黃顏色標出的是缺失數據,紅顏色的是非正常名稱

我是這樣處理的:

第一處地方:官網的數據并沒有0,所有這個空值就是0,直接填充就可

第二處地方:部分數據名稱不對,根據需求剔除或者合并到省會城市都可

看一下源代碼:

# 刪除地市的不明確數據if tag == 'city':    df_data.drop(index=df_data[df_data['city'] == '待明確地區'].index, axis=1,inplace=True)    # df_data.drop(df_data['city'] == '外地來京人員', axis=1, inplace=True)    # df_data.drop(df_data['city'] == '外地來滬人員', axis=1, inplace=True)    # df_data.drop(df_data['city'] == '外地來津人員', axis=1, inplace=True)# 填充空記錄為0df_data.fillna(0, inplace=True)# 增加日期字段df_data['date'] = time_str

代碼應該都能看懂,就不解釋了,日期字段是為了方便取出近兩天的數據進行比較

接下來就是導數據到數據庫了,一共兩種表,省份數據表和地市數據表。

看一下數據庫表結構:

省份表類似,只是把城市名換成了省份名。

當然,你要覺得兩張表麻煩,一張表也可以存這些數據,看你自己。

對于我們的 DataFrame 類型的數據,是可以直接導入數據庫的

一行代碼就行,看好了

# 連接數據庫connect = create_engine('mysql+pymysql://username:passwd@localhost:3306/db_name?charset=utf8')# 保存數據到數據庫中df_data.to_sql(name=table_name, con=connect, index=False, if_exists='append')

你不會覺得連接數據庫也算一行吧?那就兩行,給大哥跪下

數據搞定了,下面開始繪圖

數據繪圖

我們要畫的是熱力地圖,直接用 pyecharts,上手簡單

用 echarts 的原因是我曾經寫過一段時間前端代碼,echarts研究過一段時間,比較容易上手

這里需要安裝兩個模塊 pyecharts 和 ,用來畫圖和輸出成圖片保存

安裝也很簡單, cmd 下直接輸入 pip install 模塊名稱

模塊包安裝沒有問題的話就可以畫圖了

# 導入相應模塊from pyecharts import options as optsfrom pyecharts.charts import Mapfrom pyecharts.render import make_snapshotfrom snapshot_selenium import snapshot"""繪制熱力地圖"""# 獲取數據list_data = df_data.iloc[:, [1, 3]].values.tolist()# 繪制地圖ncp_map = (    Map(init_opts=opts.InitOpts('1000px', '600px'))    .add('', list_data, 'china')    .set_global_opts(        title_opts=opts.TitleOpts(            title=title,            pos_left='center'        ),        visualmap_opts=opts.VisualMapOpts(            # 設置為分段形數據顯示            is_piecewise=True,            # 設置拖拽用的手柄            is_calculable=True,            # 設置數據最大值            max_=df_data['sum_diagnose'].max(),            # 自定義的每一段的范圍,以及每一段的文字,以及每一段的特別的樣式。            pieces=[                {'min': 10001, 'label': '>10000', 'color': '#4F040A'},                {'min': 1000, 'max': 10000, 'label': '1000 - 10000', 'color': '#811C24'},                {'min': 500, 'max': 999, 'label': '500 - 999', 'color': '#CB2A2F'},                {'min': 100, 'max': 499, 'label': '100 - 499', 'color': '#E55A4E'},                {'min': 10, 'max': 99, 'label': '10 - 99', 'color': '#F59E83'},                {'min': 1, 'max': 9, 'label': '1 - 9', 'color': '#FDEBCF'},                {'min': 0, 'max': 0, 'label': '0', 'color': '#F7F7F7'}            ]        ),    ))# 保存圖片到本地make_snapshot(snapshot, ncp_map.render(), filepath_save)

看著效果還不錯。

需要提到的是,我們需要的是省份/地市名稱+累積確診人數兩列數據

它們對應的是第二列和第四列,所以上面代碼是這樣寫的

df_data.iloc[:, [1, 3]]

還有一些地圖的控件設置,看懂是什么意思就行了,不會了再去查API文檔

我有挨個行寫注釋,你可別說你看不懂

圖片生成了,看看張什么樣子

根據每日的數據更新,我們比較最近兩天的增長情況,做一個表格出來

獲取到最近兩天的數據庫數據

# 設置日期data_time = datetime.now() + timedelta(-2)data_time_str = data_time.strftime('%Y-%m-%d')# 獲取數據庫近兩天的數據sql_province = 'select * from t_ncp_province_info where date>={0}'.format(data_time_str)df_province_data = pd.read_sql_query(sql_province, connect)

將數據按天分成兩部分,做差即可,直接貼代碼

# 獲取數據日期date_list = df_data['date'].drop_duplicates().values.tolist()# 根據日期拆分dataframedf_data_1 = df_data[df_data['date'] == date_list[0]]df_data_2 = df_data[df_data['date'] == date_list[1]]# 昨天-前天 比較新增數據df_data_result = df_data_2[['curr_diagnose', 'sum_diagnose', 'death', 'cure']] - df_data_1[['curr_diagnose', 'sum_diagnose', 'death', 'cure']]

更進一步的,計算數據的環比增長率

# 新增較上一日環比列df_data_result['curr_diagnose_ratio'] = (df_data_result['curr_diagnose']/df_data_1['curr_diagnose']).apply(lambda x: format(x, '.2%'))df_data_result['sum_diagnose_ratio'] = (df_data_result['sum_diagnose']/df_data_1['sum_diagnose']).apply(lambda x: format(x, '.2%'))df_data_result['death_ratio'] = (df_data_result['death']/df_data_1['death']).apply(lambda x: format(x, '.2%'))df_data_result['cure_ratio'] = (df_data_result['cure']/df_data_1['cure']).apply(lambda x: format(x, '.2%'))

如果要在郵件中顯示表格內容,我們還需要對列名進行排序和更改

并且根據相應的數據進行降序排序,這樣增長變化看起來更明顯

if tag == 'city':    name = '城市'else:    name = '省份'df_data = df_data[[tag, 'sum_diagnose', 'sum_diagnose_ratio', 'curr_diagnose','curr_diagnose_ratio', 'death', 'death_ratio', 'cure', 'cure_ratio']]df_data.rename(    columns={        tag: name,        'sum_diagnose': '累計確診人數',        'sum_diagnose_ratio': '累計確診環比增長率',        'curr_diagnose': '現存確診人數',        'curr_diagnose_ratio': '現存確診環比增長率',        'death': '死亡人數',        'death_ratio': '死亡環比增長率',        'cure': '治愈人數',        'cure_ratio': '治愈環比增長率'    }, inplace=True)# 數據排序df_data.sort_values(['累計確診人數', '累計確診環比增長率'], inplace=True, ascending=False)df_data.reset_index(inplace=True)

ok,以上的數據,包括生成的圖片都是我們需要在郵件中顯示的。

郵件發送

郵件中,需要加入上一步的圖片和表格數據,添加到正文中發送

因此,郵件正文需要設置成 html 格式發送。

并且我們在正文中需要插入近兩天的數據,所以 html 中需要這樣設置

# 部分 html 內容'<p><img src="cid:image1" alt="最新數據地圖" width="1200" height="600"></a></p>''<p><img src="cid:image2" alt="最新數據地圖" width="1200" height="600"></a></p>'

根據 cid 區分不同的照片,同樣的,需要在郵件中這樣設置

# 讀取圖片并創建MIMEImagefor i, imag_filepath in enumerate(img_path_list):    with open(imag_filepath, 'rb') as fp:        msg_image = fp.read()    msg_image = MIMEImage(msg_image)    # 定義圖片 ID,在 HTML 文本中引用    msg_image.add_header('Content-ID', '<image{0}>'.format(i + 1))    message.attach(msg_image)

另外,郵件中設置 html 格式正文也需要設置

# 設置主題subject = '截止 ' + date_str + ' 疫情最新數據(自動推送)'# 設置發送內容:1:發送html表格數據message = MIMEMultipart()# 生成郵件正文內容emain_content = get_email_content(df_data_1, df_data_2)send_text = MIMEText(emain_content, 'html', 'utf-8')message.attach(send_text)

具體的郵件發送教程可以看最前面提到的,之前寫的很詳細

如果沒有什么異常,你會收到這樣的一封郵件

email_0.png

打開之后,你需要點擊【顯示圖片】

郵件正文部分內容是這樣的:

搞定!還有最后一部分

定時任務

程序基本上已經算是完成了,自動化這一步提供一個方法,大家參考即可:

  • Linux下:可以使用 crontab 設置定時任務
  • Win下:可以使用(控制面板搜)任務計劃程序設置定時任務

另外,我已經部署好了自己的定時任務,如果有需要的同學可以在評論區留言自己的郵箱 ,每天早上定時更新

總結一下:

先列好需求,再把需求一個個實現了,其實今天的項目就比較清晰明了了。

一個五個需求,我們再回顧一下:

  • 爬蟲獲取最新疫情數據
  • 數據簡單清洗,保存數據庫
  • 繪制熱力地圖,與前一日數據進行比較
  • 將結果以郵件形式發送
  • 每日定時執行程序

最后一步大家可以先百度,以后我會專門拎出來寫一節,可以自動化的任務它不香嗎?

源碼獲取

公眾號后臺回復 武漢加油 獲取文章源碼

有需要交流學習的同學可以加我們的交流群。(后臺回復加群

寫在后面的話

疫情還沒過去,下周大家伙應該都要上班了

我已經窩了兩星期,雖然特別想出來,但是一想到上下班的人,我就有點慫。

不多說了,下周上班,我們都要保護好自己。

碎碎念一下

對了,需要每天定時郵件更新疫情數據的同學評論區留自己的郵箱

我們評論區見

原創不易,歡迎點贊噢

文章首發:公眾號【知秋小一】

文章同步:掘金,簡書

原文鏈接 :寫了個自動化腳本,每日更新疫情數據

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375