1.开头
网站地址:广科大电费查询网站测试系统:Ubuntu 18.04.4 LTS(虚拟机)
python版本:Python 3.6.9 注:(python2和python3有很大的区别,这里都是用python3,所以安装的库也是安装python3的,应该用pip3命令,如果不懂可以百度一下)
定时器:crontab
建议:按顺序来看,虽说每章联系不是很大,但还是有些联系的,而且确保每章的程序(如果有的话)你都能执行成功,这样才能保证你能顺利的继续学习下一章。当然可能由于种种因素,可能会出现和这里不一样的错误,这个得自行百度解决了。
开发这个的背景:
就是因为老是欠费断电,导致舍友玩游戏掉线。🙃
本来理想的功能:
当电费低于一定的值可以自动发信息提醒
现在的功能:
每天或某天某段时间发邮件提醒
理想和现实的转变过程:
说来就是瓶颈
如:检测低于一定的电费的值实现方法:目前我想到的只有不断的Get or Post(毕竟是获取智慧校园的数据,不断请求可能会被封ip了,而且就算是自己提供的数据,也会浪费资源),或者是指定某段时间来Get or Post(这个还算可以,只是会有滞后)
对了,智慧校园的数据也不是实时更新的,所以也没必要不断Get or Post
一开始发信息是想用QQ的,QQ里面有个叫机器人的,刚好发现tx不允许第三方的机器人了。😕
后面发现邮箱也是可以有提醒的(在QQ里面),为什么我一直想到QQ呢,因为学生一般都会玩QQ的,肯定能准时收到信息。如果我重新做一个APP的话,肯定不会有人每天都打开看电费吧,而且这样就违背一开始的“懒”的意愿了
废话就这么多了,下面进入正题
2.python--爬电费信息
大家学过python的都知道requests模块吧,主角就是它了
import requests,json payload = {'RoomID': '000001011017003003'} i = 0 while i < 3: #多次get请求防止是因系统导致超时的 try: r = requests.get('http://zssdcz02.gxust.edu.cn/Home/GetRoomInfo', params=payload, timeout=2) r.encoding='utf-8' try: str = r.text str = json.loads(str) str = str['component'] RoomId = str['RoomId'] RoomPath = str['RoomPath'] RoomDk = str['RoomDk'] lishi = RoomDk[0]['Name']+":"+RoomDk[0]['Value'] shen = RoomDk[1]['Name']+":"+RoomDk[1]['Value'] except: #是防止获取的房间数据为空的 except requests.exceptions.RequestException: i += 1 #多次get请求防止是因系统导致超时的 if i>=3: #这里写如果超时执行的程序
程序不难写,都是一些数据处理
那个urlhttp://zssdcz02.gxust.edu.cn/Home/GetRoomInfo
就是智慧校园的里面的获取电费的api
怎么获取到这个api就不细说了,有兴趣的可以去了解一下抓包,然后去智慧校园里面尝试获取到这个api
细说一下代码里面的变量:
是房间的RoomId:payload (是需要提供的)
房间查询电费的参数:RoomId
房间对应的中文路径:RoomPath
房间的历史电费:lishi
房间的剩余电费:shen
我们已经能获取到电费信息(至于为什么用python来获取,主要还是为了定时发送的方便,其他语言应该也可以吧)
3.python--发送邮件
import smtplib from email.mime.text import MIMEText from email.header import Header # 第三方 SMTP 服务 mail_host="smtp.XXX.com" #设置服务器 mail_user="XXXX" #用户名 mail_pass="XXXXXX" #口令 sender = 'from@runoob.com' receivers = ['429240967@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 message = MIMEText('Python 邮件发送测试...', 'plain', 'utf-8') message['From'] = Header("菜鸟教程", 'utf-8') message['To'] = Header("测试", 'utf-8') subject = 'Python SMTP 邮件测试' message['Subject'] = Header(subject, 'utf-8') try: smtpObj = smtplib.SMTP() smtpObj.connect(mail_host, 25) # 25 为 SMTP 端口号 smtpObj.login(mail_user,mail_pass) smtpObj.sendmail(sender, receivers, message.as_string()) print ("邮件发送成功") except smtplib.SMTPException: print ("Error: 无法发送邮件")
我也是参考这个的,我的代码就不粘贴出来了(太多联系了,末尾会给源码),主要是搞懂怎么用python发送邮件
ps:smtplib忘记这个是不是内置的了,如果没有这个库而报错就自行安装就行了
注意:代码里面# 第三方 SMTP 服务
下面用QQ邮箱为例
mail_host="smtp.qq.com"
mail_user = "已开启SMTP的QQ邮箱账号"
mail_pass =
"授权码"
怎么开启SMTP?
登录QQ邮箱,进入设置-账户—POP3/SMTP服务,开启它,开启后会有个授权码,得记下来。
QQ邮箱官方问题查看开启SMTP,可以参考这个
代码原文链接
4.定时器
ubuntu安装cron 安装:apt-get install cron 启动:service cron start 重启:service cron restart 停止:service cron stop 检查状态:service cron status 查询cron可用的命令:service cron 检查Cronta工具是否安装:crontab -l 添加定时任务:crontab -e (不建议这个) 添加定时任务:crontab mail.cron(需要用到这个)(文件名随便)
好久了,忘记是不是要安装cron了,如果Ubuntu没有,就用这些命令行安装
原文地址(定时任务规则这里也有)5.定时器+爬电费信息+发送邮件
发送电费邮件的python代码:
import smtplib import re import requests from email.mime.text import MIMEText from email.header import Header #设置服务器(我用的gmail,你们用的什么就换成什么 #比如qq,163~记得登录网页版邮箱然后在设置里面修改支持smtp mail_host="smtp.qq.com" #用户名 mail_user="xxxxxx@qq.com" #邮箱密码 mail_pass="xxxxxx" sender = "xxxxxx@qq.com" receivers = ['1158797398@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 #读取我的自动爬虫获取的内容 #list改成str格式,否则编码不通过 payload = {'RoomID': '000001011017003003'} r = requests.get('http://zssdcz02.gxust.edu.cn/Home/GetRoomInfo', params=payload) r.encoding='utf-8' str = r.text pattern = r'[:]' str = re.findall(r"[\u4e00-\u9fa5\d+\.?\d*\/]+",re.split(pattern, str)[7])[0]+"\n"+re.findall(r"[\u4e00-\u9fa5]+",re.split(pattern, str)[9])[0] + "\n" +re.findall(r"\d+\.?\d*",re.split(pattern, str)[10])[0]+"\n"+re.findall(r"[\u4e00-\u9fa5]+",re.split(pattern, str)[11])[0]+"\n"+re.findall(r"\d+\.?\d*",re.split(pattern, str)[12])[0] print (str) str = "" f=str # 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码 message = MIMEText(f, 'html', 'utf-8') message['From'] = Header("自动爬虫", 'utf-8') message['To'] = Header("你好", 'utf-8') subject = '电费提醒' message['Subject'] = Header(subject, 'utf-8') try: smtpObj=smtplib.SMTP_SSL(mail_host,465) smtpObj.set_debuglevel(1) # smtpObj.connect(mail_host, 25) # 25 为 SMTP 端口号 smtpObj.login(mail_user,mail_pass) smtpObj.sendmail(sender, receivers, message.as_string()) print ("邮件发送成功") except smtplib.SMTPException: print ("Error: 无法发送邮件") "+str+""+"
在终端用命令行运行python
python3 文件名.py
(开头已经说明了用的是python3)
定时任务文件——mail.cron文件内容:
57 14 * * * python3 文件路径/tomail.py >> 文件路径/tomaillog.txt
简单解释一下* * * * * + 命令
是crontab定时格式
所以57 14 * * *
是时间
python3 文件路径/tomail.py >> 文件路径/tomaillog.txt
是命令
python3 文件路径/tomail.py
是执行python的命令
>>
是Linux的重定向(可以百度一下)
所以>> 文件路径/tomaillog.txt
是把运行的输出写出到文件tomaillog.txt中
可以把代码copy过去试试
copy执行步骤:
把57 14 * * * python3 文件路径/tomail.py >> 文件路径/tomaillog.txt
写进mail.cron文件
用命令crontab 文件路径/mail.cron
使其定时任务生效
可以使用crontab -l
查看是否添加成功
然后就可以等待时间的到来,看看是不是能按时发送邮件了
6.python连接数据库获取数据并自动写成mail.cron
python代码:(给这个代码名字:mailcron.py后面会用到这个名字)
import pymysql import time import os # 配置数据库 # 打开数据库连接 db = pymysql.connect("localhost","账号","密码","数据库" ) # 使用cursor()方法获取操作游标 cursor = db.cursor() # SQL 查询语句 sql = "SELECT * FROM 表名" #临时数据只用于判断之前有没有这个数据 data = [] #存储corn的任务 str_=[] try: # 执行SQL语句 cursor.execute(sql) # 获取所有记录列表 results = cursor.fetchall() for row in results: #这里的row[5]、row[6]、row[7]、row[8]要看自己的字段(这个不懂可以百度学一下数据库) if row[8] in data: continue else: data.append(row[8]) str_.append(row[7]+" "+row[6]+" "+"* * "+row[5]+" python3 "+mulu+"/tomail.py "+row[7]+","+row[6]+","+row[5]+" >> "+文件路径+"/tomaillog.txt\n") except: print (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())+"--Error: unable to fetch data") file = 文件路径+"/mail.cron" f = open(file, mode='w') f.writelines(str_) f.close() os.system('crontab '+文件路径+'/mail.cron')
pymysql库是要安装的pip3 install PyMySQL
(pip3前面说过)
为什么用数据库?
原因很简单,用户要求的定时是不一样的,当用户修改时间或有新用户注册就要重新定制任务表,所以只能通过数据库了。
最后的os.system('crontab '+文件路径+'/mail.cron')
这个是命令crontab 文件路径+mail.cron
就是要重新生效定时任务
可以知道这里的mailcron.py是用来写新的任务并且生效
7.通过python形成一个api执行python代码
这个是我的一大突破,在此之前我一直找不到能通过http的执行代码的。如果看过我之前的文章——Natapp和花生壳互补的知道,我那时用尽了方法都试着通过http执行python代码
为什么要这样呢?
通过上面的第6已经可以正常定制用户的定时需求。但是我们不可能每时每刻都要靠人工来执行python吧。如果只让某个时间段执行这个python,就不能达到及时生效新的任务,所以就要在用户注册或修改定时任务的时候触发执行python代码
进入正题:python代码:
import flask from flask import request import os from flask_cors import CORS #创建一个服务,把当前这个python文件当做一个服务 server = flask.Flask(__name__) #server.route()可以将普通函数转变为服务 登录接口的路径、请求方式 @server.route('/expy',methods=['get','post']) def expy(): try: os.system("python3 "+文件路径+"/mailcron.py") resu={'code':200,'message':'执行成功'} except: resu={'code':404,'message':'执行失败'} return json.dumps(resu,ensure_ascii=False) if __name__== '__main__': server.run(debug=False,port = 8888,host='0.0.0.0')#指定端口,host,0.0.0.0代表不管几个网卡,任何ip都可访问
需要安装:pip3 install flask
运行之后要挂着才可以用,访问0.0.0.0:8888/expy
或者 本地IP:8888/expy
(只能是内网就是本机),就可以看看是否出现了这个{'code':200,'message':'执行成功'}
如果是要后台运行可以试试nohup python -u 文件名.py > 文件名.log 2>&1 &
看到代码def expy():os.system("python3 "+文件路径+"/mailcron.py")
可以知道在访问http时是执行expy函数,函数里面执行的就是6中的python代码
所以只要在用户修改定时任务或注册新用户添加新的定时任务时,访问该http就可以重新生效定时任务了(这个是前端调用接口的问题了,这里不涉及)
8.对上面的总结一下
上面的结合起来就可以实现定时发送电费邮件了,这也是我的第一个版本系统
我来理一下思路:
首先得挂上7的python代码,建立一个api。(生效定时任务)
确保6的python能正常读取数据库信息并写出 文件名.cron,并执行它,可以通过crontab -l
来查看是否已经添加任务
api ---> 生效crontab任务
5的发送邮件python代码可以修改一下,添加时间判断,判断只有这个时间段要求发送的用户才发送邮件
api ---> 生效crontab任务 ---> 到时间crontab任务执行发送邮件的python代码
下面是示意图:
9.进阶——写一个开机自启服务
上面的文章已经完全实现了定时发送邮件
但还是有缺陷的,你想象一下每次重启服务器,是不是得要重新开启api服务才可以呢?
我之前的一篇文章——在Ubuntu系统上安装NATAPP(内网穿透)里面也涉及到了一点,但当时没有深入了解。接下来我们来看看,Systemd服务(听说ubuntu从16.04开始不再使用initd管理系统,改用systemd了)
记得这些命令行:
1.sudo systemctl enable my #开机启动 (这个是自己写的my.service文件)
2.sudo systemctl start my #启动服务
3.sudo
systemctl status my #检查启动是否成功
4.sudo systemctl stop my #停止服务
写service文件可以参考内置的service文件和这些文章——博客1博客2
写的过程艰辛就不说了😭:附上代码:
[Unit] Description=api After=syslog.targer network.target [Service] Type=simple ExecStart=python3 路径/api.py [Install] WantedBy=multi-user.target
把写好的service文件放在/etc/systemd/system
目录下,应该不用给什么权限的,因为我没有给也可以(如果需要的话,可以自行百度一下)
依次输入1.sudo systemctl enable service文件名 #开机启动
2.sudo systemctl start service文件名 #启动服务
3.sudo
systemctl status service文件名 #检查启动是否成功
看看有没有成功,在状态里面要出现下面的情况才是成功:
你们一定不成功
肯定出现import flask(不要打我脸)
当然也可能是其他模块找不到的错误,要看你的代码了
注意啊:确保之前在终端执行该python文件是成功的,如果一开始都不可以就不要继续了,回去把该做好的做好了(懂吧)。
如果真的,那看看下面怎么解决
这个确实百度很难找到答案,主要是这个service少
一开始是觉得service问题,但想想这个明显是python问题,但是这个python直接在终端执行就可以。
确实想来想去还是python的问题,应该路径或权限问题的。你们想一想,我们在运行python代码的时候是不是用python3 文件.py
但前提是不是已经在这个目录下打开的终端(可能你们不是,但我是😂)?
其实如果是利用这些自动执行的东西,你不给路径什么的,它就不知道怎么去找到文件或者它只能在它本目录找
如果给定路径了,它肯定能找到,比如/usr/bin/python3 路径/文件.py
这个就是为什么我在上面的代码中都是写路径+文件
顺着这个思路(不一定对的啊):
我们假设:用终端运行时能自动找到python3的路径和第三方库的路径,系统自动去执行的就找不到第三方库路径
sys.path——是python的搜索模块的路径集,在终端执行下面的前两行代码就可以获取,它是一个list,list的第一个是当前python执行的目录,其他应该就是库的路径吧(不是很懂)
>>> import sys >>> sys.path ['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/wwl/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages', '/usr/local/lib/python3.6/dist-packages/PyMySQL-0.7.4-py3.6.egg', '/usr/lib/python3/dist-packages']
import flask不了,就想象为找不到该库的路径。所以可以直接在python执行时加上import sys
sys.path.append(sys.path) #添加库的路径
发现还不行,但仔细一想,如果这样可以的话,为什么一开始的不行?你想啊,如果都能获取完整的sys.path了,还需要加上吗?
所以我先在终端获取到sys.path,直接写在代码里sys.path.append(获取到sys.path)
一定得写在python代码的最上面,即import sys
sys.path.append("内容")
import ...
.......
如果理解了上面的,就知道为什么了
哈哈可以了😭,注意啊,你每次修改的service文件,需要重新开启服务,即先stop停止(就是上面的命令行的),再enable(不知道重不重要),最后star,输入status这样才能看新的状态
开启服务就已经完成,可以试试重启服务器,然后在浏览器打开0.0.0.0:8888/expy
,看看是不是已经自动开启api了。但还是有其他的问题,下一章再讲这个问题
10.给crontab指定用户
看标题就知道上面说的问题了。
本来以为一切都可以了,谁知道用service开启的api是不能正常生效定时器的。api执行了这个os.system("python3 "+文件路径+"/mailcron.py")
(生成和生效)
但mailcron.py
里面的os.system('crontab '+文件路径+'/mail.cron')
执行不了(要说我怎么发现的,知道什么叫控制变量法吗,懂吧😖😭)
其实mailcron.py
里面就是生成定时任务文件,并执行。在访问api的时候其实是可以生成文件了,但查看crontab -l 没有生效,所以推测是上面的错误
通过查找发现crontab [ -u user ] file
发现了啥?我们之前是crontab file
懂了吧。假如我们第9章的想法是对的,所以再次利用机器的执行思维,可以把这个理解为:
在终端运行crontab file
有默认参数[ -u user ]——就是当前正在输入的用户(指系统的用户(一个系统是可以有多个用户的),不要把它理解成那个在网站提交信息的用户了,下面也是)
而在服务中,不知道是谁?或者说就是它指定了root用户(可以理解成系统正在输入,所以是root用户)——已经证实。所以得指定用户,不然它觉得是root用户的定时任务。
把mailcron.py
中的改成os.system('crontab -u 你的系统用户名 '+文件路径+'/mail.cron')
目前来说已经完美了,但肯定还有不足或者BUG的,等有再更新啊。
写成这个后台的心情
11.该项目的不足
1.邮箱是有限制。
为了防止垃圾邮箱,QQ邮箱和其他邮箱都有限制。
听说QQ邮箱每天100件
所以这个就限制了大量用户的使用
可以解决的方法——自制邮箱服务器、购买邮箱
当然都没有试过,有兴趣的可以自行了解一下
自制邮箱服务器可能会有以下限制:
1)如果是腾讯云、阿里云等服务器,都是不给制作邮箱服务器的,默认不提供25端口的,可以自己百度一下
2)就算你上面的成功了,你发送的邮件可能会被判为垃圾邮件(邮箱域名的原因),因为收件箱有个过滤垃圾邮件的机制。
2.个人写代码能力的不足
可以看出,我写的代码只是为了完成某些任务,还没有达到那种境界,还需多打多加强
但唯一可以承认的,我写代码的思想正在变成高内聚低耦合
代码还可以优化的,等有时间再弄了
ps:该教程仅供参考学习,不一定要按本人的思路(而且还不一定对的)来。大家可以有自己的想法、做法。