python+selenium定時爬取丁香園的新型冠狀病毒資料並製作出類似的地圖(部署到雲伺服器)
前言
硬要說這篇文章怎麼來的,那得先從那幾個吃野味的人開始說起…… 前天睡醒:假期還有幾天;昨天睡醒:假期還有十幾天;今天睡醒:假期還有一個月…… 每天過著幾乎和每個假期一樣的宅男生活,唯一不同的是玩手機已不再是看劇、看電影、打遊戲了,而是每天都在關注著這次新冠肺炎疫情的新聞訊息,真得希望這場戰“疫”快點結束,讓我們過上像以前一樣的生活。武漢加油!中國加油!!
本次爬取的網站是丁香園點選跳轉,相信大家平時都是看這個的吧。
一、準備
python3.7
- selenium:自動化測試框架,直接pip install selenium安裝即可
- pyecharts:以一切皆可配置而聞名的python封裝的js畫圖工具,其官方文件寫的很詳細了點選跳轉。
- 直接pip install pyecharts安裝即可,同時還需安裝以下地圖的包:
世界地圖:pip install echarts-countries-pypkg 中國地圖:pip install echarts-china-provinces-pypkg 中國城市地圖:pip install echarts-china-cities-pypkg
雲伺服器
二、爬取資料+畫圖
第一步、分析頁面
先用個requests模組請求一下,看能不能拿到資料:
import requests url='https://ncov.dxy.cn/ncovh5/view/pneumonia_peopleapp?from=timeline&isappinstalled=0' headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/79.0.3945.88 Safari/537.36'} r=requests.get(url,headers=headers) print(r.text)
發現數據是亂碼的並且注意到末尾處有如下字樣:
<noscript>You need to enable JavaScript to run this app.</noscript>
意思是需要執行js程式碼,百度了一下發現這個頁面應該是用react.js來開發的。限於自身技術能力,這個時候,我就只能用selenium了,它是完全模擬瀏覽器的操作,也即能執行js程式碼。
並且我需要拿到的資料並不多,也就一個頁面而已,所以耗時也可以接受。
那麼我要拿哪些資料呢,如下:
- 截至當前時間的全國資料統計
- 病毒相關描述資訊
- 全國各個省份及其城市的所有資料
- 全世界各個地區的資料
經過檢視,發現這幾處需要進行點選,才能獲取到更多資料資訊:
第二步、編寫程式碼
匯入相關包:
from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys import parsel import time import json import os import datetime import pyecharts from pyecharts import options as opts
定義爬取資料、儲存資料的函式:
def get_save_data(): ''' 部署到雲伺服器上時,注意:要安裝pyvirtualdisplay模組, 並且把下面的前5條註釋掉的程式碼給去掉註釋,再執行,不然會報錯。 ''' #from pyvirtualdisplay import Display #display = Display(visible=0,size=(800,600)) #display.start() options=webdriver.ChromeOptions() #options.add_argument('--disable-gpu') #options.add_argument("--no-sandbox") options.add_argument('--headless') #採用無頭模式進行爬取 d=webdriver.Chrome(options=options) d.get('https://ncov.dxy.cn/ncovh5/view/pneumonia_peopleapp?from=timeline&isappinstalled=0') time.sleep(2) ActionChains(d).move_to_element(d.find_element_by_xpath('//p[@class="mapTap___1k3MH"]')).perform() time.sleep(2) d.find_element_by_xpath('//span[@class="openIconView___3hcbn"]').click() time.sleep(2) for i in range(3): mores=d.find_elements_by_xpath('//div[@class="areaBox___3jZkr"]')[1].find_elements_by_xpath('./div')[3:-1] ActionChains(d).move_to_element(d.find_element_by_xpath('//div[@class="rumorTabWrap___2kiW4"]/p')).perform() mores[i].click() time.sleep(2) response=parsel.Selector(d.page_source) china=response.xpath('//div[@class="areaBox___3jZkr"]')[0] world=response.xpath('//div[@class="areaBox___3jZkr"]')[1] # 下面是病毒相關描述資訊的獲取與處理 content=response.xpath('//div[@class="mapTop___2VZCl"]/div[1]//text()').getall() s='' for i,j in enumerate(content): s=s+j if (i+1)%2 == 0: s=s+'\n' if j in ['確診','疑似','重症','死亡','治癒']: s=s+'\n' now=s.strip() msg=response.xpath('//div[@class="mapTop___2VZCl"]/div//text()').getall() s='' for i in msg: if i not in now: s=s+i+'\n' msg=s.strip() content=msg+'\n\n'+now # 下面是全國資料的獲取 china_data=[] for div_list in china.xpath('./div')[2:-1]: flag=0 city_list=[] for div in div_list.xpath('./div'): if flag == 0: if div.xpath('./p[1]/text()').get() is not None: item={} item['省份']=div.xpath('./p[1]/text()').get() item['確診']=div.xpath('./p[2]/text()').get() if div.xpath('./p[2]/text()').get() is not None else '0' item['死亡']=div.xpath('./p[3]/text()').get() if div.xpath('./p[3]/text()').get() is not None else '0' item['治癒']=div.xpath('./p[4]/text()').get() if div.xpath('./p[4]/text()').get() is not None else '0' flag=1 else: if div.xpath('./p[1]/span/text()').get() is not None: temp={} temp['城市']=div.xpath('./p[1]/span/text()').get() temp['確診']=div.xpath('./p[2]/text()').get() if div.xpath('./p[2]/text()').get() is not None else '0' temp['死亡']=div.xpath('./p[3]/text()').get() if div.xpath('./p[3]/text()').get() is not None else '0' temp['治癒']=div.xpath('./p[4]/text()').get() if div.xpath('./p[4]/text()').get() is not None else '0' city_list.append(temp) item.update({'city_list':city_list}) china_data.append(item) # 下面是全球資料的獲取 world_data=[] for div_list in world.xpath('./div')[2:-1]: flag=0 country_list=[] for div in div_list.xpath('./div'): if flag == 0: if div.xpath('./p[1]/text()').get() is not None: item={} item['地區']=div.xpath('./p[1]/text()').get() item['確診']=div.xpath('./p[2]/text()').get() if div.xpath('./p[2]/text()').get() is not None else '0' item['死亡']=div.xpath('./p[3]/text()').get() if div.xpath('./p[3]/text()').get() is not None else '0' item['治癒']=div.xpath('./p[4]/text()').get() if div.xpath('./p[4]/text()').get() is not None else '0' flag=1 else: if div.xpath('./p[1]/span/text()').get() is not None: temp={} temp['國家']=div.xpath('./p[1]/span/text()').get() temp['確診']=div.xpath('./p[2]/text()').get() if div.xpath('./p[2]/text()').get() is not None else '0' temp['死亡']=div.xpath('./p[3]/text()').get() if div.xpath('./p[3]/text()').get() is not None else '0' temp['治癒']=div.xpath('./p[4]/text()').get() if div.xpath('./p[4]/text()').get() is not None else '0' country_list.append(temp) item.update({'country_list':country_list}) world_data.append(item) d.quit() # 下面是儲存資料的操作 if not os.path.exists('./json'): os.makedirs('./json') if not os.path.exists('./txt'): os.makedirs('./txt') now_time=datetime.datetime.now().strftime("%Y-%m-%d") #獲取當前日期 index=list(range(len(china_data))) data=dict(zip(index,china_data)) json_str = json.dumps(data,indent=4,ensure_ascii=False) with open(f'./json/{now_time}.json','w',encoding='utf-8') as f: f.write(json_str) index=list(range(len(world_data))) data=dict(zip(index,world_data)) json_str = json.dumps(data,ensure_ascii=False) with open(f'{now_time}.json',encoding='utf-8') as f: f.write(json_str) with open(f'./txt/{now_time}.txt',encoding='utf-8') as f: f.write(content)
定義畫地圖的函式,輸出是一個html檔案:
def get_html(): # 首先是載入爬取到的資料 json_files=os.listdir('./json') json_data=[] date=[] for i in json_files: with open(f'./json/{i}','r',encoding='utf-8') as f: date.append(i.split('.')[0]) temp=json.load(f) json_data.append(list(temp.values())) txt_files=os.listdir('./txt') content_list=[] for i in txt_files: with open(f'./txt/{i}',encoding='utf-8') as f: content_list.append(f.read()) # 下面開始畫圖 t=pyecharts.charts.Timeline(init_opts=opts.InitOpts(width='1400px',height='1400px',page_title='武漢加油!中國加油!!')) for s,(i,data) in enumerate(zip(date,json_data)): value=[] # 儲存確診人數 attr=[] # 儲存城市名字 for each in data: attr.append(each['省份']) value.append(int(each['確診'])) map0 = ( pyecharts.charts.Map() .add( series_name='該省份確診數',data_pair=list(zip(attr,value)),maptype='china',is_map_symbol_show=True,zoom=1.1 ) .set_global_opts(title_opts=opts.TitleOpts(title="武漢加油!中國加油!!",# 標題 subtitle=content_list[s],# 副標題 title_textstyle_opts=opts.TextStyleOpts(color='red',font_size=30),# 標題文字 subtitle_textstyle_opts=opts.TextStyleOpts(color='black',font_size=20),item_gap=20),# 副標題文字 visualmap_opts=opts.VisualMapOpts(pieces=[{"max": 9,"min": 1,'label':'1-9','color':'#FFEBCD'},{"max": 99,"min": 10,'label':'10-99','color':'#F5DEB3'},{"max": 499,"min": 100,'label':'100-499','color':'#F4A460'},{"max": 999,"min": 500,'label':'500-999','color':'#FA8072'},{"max": 9999,"min": 1000,'label':'1000-9999','color':'#ee2c0f'},{"min": 10000,'label':'≥10000','color':'#5B5B5B'}],is_piecewise=True,item_width=45,item_height=30,textstyle_opts=opts.TextStyleOpts(font_size=20)) ) ) t.add(map0,"{}".format(i)) # 將這幅圖儲存為html檔案 t.render('武漢加油!中國加油!!.html')
程式入口:
if __name__ == '__main__': get_save_data() get_html()
第三步、結果展示
執行該程式之後,會在當前目錄下生成一個武漢加油!中國加油!!.html的檔案,開啟之後如下:
ps:因為只能上傳圖片,所以我就將html轉為圖片了,html是動態的,有時間軸可以拖動,由於昨天才剛開始爬資料,所以只有兩天的資料。下面附上轉圖片的程式碼:
ps:又因為這個Timeline時間線輪播多圖,配置不了背景顏色,發現生成的圖片放大看變成黑色背景的,於是研究了一下原始碼,自己修改了一下js那塊的程式碼,然後就生成可以設定背景顏色的圖片了
from selenium import webdriver import base64 import os options=webdriver.ChromeOptions() options.add_argument('--headless') #採用無頭模式進行爬取 d=webdriver.Chrome(options=options) url='file://'+os.path.abspath('武漢加油!中國加油!!.html') d.get(url) def decode_base64(data: str) -> bytes: """Decode base64,padding being optional. :param data: Base64 data as an ASCII byte string :returns: The decoded byte string. """ missing_padding = len(data) % 4 if missing_padding != 0: data += "=" * (4 - missing_padding) return base64.decodebytes(data.encode("utf-8")) def save_as_png(image_data: bytes,output_name: str): with open(output_name,"wb") as f: f.write(image_data) js = """ var ele = document.querySelector('div[_echarts_instance_]'); var mychart = echarts.getInstanceByDom(ele); return mychart.getDataURL({ type: 'png',pixelRatio: 2,backgroundColor:'#FFFFFF',excludeComponents: ['toolbox'] }); """ content=d.execute_script(js) content_array = content.split(",") image_data = decode_base64(content_array[1]) save_as_png(image_data,'武漢加油!中國加油!!.png') d.quit()
三、部署到雲伺服器
1.定時執行獲取資料
首先將爬取資料的函式,即get_save_data()單獨放到一個py檔案中(我命名為:2019-nCoV.py)。然後修改定時任務/etc/crontab檔案,如下:
2.通過微信獲取地圖(html檔案)
把畫地圖的函式,即get_html()新增到個人微信機器人當中,然後設定特定判斷條件,在手機微信上向檔案傳輸助手傳送設定好的指令,執行get_html()函式,然後把執行函式後生成的html檔案發給檔案傳輸助手,從而獲取到當前的疫情地圖。
個人微信機器人的程式碼我就不再展示了,可以看我之前的文章:python實現微信自動回覆機器人
特定判斷的語句如下:
if '2019' == msg['Text']: get_html() itchat.send('@fil@%s'%'武漢加油!中國加油!!.html',toUserName='filehelper')
同時,也可以把剛剛的獲取資料的函式一起新增進去的,然後同樣通過傳送特定指令執行函式,而獲取資料,我這裡不加進去呢,是因為我要設定個定時任務,定時獲取就行了;並且我也可以通過給檔案傳輸助手傳送shell命令,執行py檔案。
把下面的程式碼加進個人微信機器人py檔案裡就行了。
import subprocess def cmd(command): output=subprocess.getoutput(command) return output
並給出我的特定判斷語句:
if 'cmd' in msg['Text']: output=cmd(msg['Text'][3:]) if output != '': itchat.send(output,toUserName='filehelper')
四、執行展示
如上圖所示:我先是執行了爬取資料的函式,即我呼叫了雲伺服器上的定時爬取資料的py檔案,然後再輸入指令獲取當前的疫情地圖,開啟後像上面的疫情地圖一樣。
寫在最後
世界的疫情地圖我沒有畫,是因為pyecharts的世界地圖各個地區是用英文命名的,跟獲取到的地區匹配不上,其實可以加個中文轉英文給它,那就可以了,我懶的弄了,有興趣的朋友可以試一試哦
一開始,我只是在那些爬蟲微信群上看到:今天這誰在爬丁香園的資料,過幾天又看到那誰又在爬丁香園的資料,而且還提出各種問題來討論。我實在是看不下去了,於是就有了這一篇文章(反正在家閒著也是閒著)
然後呢,今天學校發通知說校外的大四學生也可以申請vpn,然後在家就可以檢視和下載知網的文獻了。準備畢業的我突然驚了,我的論文還未開始寫呢!看來是時候了……
其實我是想回學校再寫的,但是這次的新冠肺炎疫情來勢凶猛,真的希望快點好起來啊~
武漢加油!中國加油!!
總結
以上所述是小編給大家介紹的python+selenium定時爬取丁香園的新冠病毒每天的資料並製作出類似的地圖(部署到雲伺服器),希望對大家有所幫助!