爬虫笔记九----定时与邮件、
来自风变教程
定时与邮件
一般的爬虫程序新增两个实用性比较强的功能:
第一是定时功能,即程序可以根据我们设定的时间自动爬取数据;
第二是通知功能,即程序可以把爬取到的数据结果以邮件的形式自动发送到我们的邮箱。
这两个功能可以让爬虫程序定时向我们汇报。
这两个功能不仅能帮你获取这种实时变化的数据,还可以帮你获取周期性的数据。
按照一向以来的规矩,实现一个项目的流程是这样的:
明确目标
选择的项目是——自动爬取每日的天气,并定时把天气数据和穿衣提示发送到你的邮箱。
分析过程
总体上来说,可以把这个程序分成三个功能块:【爬虫】+【邮件】+【定时】对爬虫部分,我们比较熟悉;而对通知部分,选择的是用邮件来通知,我们将使用smtplib、email库来实现这一需求;对定时功能,有一个schedule,方便好用。这三个功能对应的是三段代码,分别写出三段代码后再组装起来,就能实现我们的项目目标。
我们先来看爬虫部分的程序:爬虫
在百度搜索天气,弹出来的第一个网址是:
http://www.weather.com.cn/weather/101280601.shtml。
点进去的是中国气象网的天气预报:
我打开这个网站的时间是2021年4月5日,我所在的地点是北京通州
我要爬取的是4月6日的天气,即下图框内的“20~8℃”和“多云”两个数据:
很自然地,我们点击"右键"——“检查”——“Network”,刷新页面,点看第0个请求:
数据放在HTML里,没问题。那我们点击Elements:
可以发现,温度数据放在
之下。
同样,可以发现“多云”这个数据所在的位置:
在网页源代码里面搜索观察了一番,发现可以使用class="wea"和class="tem"来匹配目标数据。
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转换一下编码就可以了
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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68'}
url='http://www.weather.com.cn/weather/101010600.shtml'
res=requests.get(url,headers=headers)
res.encoding='utf-8'
bsdata=BeautifulSoup(res.text,'html.parser')
#使用bs模块解析获取到的数据
data1= bsdata.findall(class_='tem')
#使用find()取出天气的温度数据
data2= bsdata.findall(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,465)
#连接服务器,第一个参数是服务器地址,第二个参数是SMTP端口号。
解释一下:第1行代码是引入库,第2行代码是qq邮箱的服务器地址,这个地址是可以通过搜索引擎查到的。
这样就拿到了qq邮箱的smtp地址。此刻,我们用的是qq邮箱,所以搜索qq邮箱的smtp服务器地址,如果你之后想用网易邮箱,也可以搜索网易邮箱的smtp服务器地址。
第5行代码是实例化了一个smtplib里的SMTP对象。
第7行代码是用SMTP对象的connect()方法连接服务器,第一个参数是获取到的服务器地址,第二个参数是SMTP端口号——465。端口号的选择不是唯一的.
连接服务器就讲完了,马上来看第1和第2步:通过账号和密码登录邮箱;填写收件人。
来看登录邮箱的代码(第11行为新增代码):
import smtplib
#smtplib是python的一个内置库,所以不需要用pip安装
mailhost='smtp.qq.com'
#把qq邮箱的服务器地址赋值到变量mailhost上
qqmail = smtplib.SMTP()
#实例化一个smtplib模块里的SMTP类的对象,这样就可以SMTP对象的方法和属性了
qqmail.connect(mailhost,465)
#连接服务器,第一个参数是服务器地址,第二个参数是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步都完成了。
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’]这个属性赋值。
好啦,到现在,我们就明白如何填写主题和撰写正文了。
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()
#以上为发送邮件和退出邮箱。
关于如何用Python发送邮件,就讲到这里。
好,我们可以再次试着梳理一下刚刚的流程:
首先是连接服务器和登录,然后就是发送,发送的内容是邮件数据。邮件数据由两部分构成,一部分是邮件的主题,一部分是邮件的正文(即爬虫获取到的数据)。
当然,发送的动作里必须填写收件人,发送完毕后就可以退出邮箱了。
而smtplib库主要负责的是横向的连接服务器、登录、发送和退出;而email库主要负责的是邮件主题和正文。
好,现在,咱们来看看如何实现爬虫的定时功能。
定时
关于时间,其实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)
#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
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):
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)
复习
关于邮件,它是这样一种流程:
我们要用到smtplib和email库,前者负责连接服务器、登录、发送和退出的流程。后者负责邮件的标题与正文。
对于定时,我们选取了schedule模块,它的用法非常简洁,官方文档里是这样讲述:
最后一个示例代码,是这个模样:
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()
#以上为发送邮件和退出邮箱。
对于定时,我们选取了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都是检查部署的情况,如果任务准备就绪,就开始执行任务。
如果你想要明早真正受到天气信息的话,需要做两件事:
首先,让该程序在本地电脑运行,而不是在课程系统里运行,因为课程的系统是会销毁程序的进程的。
其次,保持程序一直运行的状态,和电脑在一直开机的状态。因为如果程序结束或者电脑关机了的话,就不会定时爬取天气信息了。
事实上,在程序员真实的开发环境中,程序一般都会挂在远端服务器,因为远端服务器24小时都不会关机,就能保证定时功能的有效性了。如果你也想让程序挂在远端服务器的话,需要自己去做一些额外的学习。