定時與郵件
在這一關,我們希望為一般的爬蟲程序新增兩個實用性比較強的功能:
第一是定時功能,即程序可以根據我們設定的時間自動爬取數據;第二是通知功能,即程序可以把爬取到的數據結果以郵件的形式自動發送到我們的郵箱。
這兩個功能可以讓爬蟲程序定時向我們匯報。
試想一下,如果你是一位股票(或比特幣)的持有者,你希望及時爬取股票(或比特幣)每日的價格數據,方便你能及時賣出或買入,那每天都去啟動一遍爬蟲程序是極其不高效的。
而此時,如果你的爬蟲程序有定時和發送郵件功能,能自動爬取每天的數據,并且只有當價格達到某個你設置的價位時,才通知你可以有所行動了,平時都不打擾你,是不是很爽?
不止如此,如果你有特別想看的演唱會,但一開售就賣完了,有定時和發送郵件功能的爬蟲程序同樣可以辛勤地幫你刷票,當刷到有余票時,馬上通知你去購票,多好。(買火車票也是一樣的道理噢)
這兩個功能不僅能幫你獲取這種實時變化的數據,還可以幫你獲取周期性的數據。
比如,你所在的公司每周都會把周報發到官網上,而你所在的部門是由你去負責下載周報,并整理相關信息,再傳遞給部門成員。那如果有定時和通知功能的程序,每周你就可以靜待程序把更新的周報信息爬下來,并自動發送到你郵箱。
我們選擇的項目是——自動爬取每日的天氣,并定時把天氣數據和穿衣提示發送到你的郵箱。
之所以選擇這個相對樸實的爬蟲項目,是因為天氣每天都會有變化,那么在學完這一關之后,不出意外,你就可以在明早收到天氣信息了。以此,親身體驗程序的作用。
分析過程
總體上來說,可以把這個程序分成三個功能塊:【爬蟲】+【郵件】+【定時】
對爬蟲部分,我們比較熟悉;而對通知部分,選擇的是用郵件來通知,我們將使用smtplib、email庫來實現這一需求;對定時功能,有一個schedule,方便好用。
這三個功能對應的是三段代碼,分別寫出三段代碼后再組裝起來,就能實現我們的項目目標。
對于曾經在Python基礎課學過發送郵件的同學,如果你對這部分知識比較熟悉,等下講到郵件部分時,你可以選擇跳過。不過,還是建議你可以簡單復習一下。
爬蟲
在百度搜索天氣,彈出來的第一個網址是:
http://www.weather.com.cn/weather/101280601.shtml
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)
print(res.text)
print(res.status_code)
運行結果返回的是200,證明狀態是正常的,再來看看網頁源代碼,滑動看看:
等等,好像出現了一些奇怪的東西…(⊙o⊙)噢,是亂碼,這意味著出現了編碼問題。
不過還好,我們在第0關就知道碰到編碼可以怎么解決,用response.encoding屬性就好。好滴,那我們在網頁上點擊"右鍵"——“查看網頁源代碼”,會彈出一個新的標簽頁,然后搜索charset,查看一下編碼方式。
那么只要用response.encoding轉換一下編碼就可以了
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.text)
print(res.status_code)
接下來,就可以用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'
html=res.text
soup=BeautifulSoup(html,'html.parser')
items=soup.find('ul',class_='t clearfix').find_all('li')
for item in items:
print(item.find('h1').text+':',end='\t')
print(item.find(class_='wea').text,end='\t')
print(item.find(class_='tem').text)
進入到郵件功能部分的學習,先來模仿一下平時我們發郵件時計算機的操作:
我們的代碼邏輯也會按照上圖來進行,并且在其中用到兩個庫——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行代碼是引入庫,第2行代碼是qq郵箱的服務器地址,這個地址是可以通過搜索引擎查到的。
此刻,我們用的是qq郵箱,所以搜索qq郵箱的smtp服務器地址,如果你之后想用網易郵箱,也可以搜索網易郵箱的smtp服務器地址。
第5行代碼是實例化了一個smtplib里的SMTP對象。
第7行代碼是用SMTP對象的connect()方法連接服務器,第一個參數是獲取到的服務器地址,第二個參數是SMTP端口號——25。
端口號的選擇不是唯一的,但是25是一個最簡單、最基礎的端口號,所以我們填25。
連接服務器就講完了,馬上來看第1和第2步:通過賬號和密碼登錄郵箱;填寫收件人。
來看登錄郵箱的代碼(第11行為新增代碼):
import smtplib
#smtplib是python的一個內置庫,所以不需要用pip安裝
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('請輸入收件人的郵箱:')
#獲取收件人的郵箱
解釋一下從11行新增的代碼:第11行是用input()獲取郵箱賬號。第12行是用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’]這個屬性賦值。
好啦,到現在,我們就明白如何填寫主題和撰寫正文了。
接下來就是最后兩步:發送郵件和退出郵箱了。
來看代碼(從33行開始看):
import smtplib
#smtplib是python的一個內置庫,所以不需要用pip安裝
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('請輸入收件人的郵箱:')
#獲取收件人的郵箱
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']。
qqmail.sendmail(sender, receiver, message.as_string())
#發送郵件,調用了sendmail()方法,寫入三個參數,分別是發件人,收件人,和字符串格式的正文。
qqmail.quit()
#退出郵箱
解釋一下:第33行代碼的意思是調用sendmail()發送郵件,括號里面有三個參數,第0個是發件人的郵箱地址,第1個是收件人的郵箱地址,第2個是正文,但必須是字符串格式,所以用as_string()函數轉換了一下。
但是我們希望發送成功后能顯示“郵件發送成功”,失敗的時候能提示我們“郵件發送失敗”,可以使用try語句來實現。
try:
qqmail.sendmail(sender, 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')
#實例化一個MIMEText郵件對象,該對象需要寫進三個參數,分別是郵件正文,文本格式和編碼
subject = input('請輸入你的郵件主題:')
#輸入你的郵件主題,為字符串格式
message['Subject'] = Header(subject, 'utf-8')
#在等號的右邊是實例化了一個Header郵件頭對象,該對象需要寫入兩個參數,分別是郵件主題和編碼,然后賦值給等號左邊的變量message['Subject']。
#以上,為填寫主題和正文。
try:
qqmail.sendmail(account, receiver, message.as_string())
print ('郵件發送成功')
except:
print ('郵件發送失敗')
qqmail.quit()
#以上為發送郵件和退出郵箱。
更多的功能(比如發送附件等)同學們可以在課外主動學習。
好,我們可以再次試著梳理一下剛剛的流程:
首先是連接服務器和登錄,然后就是發送,發送的內容是郵件數據。郵件數據由兩部分構成,一部分是郵件的主題,一部分是郵件的正文(即爬蟲獲取到的數據)。
當然,發送的動作里必須填寫收件人,發送完畢后就可以退出郵箱了。
而smtplib庫主要負責的是橫向的連接服務器、登錄、發送和退出;而email庫主要負責的是郵件主題和正文。
好,現在,咱們來看看如何實現爬蟲的定時功能。
定時
關于時間,其實Python有兩個內置的標準庫——time和datetime(我們在基礎課也學過time.sleep())。
但在這里,我們不準備完全依靠標準庫來實現,而準備選取第三方庫——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)
#13-15都是檢查部署的情況,如果任務準備就緒,就開始執行任務。
第1行和第2行,是引入schedule和time。
第5行和第6行,是定義了一個叫job()的函數,調用這個函數時,函數會打印I’m working…。
第9行-13行都是相關的時間設置,你可以根據自己的需要來確定。
第15-17行是一個while循環,是去檢查上面的任務部署情況,如果任務已經準備就緒,就去啟動執行。其中,第15行的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)
好啦,定時功能我們也都搞定了。也就是說,第二步分析過程,我們也搞定了。
代碼組裝
因為剛剛在分析過程里面,就已經分別搞定了三段程序,所以在這一部分,只要組合起來就好啦。
首先是爬蟲的代碼,封裝后為:
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'
html=res.text
soup=BeautifulSoup(html,'html.parser')
item=soup.find('ul',class_='t clearfix').find('li')
weather=item.find(class_='wea').text
tem=item.find(class_='tem').text
return weather,tem
第3行代碼:定義這個函數叫weather_spider();第13行代碼:設置函數返回的變量是tem和weather。其他代碼都是和封裝前一致的。
接著是郵件的程序,封裝后的代碼是這樣的:
import smtplib
from email.mime.text import MIMEText
from email.header import Header
account = input('請輸入你的郵箱:')
password = input('請輸入你的密碼:')
receiver = input('請輸入收件人的郵箱:')
def send_email(tem,weather):
global account,password,receiver
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行:把用input()獲取數據的部分全部放到函數外面,因為這些數據是有可能改變的。
第9行:定義了函數的名字叫send_email(),定義了兩個參數tem和weather。當然,等下需要把爬蟲獲取到的溫度信息和天氣信息傳遞給該函數的參數。
第10行:定義account、password和receiver為全局變量,即用input()獲取到的數據.
第15行:是把郵件正文寫為天氣數據。其他代碼基本一致。
好現在只剩定時功能了,可以和上面兩個程序組合在一塊兒了。
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import requests
from bs4 import BeautifulSoup
import schedule
import time
def weather_spider():
global tem,weather
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'
html=res.text
soup=BeautifulSoup(html,'html.parser')
item=soup.find('ul',class_='t clearfix').find('li')
weather=item.find(class_='wea').text
tem=item.find(class_='tem').text
return tem,weather
account = input('請輸入你的郵箱:')
password = input('請輸入你的密碼:')
receiver=input('請輸入收件人的郵箱:')
def send_email(tem,weather):
global account,password,receiver
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-40行,我們都講過了。
從42行開始,定義一個函數叫job();43行是打印’開始一次任務’,為了記錄和顯示任務的開始。
第44行,是調用爬蟲函數weather_spider(),然后把這個函數內部return的兩個變量tem、weather賦值給job()函數里面的變量tem,weathe;第45行是調用函數send_email(),并且把參數傳入。
第46行打印’任務完成’,表示這部分程序運行正常。
48-51行都是定時功能我們見過的函數,我們設定的是每天早上七點半把天氣信息傳遞給收件人。
有個小小的提醒,如果你想要明早真正受到天氣信息的話,需要做兩件事:
首先,讓該程序在本地電腦運行,而不是在課程系統里運行,因為課程的系統是會銷毀程序的進程的。
其次,保持程序一直運行的狀態,和電腦在一直開機的狀態。因為如果程序結束或者電腦關機了的話,就不會定時爬取天氣信息了。
事實上,在程序員真實的開發環境中,程序一般都會掛在遠端服務器,因為遠端服務器24小時都不會關機,就能保證定時功能的有效性了。如果你也想讓程序掛在遠端服務器的話,需要自己去做一些額外的學習。
好啦,這一關就完成啦。下一關,我們會學習一個新技能——協程。它能夠成倍提高我們的代碼運行速度,當你遇到海量數據抓取的任務時,它能夠為你提供有力的幫助。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
