我的第一个自动刷作业脚本(大起大落的selenium经验分享)
起因
故事的开始是大二的上学期,有一门叫计算机结构(computer organization)的课。新教授这门课的教授在原来的政策上做了一些变动。他引入了一个叫做zybook的作业平台来确保我们能跟上每周的课的进度,即每周做一章(400-500道题,前几周甚至有1500题一周的章节)各种各样的小题。
但是!!!
这些题目简直又多又无聊!!
这些题目主要由选择题,填空题,视频播放题(多个阶段的动画演示,需要你看完一个section点击继续以进入下个section)构成。听着好像很多样,但是每道题,尤其最开始的几周,都简单的就像给完全没上过课的人做的...
就这样,每个周末都得做在床上耗一下午一边看着柯南国配版一边应付这些无脑的题,让我不禁开始思考人生:是否可以写个脚本来省去这不必要的工程
可行性分析
题目性质
在这里就不得不提到zybook这些被叫做activity的题目最大的,让我眼前一亮直接打开ide开写脚本的特点:可以暴力破解。这上面所有的activity都没有做错这一概念,即你有无数次的试错机会。
举例来说,对于选择题,你只需要把所有选项都选一遍,就算做对了;对于填空题,它提供了一个双击按钮来展示答案;对于视频播放题,你只需要在每个section的动画播完后,点击一下继续的按钮就行
工具
很久很久以前,有位学数学的研究生学姐来问我能不能帮她爬取一个网站的论文。当时功力不到家的我一看到那网页是动态加载就直接认输了,失去了一个跟漂亮学姐聊天的好机会QAQ
自此之后,我就开始奋发图强!!搜遍全网,了解到有一个神奇的东西叫做selenium。
Selenium是一个自动化测试工具,可以模拟用户对网站进行操作,意味着它可以代替我完成那些定位元素,鼠标点击和复制粘贴工作。在这个情况下,没有工具能比它更能满足我的需求了
关于selenium的用法,由于篇幅原因,在此不再详细叙述,感兴趣的大家可以去看这个教程:tutorial
总结:完全可行,开始写码
code, code, code
准备工作
这一步没啥好说的,创建webdriver对象以进行之后的操作
if __name__ == "__main__":
browser = webdriver.Chrome()
action = ActionChains(browser)
browser.get('https://learn.zybooks.com/library')
登录
使用过的人都知道,使用自动化测试工具对网页进行操作不会进入正常的浏览器模式,而是进入专门给测试工具的窗口,类似于隐私模式。这就意味着你每次运行脚本都需要登陆一次,因为里面没有你的历史记录。这个是zybook平台的登陆界面
可以看到,两个Input元素非常显眼的耸立在那。对于这种数量少且位置确定的元素,直接用ID来定位就好。定位到之后用send_key()来把邮箱和密码输入进去
def login(browser):
user_id = browser.find_element(By.ID, "ember9")
user_id.clear()
user = input("input your username(mail address): ").strip()
user_id.send_keys(user)
password_id = browser.find_element(By.ID, "ember11")
password_id.clear()
password = input("input your password: ").strip()
password_id.send_keys(password)
browser.find_element(By.CLASS_NAME, "signin-button").click()
选择课程和章节
登录之后,你会进入zybook的主页面,里面有你注册的课程
检查html后会发现,每一个课程的元素都是名为class的class。那么用class来定位所有课程,之后再选择想要完成的课程就行。
然而出现了一个问题,heading虽然能区分课程,但是无法被点击。那么只好重新找到div里面肯定能被点击的a元素(可能还有别的元素可以点,这里当时懒得细想就直接去找a了)。这里使用了xpath去定位
def get_class(browser):
classes = browser.find_elements(By.CLASS_NAME, "heading")
print("----------")
for x, y in enumerate(classes):
if x == len(classes)-1:
break
print(str(x), ": ", y.text)
print("----------")
choose = int(input("input(0-" + str(len(classes)-2) + ") to choose the course: "))
while choose < 0 or choose > len(classes)-2:
print("invalid number")
choose = int(input("input(0-" + str(len(classes) - 2) + ") to choose the course: "))
browser.find_element(By.XPATH, "//div[@class='zybooks-container large']/a[" + str(choose+1) + "]").click()
之后我们会进入课程页面,里面有每个章节的目录。相同道理,我们需要选择我们想完成的章节来打开里面的section目录
(当然,你也可以直接遍历所有的章节,一次性全部做完。我只是懒得等它运行那么长时间,选择一周运行一次)
def get_week(browser, action):
xpath = "//ul[@class='table-of-contents-list fixed-header header-absent']/li"
week = browser.find_elements(By.XPATH, xpath)
choose = int(input("input week number(1-" + str(len(week)) + ") or -1 to quit: "))
if choose == -1:
return True
while choose < 1 or choose > len(week):
print("invalid number")
choose = int(input("input week number(1-" + str(len(week)) + "): "))
action.move_to_element(week[choose-1]).perform()
week[choose-1].click()
return False
选择题
现在我们终于来到了今天的重头戏,做题!
对于选择题,逻辑非常简单,我们定位所有的选项按钮,遍历并点击即可
def multiple_choice(browser, action):
multi_choice = browser.find_elements(By.CLASS_NAME, "zb-radio-button")
for x, y in enumerate(multi_choice):
if not y.is_enabled() and not y.is_displayed():
continue
action.move_to_element(y).perform()
y.click()
注意,这里,包括前面的get_week()
function,我们多了一个叫做action
的parameter。这是selenium里面的一个叫做Actionchain
的功能。
在之前的测试过程中,我发现有时候网页太长,元素与元素间隔相差太大,脚本会无法立刻跳到那个元素进行操作,因此会直接报错终止。所以,在这里我使用了actionchain,每次在进行操作前先把页面移动到那个元素的位置,这样就可以进行操作了。
并且,由于zybook的网页会有些区别,class定位有时会定位到不是选项按钮的不可见元素,所以我加了is_enable()
和is_displayed()
的if条件来防止误判
填空题
对于填空题,我们看到有一个show answer的按钮,我们可以双击来使右侧出现答案。因此,要做的是:
- 定位show answer按钮
- 点击,等一小段时间,再点击
- 定位答案,储存到变量里
send_key()
到input元素中
def filling(browser, action):
show_answer_list = browser.find_elements(By.CLASS_NAME, "show-answer-button")
for x, y in enumerate(show_answer_list):
if not y.is_enabled() and not y.is_displayed():
continue
action.move_to_element(y).perform()
y.click()
time.sleep(0.1)
y.click()
time.sleep(0.1)
# get answer
answer = browser.find_elements(By.CLASS_NAME, "forfeit-answer")[x].text
textarea = browser.find_elements(By.CLASS_NAME, "ember-text-area")[x]
textarea.send_keys(answer)
time.sleep(0.1)
browser.find_elements(By.CLASS_NAME, "check-button")[x].click()
time.sleep(0.5)
视频播放题!!! 最精彩的部分!!!
这个是三种题型里面写码逻辑相对复杂的题。首先,我们需要点击start按钮来开始播放视频。之后,我们需要不断判断视频告一段落没有。因为很显然,播放和暂停是同一个按钮,如果你在播放过程中点击,会暂停动画。只有在动画停止之后,才能点击以进入下一阶段。
经过一段时间的听音乐,观察,还有头脑风暴。我发现,当动画结束(也可以说是没开始)时,按钮的class是play-button。但当动画播放时,这个class会被移除。取而代之的是pause-button
也就是说,我们会有这么几种可能:
- 动画播放完了的之前的题,有play-button
- 当前的题目,播放时没有play-button,结束时有play-button
- 后面的题目,还没有点击start按钮,因此pay-button还没出现
那么,我们有已知最多能有多少个play-button,便可以用其来判断动画是否还在进行。
举个例子,假设我们现在做到第二道动画题,那么我们最多就应该有1+1=2
个play-button。我们保存下这个数字,并每隔一段时间去用play-button class去定位一次元素。
如果只定位到1个元素,就说明动画还在播放。反之如果定位到2个,就说明动画已经结束。
这时候,根据上面提到的可能我们知道,play-button定位到的元素列表里,我们在做的这道题在列表的最后一位。由此我们可以点击去进行下一段动画。
代码里我为求谨慎,又去检查了一下元素里有没有Bounce class。这个class只在动画结束一部分的时候出现。这样就确认一定是该元素
至于判断何时动画结束,移步到下一道题,类似于上面的bounce class,结束的按钮都会有一个rotate-180的class。我们只要每次检查一下该元素有无这个class就行
def video(browser, action):
video_list = browser.find_elements(By.CLASS_NAME, "start-graphic")
for x, y in enumerate(video_list):
if not y.is_enabled() and not y.is_displayed():
continue
action.move_to_element(y).perform()
y.click()
time.sleep(1)
# judge whether this animation is finished or not
while True:
time.sleep(3)
play_button_img = browser.find_elements(By.CLASS_NAME, "play-button")
if len(play_button_img) <= x: # animation is still processing
continue
else:
play_button_img = play_button_img[x]
judge_end = play_button_img.get_attribute("class").find("rotate-180")
judge = play_button_img.get_attribute("class").find("bounce")
if judge_end != -1: # the animation has finished
break
if judge != -1: # step finished
play_button_img.click()
意料之外,情理之中的意外
就在我渐入佳境,不断写码不断运行不断测试的时候,之前的一个伏笔在悄然回收。
就在写完动画题进行最终测试时,我突然不能登录进网站了。不管怎么刷新页面,网页一直都停在登陆界面。我缓缓打出一个问号,不会被教授或平台发现,账号被封了吧??!!
颤抖地抽完根烟,仔细思索了要不要drop这门课后,上楼坐下做最后的抵抗。
仔细的检查了一下console log后才发现,进不去是因为status变成429了,即too many requests。好家伙,世界线收束了
记得我介绍登录部分的时候说过,每次运行一次脚本都需要重新登录一次平台。这样频繁的测试代码,频繁的登录操作,让服务器以为我在进行ddos攻击啥的,把我暂时ban掉了。
那么原因了解了,幸好不是因为自动化测试。但是!这个ban的时间是多久呢?我查了很多经验,说严重的会一直拒绝访问,原本放下的心又开始紧张起来...幸好,24小时后我又能正常登录了WOW
结尾
经过一夜肾上腺素飙升的coding和429的大起大落后,生活和内心又恢复平静。但是!脚本还差最终测试!不作死到底怎么对得起极客之名(自称)!最终,脚本在一个摸鱼的讲座上悄悄发布到了github上。把脚本分享给了几个一起上课的朋友。从此,周末晚上少了几个坐在电脑前机械地点击选择题选项的计算机结构学生
思考
很久以前我看过Ele实验室老师的一个经验分享视频,他说编程是一个很纯粹的工科,与其枯燥的读书,不如多写多实验,一步一步走出自己的路,这样才能学到更多东西。我深以为然。计算机只是一个工具(当然工具也能与你建立深厚的关系),更重要的是运用这些技能的思想。学计算机的人能在数码的世界里自由的发挥,这是莫大的自由!所以,如果有天,你的脑中突然浮现出一些想法,或许只是一些碎片,或许很天马行空,请不要让它一闪而过。你可能会因此错过一个精彩的世界
最后,这个脚本的github链接:https://github.com/Yujjio/Zybook_Auto_Completer
我的第一个自动刷作业脚本(大起大落的selenium经验分享)的更多相关文章
- Selenium执行测试脚本稳定性的一些经验分享交流
Selenium执行测试脚本稳定性的一些经验分享交流 公司的自动化WEB测试框架IATA已上线运行了一段时间,期间发现一些脚本稳定性的问题,与大家分享一下. CASE执行游览器:ie firefox ...
- 用node写一个皖水公寓自动刷房源脚本
因为住的地方离公司太远,每天上下班都要坐很久的班车,所以最近想搬到公司旁边的皖水公寓住.去问了一下公寓的客服,客服说房源现在没有了,只能等到别人退房,才能在网站上申请到. 如果纯靠手动F5刷新浏览器, ...
- 网课应该这么刷(油猴Tampermonkey脚本自动刷课)
懒人福利 首先有些人不想学怎么用脚本,满足你们,压缩包解压之后直接登录即可.戳我下载 脚本已经集成好了,登录即可刷课.章节测试还会自动答题呦,正确率高达97%呦. 油猴及脚本安装 油猴的脚本不知可以刷 ...
- 自动备份并保存最近几天的SQL数据库作业脚本
DECLARE @filename VARCHAR(255) DECLARE @date DATETIME SELECT @date=GETDATE() SELECT @filename = 'G:\ ...
- python网课自动刷课程序-------selenium+chromedriver
python的强大之处就在于有许多已经写好的功能库提供,这些库强大且易用,对于写一些有特定功能的小程序十分方便. 现在就用pyhton的selenium+谷歌游览器写一个可以自动刷课的程序,以智慧树上 ...
- 【BZOJ-4590】自动刷题机 二分 + 判定
4590: [Shoi2015]自动刷题机 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 156 Solved: 63[Submit][Status ...
- BZOJ4590 自动刷题机
Description 曾经发明了信号增幅仪的发明家SHTSC又公开了他的新发明:自动刷题机--一种可以自动AC题目的神秘装置.自动 刷题机刷题的方式非常简单:首先会瞬间得出题目的正确做法,然后开始写 ...
- 被「李笑来老师」拉黑之「JavaScript微博自动转发的脚本」
故事的背景如下图,李笑来 老师于10月19日在 知乎Live 开设 一小时建立终生受用的阅读操作系统 的讲座,他老人家看到大家伙报名踊跃,便在微博上发起了一个 猜数量赢取iPhone7 的活动. 因为 ...
- 使用node自动刷房源并发送可入住房源到邮箱
因为住的地方离公司太远,每天上下班都要坐很久的班车,所以最近想搬到公司旁边的皖水公寓住.去问了一下公寓的客服,客服说房源现在没有了,只能等到别人退房,才能在网站上申请到. 如果纯靠手动F5刷新浏览器, ...
- BZOJ_4590_[Shoi2015]自动刷题机_二分答案
BZOJ_4590_[Shoi2015]自动刷题机_二分答案 Description 曾经发明了信号增幅仪的发明家SHTSC又公开了他的新发明:自动刷题机--一种可以自动AC题目的神秘装置.自动 刷题 ...
随机推荐
- 2022牛客OI赛前集训营-提高组(第一场) 奇怪的函数 根号很好用
奇怪的函数 考虑暴力,每次查询\(O(n)\)扫所有操作,修改\(O(1)\) 这启发我们平衡复杂度,考虑分块. 观察题目性质,可以发现,经过若干次操作后得到的结果一定是一个关于\(x\)的分段函数, ...
- C/S、B/S、Web的介绍(Web应用开发)
文章目录 1.C/S结构介绍 2.B/S结构介绍 3.Web介绍 3.1 .什么是web? 3.2 .Web的工作原理 3.3 客户端应用技术 3.4 服务端应用技术 1.C/S结构介绍 Client ...
- go-zero docker-compose 搭建课件服务(三):编写courseware api服务
0.转载 go-zero docker-compose 搭建课件服务(三):编写courseware api服务 0.1源码地址 https://github.com/liuyuede123/go-z ...
- 29.渲染器Renderer
什么是渲染器 渲染器就是将服务器生成的数据格式转为http请求的格式 渲染器触发及参数配置 在DRF配置参数中,可用的渲染器作为一个类的列表进行定义 但与解析器不同的是,渲染器的列表是有顺 ...
- Debian 参考手册之第6章Debian档案库
来源:https://www.debian.org/doc/manuals/debian-faq/ftparchives#oldcodenames 第 6 章 Debian 档案库 目录 6.1. 有 ...
- 加速乐逆向 cookies 参数
简介 加速乐用于解决网站访问速度过慢及网站反黑客问题. 爬取使用该技术网站时需要携带特定的cookies参数(有的是__jsl_clearance_s,有的__jsl_clearance),本项目以一 ...
- VirtualBox 下 CentOS7 静态 IP 的配置 → 多次踩坑总结,蚌埠住了!
开心一刻 一个消化不良的病人向医生抱怨:我近来很不正常,吃什么拉什么,吃黄瓜拉黄瓜,吃西瓜拉西瓜,怎样才能恢复正常呢? 医生沉默片刻:那你只能吃屎了 环境准备 VirtualBox 6.1 网络连接方 ...
- luoguP1505旅游(处理边权的树剖)
/* luogu1505 非常简单的处理边权的树剖题. 在树上将一条边定向,把这条边的权值赋给这条边的出点 树剖的时候不计算lca权值即可 */ #include<bits/stdc++.h&g ...
- ironic组件硬件自检服务——ironic-inspector
介绍 ironic-inspector是一个用于硬件自检的辅助型服务,它可以对被ironic组件管理的裸金属节点进行硬件自检,通过在裸金属节点上运行内存系统,发现裸金属节点的硬件信息,例如CPU数量和 ...
- 自学 TypeScript 第二天 编译选项
前言: 昨天我们学习了 TS 的数据类型,不知道大家回去以后练习没练习,如果你练习了一定会发现一个问题,我们的 TS 好像和 JS 不太一样 JS 写完之后直接就可以放到页面上,就可以用了,而我们的 ...