2020,努力做一個無可替代的人!
寫在前面的話
先說明一下,這是一篇爬蟲+分析+自動化的文章,并不是上節說到的 NumPy 系列文章,NumPy 系列請期待下節內容。
這篇實戰文章也屬于心血來潮吧,簡單說一下:
小一我自從疫情發生了之后,每天早上第一件事就是關注微博熱搜里面關于各地確診人數的新聞,不得不說,確實很牽動人心,前幾天的突增1w+,有點害怕,還好這幾天降下來了。
最近幾天和往常一樣去看熱搜的時候,卻發現好像確診人數的新聞并不在熱搜里面,有時候還需要折騰一會才能搜到相關數據。
好吧,既然這樣,那咱們就自己寫一個程序,自己更新數據。
大概這篇文章的起源就是這樣,就一個心血來潮的沖動,就有了。
ok,該介紹的背景都說完了,再來說下這篇文章:
技術方面:會用到 爬蟲+數據庫+數據處理+繪圖+郵件 相關技術
咋一看,發現技術點還挺多,如果你經常讀公眾號的文章,會發現大部分知識點都有專門寫過。
我都一一列出來,文章哪一塊看不明白了回來查一下再繼續
數據庫:數據庫存儲
郵件:郵件發送
正文
我們要做一個自動化的程序,當然就不只是爬蟲那么簡單了。
先明確一下需求:
- 爬蟲獲取最新疫情數據
- 數據簡單清洗,保存數據庫
- 繪制熱力地圖,與前一日數據進行比較
- 將結果以郵件形式發送
- 每日定時執行程序
大概就上面五個步驟,也不是很難嘛。畫熱力地圖是個新知識,可能需要花一些時間
準備好了,我們就開始吧!
爬取數據
首先我們需要確定數據源,這個簡單。
說個題外話,這次疫情期間,我感覺官方媒體還是很給力的,數據都能在第一時間公開公布,讓大眾知道,還是很給力的
其中包括衛健委、人民日報、丁香園、百度地圖等,都有最新數據。
就不一一列舉了,網上都能搜得到。
本次爬蟲我用的是丁香園的數據。
再說個題外話,別整那些惡意爬蟲去搞這些網站,特別是最近一段時間。慎之慎之
看一下丁香園的疫情官網,可以看到有這樣一些(國內)數據
一個是地區累積確診人數的熱力分布圖,一個是當前的最新數據,當然還有很多折線圖,我沒有截
我們需要的是每日的各個省、地市的相關數據。
檢查源代碼,可以看到:
其中有三個 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)
具體的郵件發送教程可以看最前面提到的,之前寫的很詳細
如果沒有什么異常,你會收到這樣的一封郵件
打開之后,你需要點擊【顯示圖片】
郵件正文部分內容是這樣的:
搞定!還有最后一部分
定時任務
程序基本上已經算是完成了,自動化這一步提供一個方法,大家參考即可:
- Linux下:
可以使用 crontab 設置定時任務
- Win下:
可以使用(控制面板搜)任務計劃程序設置定時任務
另外,我已經部署好了自己的定時任務,如果有需要的同學可以在評論區留言自己的郵箱
,每天早上定時更新
總結一下:
先列好需求,再把需求一個個實現了,其實今天的項目就比較清晰明了了。
一個五個需求,我們再回顧一下:
- 爬蟲獲取最新疫情數據
- 數據簡單清洗,保存數據庫
- 繪制熱力地圖,與前一日數據進行比較
- 將結果以郵件形式發送
- 每日定時執行程序
最后一步大家可以先百度,以后我會專門拎出來寫一節,可以自動化的任務它不香嗎?
源碼獲取
在公眾號后臺
回復 武漢加油
獲取文章源碼
有需要交流學習的同學可以加我們的交流群。(后臺回復加群
)
寫在后面的話
疫情還沒過去,下周大家伙應該都要上班了
我已經窩了兩星期,雖然特別想出來,但是一想到上下班的人,我就有點慫。
不多說了,下周上班,我們都要保護好自己。
碎碎念一下
對了,需要每天定時郵件更新疫情數據的同學評論區留自己的郵箱
我們評論區見
原創不易,歡迎點贊噢
文章首發:公眾號【知秋小一】
文章同步:掘金,簡書
原文鏈接 :寫了個自動化腳本,每日更新疫情數據