我用 Python 和 Twilio 实现自动化选课

Posted 派森学python

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我用 Python 和 Twilio 实现自动化选课相关的知识,希望对你有一定的参考价值。

大学生都知道那种选课时无课可选的痛苦,而我所在的大学甚至对大部分课程都不提供候补系统。我们每天不得不多次登录查看选课网站。这种机械操作似乎是计算机擅长的事,所以我着手用一些学过的 Python 知识和Twilio API来实现选课自动化

开始阶段

由于大学的课程注册系统需要密码登录,我们打算使用自建的简化版网站。出于演示的目的,CS 101 课程的空余名额将以 1 分钟 1 次的频率在 0 和 1 之间切换。

本项目中我们打算使用一些库来帮助我们。假设你已经安装了pip,使用下面的 pip 命令来安装需要的库:

1pip install requests==2.17.3 beautifulsoup4==4.6.0 redis==2.10.5 twilio==6.3.0 Flask==0.12.2

随着项目的深入,我们会仔细研究用到的每一个库。

抓取注册系统

我们需要写一个程序来帮助我们确定指定的课程是否有空余名额。这里我们使用网页抓取技术来实现,它将从网络上下载网页并寻找到重要的字段(课程名额)。RequestsBeautifulSoup是简化这个过程的两个非常流行的库:Requests 让获取网页变得更加简单,而 BeautifulSoup 帮我们找到网页中我们需要的部分。

号:923414804 群里有志同道合的小伙伴,互帮互助, 群里有视频学习教程和PDF!

# scraper.py

import requests

from bs4 import BeautifulSoup

 

URL = ‘http://courses.project.samueltaylor.org/‘

COURSE_NUM_NDX = 0

SEATS_NDX = 1

 

def get_open_seats():

r = requests.get(URL)

soup = BeautifulSoup(r.text, ‘html.parser‘)

courses = {}

 

for row in soup.find_all(‘tr‘):

cols = [e.text for e in row.find_all(‘td‘)]

if cols:

courses[cols[COURSE_NUM_NDX]] = int(cols[SEATS_NDX])

return courses

这里的关键是 get_open_seats 函数。此函数中,我们使用 requests.get 下载网页的 HTML 源码,然后使用 BeautifulSoup 解析它。我们使用 find_all(‘tr’) 获得表内的所有行,通过更新课程词典以显示指定课程的剩余名额。find_all 具有极其强大的功能,如果你对它感兴趣并想要深入了解,你可以查看官方文档。最后,我们返回课程词典,这样程序就能看到指定课程还有多少空余名额(比如,courses[‘CS 101’]是 CS 101 的空余名额)。

好极了,现在我们可以判断课程是否有空位了。Python 解释器是检验函数的好办法。将这段代码保存在文件中,并命名为 scraper.py,然后运行脚本并切入交互模式看看函数的功能:

$ python -i scraper.py

>>> get_open_seats()

{‘CS 101‘: 1, ‘CS 201‘: 0}

尽管一切顺利,但我们还没有解决这个问题,我们还需要在空余名额出现时,想办法通知用户。该是Twilio SMS出场的时候了!

通过 SMS 获取更新

构建用户界面时,我们希望化繁为简。本程序中,用户希望在课程座位开放时收到通知。最简单的解决办法是分享课程编号,我们通过建立和处理 webhook 实现订阅功能。我选择使用Redis(提供可以从多个进程访问的数据结构的工具)来存储订阅。

# sms_handler.py

from flask import Flask, request

import redis

 

twilio_account_sid = ‘ACXXXXX‘

redis_client = redis.StrictRedis(host=‘localhost‘, port=6379, db=0)

app = Flask(__name__)

 

@app.route(‘/sms‘, methods=[‘POST‘])

def handle_sms():

user = request.form[‘From‘]

course = request.form[‘Body‘].strip().upper()

 

redis_client.sadd(course, user.encode(‘utf-8‘))

 

if __name__ == ‘__main__‘:

app.run(debug=True)

现在我们使用一个叫做Flask的 Python 网络框架来创建小型服务,用于处理 SMS 信息。完成一些初始的设置后,我们设置 handle_sms 函数用于处理 /sms 端点的请求。利用这个函数,我们抓取用户的手机号以及他们寻找的课程,并将其存储在以课程命名的集合中。

