0 復習
上一關我們學習了selenium,它有可視模式與靜默模式這兩種瀏覽器的設置方法,二者各有優勢。然后學習了使用.get('URL')獲取數據,以及解析與提取數據的方法。
在這個過程中,我們操作對象的轉換過程:
除了上面的方法,還可以搭配BeautifulSoup解析提取數據,前提是先獲取字符串格式的網頁源代碼。
HTML源代碼字符串 = driver.page_source
以及自動操作瀏覽器的一些方法。
1 定時與郵件
在這一關,我們希望為一般的爬蟲程序新增兩個實用性比較強的功能:第一是定時功能,即程序可以根據我們設定的時間自動爬取數據;第二是通知功能,即程序可以把爬取到的數據結果以郵件的形式自動發送到我們的郵箱。
這兩個功能可以讓爬蟲程序定時向我們匯報。
試想一下,如果你是一位股票(或比特幣)的持有者,你希望及時爬取股票(或比特幣)每日的價格數據,方便你能及時賣出或買入,那每天都去啟動一遍爬蟲程序是極其不高效的。
而此時,如果你的爬蟲程序有定時和發送郵件功能,能自動爬取每天的數據,并且只有當價格達到某個你設置的價位時,才通知你可以有所行動了,平時都不打擾你,是不是很爽?
不止如此,如果你有特別想看的演唱會,但一開售就賣完了,有定時和發送郵件功能的爬蟲程序同樣可以辛勤地幫你刷票,當刷到有余票時,馬上通知你去購票,多好。(買火車票也是一樣的道理噢)。
這兩個功能不僅能幫你獲取這種實時變化的數據,還可以幫你獲取周期性的數據。比如,你所在的公司每周都會把周報發到官網上,而你所在的部門是由你去負責下載周報,并整理相關信息,再傳遞給部門成員。那如果有定時和通知功能的程序,每周你就可以靜待程序把更新的周報信息爬下來,并自動發送到你郵箱。
了解這兩個功能的意義后,我們就可以開始今天正式的學習了。
按照一向以來的規矩,實現一個項目的流程是這樣的:
2 明確目標
我們選擇的項目是——自動爬取每日的天氣,并定時把天氣數據和穿衣提示發送到你的郵箱。之所以選擇這個相對樸實的爬蟲項目,是因為天氣每天都會有變化,那么在學完這一關之后,不出意外,你就可以在明早收到天氣信息了。以此,親身體驗程序的作用。
3 分析過程
總體上來說,可以把這個程序分成三個功能塊:【爬蟲】+【郵件】+【定時】
對爬蟲部分,我們比較熟悉;而對通知部分,選擇的是用郵件來通知,我們將使用smtplib、email庫來實現這一需求;對定時功能,有一個schedule,方便好用。
這三個功能對應的是三段代碼,分別寫出三段代碼后再組裝起來,就能實現我們的項目目標。
3.1 爬蟲
在百度搜索天氣,彈出來的第一個網址是:
http://www.weather.com.cn/weather/101280601.shtml
點進去的是中國氣象網的天氣預報:
我打開這個網站的時間是2019年1月11日,我所在的地點是深圳,瀏覽器會自動定位,所以你能看到上圖顯示的是1月11日-1月17日深圳的天氣。
我要爬取的是1月11日的天氣,即下圖框內的“23/18℃”和“多云”兩個數據:
很自然地,我們點擊"右鍵"——"檢查"——"Network",刷新頁面,點看第0個請求:
數據放在HTML里,沒問題。那我們點擊Elements:
可以發現,溫度數據放在
之下。
同樣,可以發現“多云”這個數據所在的位置:
“小雨”所在的位置是
小雨
。在網頁源代碼里面搜索觀察了一番,發現可以使用和來匹配目標數據。
import requests
from bs4 import BeautifulSoup
#引入requests庫和BeautifulSoup庫
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#封裝headers
url='http://www.weather.com.cn/weather/101280601.shtml'
#把URL鏈接賦值到變量url上。
res=requests.get(url,headers=headers)
#發送requests請求,并把響應的內容賦值到變量res中。
print(res.status_code)
#檢查響應狀態是否正常
print(res.text)
#打印出res對象的網頁源代碼? ?
運行結果返回的是200,證明狀態是正常的,再來看看網頁源代碼,滑動看看:等等,好像出現了一些奇怪的東西......(⊙o⊙)噢,是亂碼,這意味著出現了編碼問題。不過還好,我們在第0關就知道碰到編碼可以怎么解決,用response.encoding屬性就好。好滴,那我們在網頁上點擊"右鍵"——"查看網頁源代碼",會彈出一個新的標簽頁,然后搜索charset,查看一下編碼方式。
噢,網頁是用utf-8編碼的。
那么只要用response.encoding轉換一下編碼就可以了,請你來寫一寫代碼,定義Response對象的編碼,還有檢查請求的結果,并且打印網頁源代碼:
import requests
from bs4 import BeautifulSoup
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
url='http://www.weather.com.cn/weather/101280601.shtml'
res=requests.get(url,headers=headers)
res.encoding='utf-8'
print(res.status_code)
print(res.text)
這次沒問題了喲。接下來,就可以用BeautifulSoup模塊解析和提取數據了:
import requests
from bs4 import BeautifulSoup
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
url='http://www.weather.com.cn/weather/101280601.shtml'
res=requests.get(url,headers=headers)
res.encoding='utf-8'
bsdata=BeautifulSoup(res.text,'html.parser')
#使用bs模塊解析獲取到的數據
data1= bsdata.find(class_='tem')
#使用find()取出天氣的溫度數據
data2= bsdata.find(class_='wea')
#使用find()取出天氣的文字描述
print(data1.text)
#取出變量data1中的字符串內容,并打印
print(data2.text)
#取出變量data2中的字符串內容,并打印
搞定啦~(≧▽≦)/~啦啦啦。
當然,每個人所在的地區都不一樣,所以你要選擇好你所在地區的天氣網址來替換這段代碼中的URL。
接下來,就可以進入到通知功能,我們選擇的是用郵件來發送爬蟲結果。
進入到郵件功能部分的學習,先來模仿一下平時我們發郵件時計算機的操作:
我們的代碼邏輯也會按照上圖來進行,并且在其中用到兩個庫——smtplib和email。
以qq郵箱為例,先來看第0步:連接服務器。
連接服務器需要用到smtplib庫。為什么叫這個名字呢?其實,SMTP代表簡單郵件傳輸協議,相當于一種計算機之間發郵件的約定。
好,來看下具體怎么用smtplib庫來連接服務器:
import smtplib
#smtplib是python的一個內置庫,所以不需要用pip安裝
mailhost='smtp.qq.com'
#把qq郵箱的服務器地址賦值到變量mailhost上,地址需要是字符串的格式。
qqmail = smtplib.SMTP()
#實例化一個smtplib模塊里的SMTP類的對象,這樣就可以使用SMTP對象的方法和屬性了
qqmail.connect(mailhost,25)
#連接服務器,第一個參數是服務器地址,第二個參數是SMTP端口號。
解釋一下:第1行代碼是引入庫,第3行代碼是qq郵箱的服務器地址,這個地址是可以通過搜索引擎查到的。
點進去第一個網址:
這樣就拿到了qq郵箱的smtp地址。此刻,我們用的是qq郵箱,所以搜索qq郵箱的smtp服務器地址,如果你之后想用網易郵箱,也可以搜索網易郵箱的smtp服務器地址。
繼續看代碼的第5行:
第5行代碼是實例化了一個smtplib里的SMTP對象。
第7行代碼是用SMTP對象的connect()方法連接服務器,第一個參數是獲取到的服務器地址,第二個參數是SMTP端口號——25。
端口號的選擇不是唯一的,但是25是一個最簡單、最基礎的端口號,所以我們填25。
連接服務器就講完了,馬上來看第1和第2步:通過賬號和密碼登錄郵箱;填寫收件人。
來看登錄郵箱的代碼(第11行為新增代碼):
account = input('請輸入你的郵箱:')
#獲取郵箱賬號
password = input('請輸入你的密碼:')
#獲取郵箱密碼
qqmail.login(account,password)
#登錄郵箱,第一個參數為郵箱賬號,第二個參數為郵箱密碼
receiver=input('請輸入收件人的郵箱:')
#獲取收件人的郵箱
解釋一下從11行新增的代碼:第11行是用input()獲取郵箱賬號。第13行是用input()獲取郵箱密碼,但注意了,這里可不是你平時登錄郵箱的密碼!這個密碼需要我們去到這里獲取:請打開https://mail.qq.com/,登錄你的郵箱。然后點擊位于頂部的【設置】按鈕,選擇【賬戶設置】,然后下拉到這個位置。
就像上面的一樣,把首個SMTP服務開啟。這時,QQ郵箱會提供給你一個授權碼,注意保護好你的授權碼:
接下來,在你使用SMTP服務登錄郵箱時,就可以輸入這個授權碼作為密碼登錄了。
然后看上面第18行代碼,就是獲取收件人的郵箱,沒有太多可說的。
至此,第1步和第2步都完成了。
繼續看第3步和第4步:填寫主題和撰寫正文,在這里需要用到email庫。
來看用法:
from email.mime.text import MIMEText
from email.header import Header
#引入Header和MIMEText模塊
content=input('請輸入郵件正文:')
#輸入你的郵件正文
message = MIMEText(content, 'plain', 'utf-8')
#實例化一個MIMEText郵件對象,該對象需要寫進三個參數,分別是郵件正文,文本格式和編碼.
subject = input('請輸入你的郵件主題:')
#用input()獲取郵件主題??
message['Subject'] = Header(subject, 'utf-8')
#在等號的右邊,是實例化了一個Header郵件頭對象,該對象需要寫入兩個參數,分別是郵件主題和編碼,然后賦值給等號左邊的變量message['Subject']。
解釋一下:第1行和第2行代碼是引入了email庫中的MIMEText模塊和Header模塊。
第4行代碼是用input()函數獲取郵件正文,第6行代碼是實例化一個MIMEText的郵件對象,這樣我們就構造了一個純文本郵件了。
這個MIMEText對象有三個參數,一個是郵件正文;另一個是文本格式,一般設置為plain純文本格式;最后一個是編碼,設置為utf-8,因為utf-8是最流行的萬國碼。
繼續看第8行代碼,是用input()函數獲取郵件主題,第10行代碼比較重要,我們仔細講解一下:message['Subject'] = Header(subject, 'utf-8'),等號右邊是實例化了一個Header郵件頭對象,該對象需要寫入兩個參數,分別是郵件主題和編碼。等號左邊的message['Subject']的變量是一個a['b']的代碼形式,它長得特別像字典根據鍵取值的表達,但是這里的message是一個MIMEText類的對象,并不是一個字典,那message['Subject']是什么意思呢?
其實,字典和類在結構上,有相似之處。請看下圖:
字典里面的元素是【鍵】和【值】一一對應,而類里面的【屬性名】和【屬性】也是一一對應的。我們可以根據字典里的【鍵】取到對應的【值】,同樣的,也可以根據類里面的【屬性名】取到【屬性】。
所以message['Subject']就代表著根據MIMEText類里面的Subject的屬性名取到該屬性。
需要注意的是,不是每一個類都可以這樣訪問其屬性的,之所以能這樣訪問是因為這個MIMEText的類實現了這個功能。所以,message['Subject'] = Header(subject, 'utf-8')?就是在為message['Subject']這個屬性賦值。好啦,到現在,我們就明白如何填寫主題和撰寫正文了。
接下來就是最后兩步:發送郵件和退出郵箱了。
來看代碼:
qqmail.sendmail(account, receiver, message.as_string())
#發送郵件,調用了sendmail()方法,寫入三個參數,分別是發件人,收件人,和字符串格式的正文。
qqmail.quit()
#退出郵箱
解釋一下:第33行代碼的意思是調用sendmail()發送郵件,括號里面有三個參數,第0個是發件人的郵箱地址,第1個是收件人的郵箱地址,第2個是正文,但必須是字符串格式,所以用as_string()函數轉換了一下。但是我們希望發送成功后能顯示“郵件發送成功”,失敗的時候能提示我們“郵件發送失敗”,可以使用try語句來實現。
try:
qqmail.sendmail(account, receiver, message.as_string())
print ('郵件發送成功')
except:
print ('郵件發送失敗')
qqmail.quit()
到此,發送郵件的程序就完成了,一起看看完整的代碼。
import smtplib
from email.mime.text import MIMEText
from email.header import Header
#引入smtplib、MIMETex和Header
mailhost='smtp.qq.com'
#把qq郵箱的服務器地址賦值到變量mailhost上,地址應為字符串格式
qqmail = smtplib.SMTP()
#實例化一個smtplib模塊里的SMTP類的對象,這樣就可以調用SMTP對象的方法和屬性了
qqmail.connect(mailhost,25)
#連接服務器,第一個參數是服務器地址,第二個參數是SMTP端口號。
#以上,皆為連接服務器。
account = input('請輸入你的郵箱:')
#獲取郵箱賬號,為字符串格式
password = input('請輸入你的密碼:')
#獲取郵箱密碼,為字符串格式
qqmail.login(account,password)
#登錄郵箱,第一個參數為郵箱賬號,第二個參數為郵箱密碼
#以上,皆為登錄郵箱。
receiver=input('請輸入收件人的郵箱:')
#獲取收件人的郵箱。
content=input('請輸入郵件正文:')
#輸入你的郵件正文,為字符串格式
message = MIMEText(content, 'plain', 'utf-8')
subject = input('請輸入你的郵件主題:')
#輸入你的郵件主題,為字符串格式
message['Subject'] = Header(subject, 'utf-8')
#以上,為填寫主題和正文。
try:
qqmail.sendmail(account, receiver, message.as_string())
print ('郵件發送成功')
except:
print ('郵件發送失敗')
qqmail.quit()
#以上為發送郵件和退出郵箱。
好,我們可以再次試著梳理一下剛剛的流程:
首先是連接服務器和登錄,然后就是發送,發送的內容是郵件數據。郵件數據由兩部分構成,一部分是郵件的主題,一部分是郵件的正文(即爬蟲獲取到的數據)。當然,發送的動作里必須填寫收件人,發送完畢后就可以退出郵箱了。
而smtplib庫主要負責的是橫向的連接服務器、登錄、發送和退出;而email庫主要負責的是郵件主題和正文。
好,現在,咱們來看看如何實現爬蟲的定時功能。
3.2 定時
關于時間,其實Python有兩個內置的標準庫——time和datetime。
但在這里,我們不準備完全依靠標準庫來實現,而準備選取第三方庫——schedule。原因在于:標準庫一般意味著最原始最基礎的功能,第三方庫很多是去調用標準庫中封裝好了的操作函數。比如schedule,就是用time和datetime來實現的。而對于我們需要的定時功能,time和datetime當然能實現,但操作邏輯會相對復雜;而schedule就是可以直接解決定時功能,代碼比較簡單,這是我們選擇schedule的原因。
這并不意味著time和datetime比schedule差,只是這個項目場景下,我們傾向于調用schedule。
馬上來看代碼,官方文檔上的代碼也很簡潔,你可以先嘗試著自己閱讀一下。
最上面的一行很好理解,因為是第三方庫,所以需要安裝。下面的代碼我們放到代碼框里好好研究一下:
import schedule
import time
#引入schedule和time
def job():
? ??print("I'm working...")
#定義一個叫job的函數,函數的功能是打印'I'm working...'
schedule.every(10).minutes.do(job) ? ? ? #部署每10分鐘執行一次job()函數的任務
schedule.every().hour.do(job) ? ? ? ? ? ?#部署每×小時執行一次job()函數的任務
schedule.every().day.at("10:30").do(job) #部署在每天的10:30執行job()函數的任務
schedule.every().monday.do(job) ? ? ? ? ?#部署每個星期一執行job()函數的任務
schedule.every().wednesday.at("13:15").do(job)#部署每周三的13:15執行函數的任務
while True:
? ? schedule.run_pending()
? ? time.sleep(1)
#15-17都是檢查部署的情況,如果任務準備就緒,就開始執行任務。
第1行和第2行,是引入schedule和time。
第5行和第6行,是定義了一個叫job()的函數,調用這個函數時,函數會打印I'm working...。
第9行-13行都是相關的時間設置,你可以根據自己的需要來確定。
第15-17行是一個while循環,是去檢查上面的任務部署情況,如果任務已經準備就緒,就去啟動執行。其中,第17行的time.sleep(1)是讓程序按秒來檢查,如果檢查太快,會浪費計算機的資源。
其實,就算不懂具體的代碼什么意思,我們先試著來用,發現誒,成功了,再去研究,也是不錯的。
為了展示一下schedule的作用,我們看下面這段代碼:是每兩秒就運行job()函數。
import schedule
import time
#引入schedule和time模塊
def job():
? ??print("I'm working...")
#定義一個叫job的函數,函數的功能是打印'I'm working...'
schedule.every(2).seconds.do(job) ? ? ? ?#每2s執行一次job()函數
while True:
? ? schedule.run_pending()
? ? time.sleep(1)
好啦,定時功能我們也都搞定了。也就是說,第二步分析過程,我們也搞定了。
4 代碼組裝
因為剛剛在分析過程里面,就已經分別搞定了三段程序,所以在這一部分,只要組合起來就好啦。我們一個一個功能來封裝,首先是爬蟲的代碼,封裝前的代碼是這樣的:
import requests
from bs4 import BeautifulSoup
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
url='http://www.weather.com.cn/weather/101280601.shtml'
res=requests.get(url,headers=headers)
res.encoding='utf-8'
bsdata=BeautifulSoup(res.text,'html.parser')
data1= bsdata.find(class_='tem')
data2= bsdata.find(class_='wea')
print(data1.text)
print(data2.text)
封裝后是這樣的:
import requests
from bs4 import BeautifulSoup
def weather_spider():
?headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}? ??
? ? url='http://www.weather.com.cn/weather/101280601.shtml'? ??
? ? res=requests.get(url,headers=headers)
? ? res.encoding='utf-8'
? ? soup=BeautifulSoup(res.text,'html.parser')
? ? data1= soup.find(class_='tem')
? ? data2= soup.find(class_='wea')
? ? tem=data1.text
? ? weather=data2.text
? ??return tem,weather
第4行代碼:定義這個函數叫weather_spider();第14行代碼:設置函數返回的變量是tem和weather。其他代碼都是和封裝前一致的。接著是郵件的程序,封裝前的代碼是這樣的:
import smtplib?
from email.mime.text import MIMEText
from email.header import Header
mailhost='smtp.qq.com'
qqmail = smtplib.SMTP()
qqmail.connect(mailhost,25)
account = input('請輸入你的郵箱:')
password = input('請輸入你的密碼:')
qqmail.login(account,password)
receiver=input('請輸入收件人的郵箱:')
content=input('請輸入郵件正文:')
message = MIMEText(content, 'plain', 'utf-8')
subject = input('請輸入你的郵件主題:')
message['Subject'] = Header(subject, 'utf-8')
try:
? ? qqmail.sendmail(account, receiver, message.as_string())
? ??print ('郵件發送成功')
except:
? ??print ('郵件發送失敗')
qqmail.quit()
封裝后是這樣的:
import smtplib
from email.mime.text import MIMEText
from email.header import Header
account = input('請輸入你的郵箱:')
password = input('請輸入你的密碼:')
receiver = input('請輸入收件人的郵箱:')
def send_email(tem,weather):
? ? mailhost='smtp.qq.com'
? ? qqmail = smtplib.SMTP()
? ? qqmail.connect(mailhost,25)
? ? qqmail.login(account,password)
? ? content= '親愛的,今天的天氣是:'+tem+weather
? ? message = MIMEText(content, 'plain', 'utf-8')
? ? subject = '今日天氣預報'
? ? message['Subject'] = Header(subject, 'utf-8')
? ??try:
? ? ? ? qqmail.sendmail(account, receiver, message.as_string())
? ? ? ??print ('郵件發送成功')
? ??except:
? ? ? ??print ('郵件發送失敗')
? ? qqmail.quit()
看第5-7行:定義account、password和receiver為全局變量,即用input()獲取到的數據。
第9行:定義了函數的名字叫send_email(),定義了兩個參數tem和weather。當然,等下需要把爬蟲獲取到的溫度信息和天氣信息傳遞給該函數的參數。
第14行:是把郵件正文寫為天氣數據。其他代碼基本一致。
好現在只剩定時功能了,可以和上面兩個程序組合在一塊兒了。
import requests
import smtplib
import schedule
import time
from bs4 import BeautifulSoup
from email.mime.text import MIMEText
from email.header import Header
account = input('請輸入你的郵箱:')
password = input('請輸入你的密碼:')
receiver = input('請輸入收件人的郵箱:')
def weather_spider():
?headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
? ?url='http://www.weather.com.cn/weather/101280601.shtml'
? ?res=requests.get(url,headers=headers)
? ?res.encoding='utf-8'
? ?soup=BeautifulSoup(res.text,'html.parser')
? ?tem1= soup.find(class_='tem')
? ?weather1= soup.find(class_='wea')
? ?tem=tem1.text
? ?weather=weather1.text
? ?return tem,weather
def send_email(tem,weather):
? ?mailhost='smtp.qq.com'
? ?qqmail = smtplib.SMTP()
? ?qqmail.connect(mailhost,25)
? ?qqmail.login(account,password)
? ?content= tem+weather
? ?message = MIMEText(content, 'plain', 'utf-8')
? ?subject = '今日天氣預報'
? ?message['Subject'] = Header(subject, 'utf-8')
? ?try:
? ? ? ?qqmail.sendmail(account, receiver, message.as_string())
? ? ? ?print ('郵件發送成功')
? ?except:
? ? ? ?print ('郵件發送失敗')
? ?qqmail.quit()
def job():
? ?print('開始一次任務')
? ?tem,weather = weather_spider()
? ?send_email(tem,weather)
? ?print('任務完成')
schedule.every().day.at("07:30").do(job)
while True:
? ?schedule.run_pending()
? ?time.sleep(1)
第1-7行是把所有引入都放到程序的頂部;從9-11行,把獲取數據也放到函數的外面;然后13-39行,我們都講過了。從41行開始,定義一個函數叫job();42行是打印'開始一次任務',為了記錄和顯示任務的開始。
第43行,是調用爬蟲函數weather_spider(),然后把這個函數內部return的兩個變量tem、weather賦值給job()函數里面的變量tem,weathe;第44行是調用函數send_email(),并且把參數傳入。
第45行打印'任務完成',表示這部分程序運行正常。
47-50行都是定時功能我們見過的函數,我們設定的是每天早上七點半把天氣信息傳遞給收件人。
到此,這一關的任務就圓滿完成了,我們學習了用Python發郵件,用Python定時執行一個程序,并且完成了一個每日爬取天氣并把結果發送到郵箱的程序。給你比個大拇哥。
不過,老師有個小小的提醒,如果你想要明早真正收到天氣信息的話,需要做兩件事:首先,讓該程序在本地電腦運行;其次,保持程序一直運行的狀態,和電腦在一直開機的狀態。因為如果程序結束或者電腦關機了的話,就不會定時爬取天氣信息了。
事實上,在程序員真實的開發環境中,程序一般都會掛在遠端服務器,因為遠端服務器24小時都不會關機,就能保證定時功能的有效性了。如果你也想讓程序掛在遠端服務器的話,需要自己去做一些額外的學習。