做到获取订阅这步,一切还算顺利,但有一个明显的问题:用户界面太烂,它不能给用户提供反馈。我们想要回复用户,通知我们是否能够立刻服务他们的要求。要做到这一点,我们将提供一个TwiML 响应,额外需要的代码在下方高亮显示:

# sms_handler.py

from flask import Flask, request

import redis

from twilio.twiml.messaging_response import MessagingResponse

 

twilio_account_sid = ‘ACXXXXX‘

my_number = ‘+1XXXXXXXXXX‘

valid_courses = {‘CS 101‘, ‘CS 201‘}

 

redis_client = redis.StrictRedis(host=‘localhost‘, port=6379, db=0)

app = Flask(__name__)

 

def respond(user, body):

response = MessagingResponse()

response.message(body=body)

return str(response)

 

@app.route(‘/sms‘, methods=[‘POST‘])

def handle_sms():

user = request.form[‘From‘]

course = request.form[‘Body‘].strip().upper()

if course not in valid_courses:

return respond(user, body="Hm, that doesn‘t look like a valid course. Try something like ‘CS 101‘.")

 

redis_client.sadd(course, user.encode(‘utf-8‘))

return respond(user, body=f"Sweet action. We‘ll let you know when there are seats available in {course}")

 

if __name__ == ‘__main__‘:

app.run(debug=True)

我们针对以上代码做了两个大的改动。首先,我们验证用户是否在寻找一个有效的课程。其次,当用户请求更新时,我们对其响应。在响应函数中,我们建立了一个 TwiML 响应,用指定的信息内容回复指定的号码。

确保已安装 Redis并通过 redis 服务器命令启动它。将上述代码保存在一个命名为 sms_handler.py 的文件中,并通过 Python 运行它。

诚然,这里的响应消息有点傻,但我惊讶地看到用户很喜欢它们。有时候一些人为触发的反馈能带来更好的用户体验。

现在,让我们扩展早期的脚本,完善通知功能,真正满足那些想要在课程开放时获得通知信息的人。

# scraper.py

from twilio.rest import Client

 

client = Client(twilio_account_sid, token)

redis_client = redis.StrictRedis(host=‘localhost‘, port=6379, db=0)

 

def message(recipient, body):

message = client.messages.create(to=recipient, from_=my_number, body=body)

 

 

if __name__ == ‘__main__‘:

courses = get_open_seats()

for course, seats in courses.items():

  if seats == 0:

  continue

 

to_notify = redis_client.smembers(course)

for user in to_notify:

message(user.decode(‘utf-8‘),

body=f"Good news! Spots opened up in {course}. " +

"We‘ll stop bugging you about this one now.")

    redis_client.srem(course, user)

通过 Python 运行 scraper.py,我们对采集程序进行一次性测试。

使用 Cron 密切关注课程

虽然将查看课程注册网站的过程简化成了单个的脚本,我们仍希望脚本能够隔几分钟自动运行一次。通过使用Cron能够轻松解决这一问题。运行 crontab-e 并添加以下代码后,我们可以添加一个每三分钟运行一次的任务:

1*/3 * * * * /path/to/scraper.py

写入代码后,Cron 守护进程将每隔三分钟运行一次我们的采集程序。运行 crontab-l 后,我们可以看到计划任务。这就完成了!我们订阅课程更新后,就可以不用管它,专注于我们手边重要的事情。除了获得乐趣外,你的朋友会非常感激你,因为你让他们紧绷的神经得到了舒缓,“轻轻松松”就能选到想要的课程。依靠这个程序选到了想要的课程无疑是对我努力的最大的回报,但它同时也帮助我周围的很多人选到了心仪的课程。

技术分享图片
 

(—— CS 101)

(—— 收到,当课程 CS 101 有空位时,我们会立刻通知您!)

(—— 好消息,侦察到课程 CS 101 存在空位,我们将不再发送关于此课程的消息。)


以上是关于我用 Python 和 Twilio 实现自动化选课的主要内容,如果未能解决你的问题,请参考以下文章

使用 Twilio 和 Android 发送短信

Python利用Twilio(国际)以及腾讯云服务做一些事情

使用Python往手机发送短信(基于twilio模块)

Twilio 应用程序 - 更新通话资源结束通话

使用 ngrok 地址来电自动设置 Twilio webhook 地址

如何在呼叫状态更改为响铃时自动取消 Twilio 出站呼叫