学习通自动看课(基于Python+Selenium)

前言 :上大学后每个学期都要刷网课(费时费力),为了节约时间便有了一系列用爬虫自动化完成的想法,科技改变生活(bushi)。但是上学期完成后并没有记录,而这学期再用发现X习T网站架构更新了,旧的便无法使用了(防了一手),借此机会,重写一下并做个记录


文章目录

  • 前言 :上大学后每个学期都要刷网课(费时费力),为了节约时间便有了一系列用爬虫自动化完成的想法,科技改变生活(bushi)。但是上学期完成后并没有记录,而这学期再用发现X习T网站架构更新了,旧的便无法使用了(防了一手),借此机会,重写一下并做个记录
  • 一、实现功能
  • 二、工具/库介绍
    • 1.Selenium介绍
    • 2.工具
    • 3.库
  • 三、具体实现
    • 1、引入库
    • 2、图形化界面设计(不过多解释)
    • 3、自动化过程
      • 1.无头浏览器实现(不显示浏览器)
      • 2.登录学习通
      • 3.找到对应课程(支持模糊查询,利用__contain__函数)
      • 4.*切换窗口句柄
      • 5.找到对应课时(和找课程类似,但只支持精确值)
      • 6.自动看视频
      • 7.完整代码请留言或私聊
  • 总结


一、实现功能

#本文介绍功能1的实现,功能2、3日后更新#

1、自动化看网课
2、自动化获取各章节题目,并转发至公众号获取答案
3、根据上面获取的答案自动答题(因作用不大,拟开发)


二、工具/库介绍

1.Selenium介绍

Selenium是最广泛使用的开源Web UI(用户界面)自动化测试套件之一。它最初由杰森·哈金斯(Jason Huggins)于2004年开发,作为Thought Works的内部工具。Selenium支持跨不同浏览器,平台和编程语言的自动化。
Selenium可以轻松部署在Windows,Linux,Solaris和Macintosh等平台上。此外,它支持IOS(IOS,Windows Mobile和Android)等移动应用程序的OS(操作系统)。
Selenium通过使用特定于每种语言的驱动程序支持各种编程语言。Selenium支持的语言包括C#,Java,Perl,PHP,Python和Ruby。目前,Selenium Web驱动程序最受Python和C#欢迎。 Selenium测试脚本可以使用任何支持的编程语言进行编码,并且可以直接在大多数现代Web浏览器中运行。 Selenium支持的浏览器包括Internet Explorer,Mozilla Firefox,Google Chrome和Safari。

更多介绍->Selenium简介
使用方法->官方文档

2.工具

语言:Python(不得不说小工具首选python)
浏览器:Microsoft Edge,其他浏览器也行
浏览器驱动器:自行下载对应浏览器及版本的驱动器
(参考下载地址:edge浏览器驱动 chrome浏览器驱动)

3.库

# 标*为必用的库,不标可视功能选用
from selenium import webdriver # *浏览器驱动
from time import sleep	# *等待特定时间
import time	# 用于控制台输出时间
from lxml import etree	# *解析页面数据
import tkinter	# 图形化界面
from tkinter import ttk # 下拉框
import tkinter.messagebox	# 消息框
from selenium.common.exceptions import NoSuchElementException	
# *用于特定异常处理

三、具体实现

1、引入库

代码如下:

from selenium import webdriver
from time import sleep
import time
from lxml import etree
import tkinter
from tkinter import ttk
import tkinter.messagebox
from selenium.common.exceptions import NoSuchElementException

2、图形化界面设计(不过多解释)

代码如下:

def __init__(self):
    self.window = tkinter.Tk()
    self.window.title("学习通自动刷课")
    # 设置窗口大小
    winWidth = 300
    winHeight = 200
    # 获取屏幕分辨率
    screenWidth = self.window.winfo_screenwidth()
    screenHeight = self.window.winfo_screenheight()
    # 居中显示
    x = int((screenWidth - winWidth) / 2)
    y = int((screenHeight - winHeight) / 2)
    self.window.geometry("%sx%s+%s+%s" % (winWidth, winHeight, x, y))
    tkinter.Label(self.window, text="用户名:").grid(row=1, column=1)
    self.username = tkinter.StringVar()
    tkinter.Entry(self.window, textvariable=self.username).grid(row=1,
                                                                column=2)
    tkinter.Label(self.window, text="密  码:").grid(row=2, column=1)
    self.password = tkinter.StringVar()
    tkinter.Entry(self.window, textvariable=self.password,
                  show="*").grid(row=2, column=2)
    tkinter.Label(self.window, text="科目名:").grid(row=3, column=1)
    self.subjestName = tkinter.StringVar()
    tkinter.Entry(self.window,
                  textvariable=self.subjestName).grid(row=3, column=2)
    tkinter.Label(self.window, text="起始课时序号:").grid(row=4, column=1)
    self.courseNum = tkinter.StringVar()
    tkinter.Entry(self.window, textvariable=self.courseNum).grid(row=4,
                                                                 column=2)
    tkinter.Label(self.window, text="是否显示浏览器:").grid(row=5, column=1)
    self.view = tkinter.IntVar()
    tkinter.Radiobutton(self.window, text="是", variable=self.view,
                        value=1).grid(row=5, column=2, sticky='W')
    tkinter.Radiobutton(self.window, text="否", variable=self.view,
                        value=2).grid(row=5, column=2, sticky='E')
    tkinter.Label(self.window, text="选择播放速度:").grid(row=6, column=1)
    self.values = [1, 1.25, 1.5, 2]
    self.speed = tkinter.StringVar()
    ttk.Combobox(self.window, values=self.values,
                 textvariable=self.speed).grid(row=6, column=2)
    tkinter.Label(self.window, text="是否静音播放:").grid(row=7, column=1)
    self.sound = tkinter.IntVar()
    tkinter.Radiobutton(self.window,
                        text="是",
                        variable=self.sound,
                        value=1).grid(row=7, column=2, sticky='W')
    tkinter.Radiobutton(self.window,
                        text="否",
                        variable=self.sound,
                        value=2).grid(row=7, column=2, sticky='E')
    tkinter.Button(self.window, text="开始刷课", command=self.shuake,
                   width=15).grid(row=8, column=2)
    self.window.protocol("WM_DELETE_WINDOW",
                         self.close)  # 点击右上角X号关闭界面时触发函数self.close
    self.window.mainloop()

def close(self):
    self.flag = False
    tkinter.messagebox.showinfo("close", "退出刷课?")
    self.window.destroy()  # 关闭页面时触发

实现样式:
学习通自动看课(基于Python+Selenium)_第1张图片


3、自动化过程

1.无头浏览器实现(不显示浏览器)

可以挂在后台完成刷课,但只适用于Microsoft Edge,未尝试谷歌浏览器的实现方法,可自行探索

代码如下:

if (self.view.get() == 2): # self.view由图形化界面传值
    EDGE = {
     
        "browserName": "MicrosoftEdge",
        "version": "",
        "platform": "WINDOWS",
        "ms:edgeOptions": {
     
            'extensions': [],
            'args': [
                '--headless',
                '--disable-gpu',
                '--remote-debugging-port=9222',
            ]
        }
    }
    bro = webdriver.Edge(executable_path='./msedgedriver', capabilities=EDGE)
else:
    bro = webdriver.Edge(executable_path='./msedgedriver')

executable_path 指的是驱动器存放地址,我存放在同一目录下,故为 './msedgedriver’

2.登录学习通

sleep(3):睡眠3秒,防止页面加载太慢而出错

代码如下:

bro.get('http://passport2.chaoxing.com/login?fid=&newversion=true&refer=http%3A%2F%2Fi.chaoxing.com')
bro.maximize_window()	#最大化窗口
bro.find_element_by_id('phone').send_keys(self.username.get())
bro.find_element_by_id('pwd').send_keys(self.password.get())
bro.find_element_by_id('loginBtn').click()
sleep(3)

3.找到对应课程(支持模糊查询,利用__contain__函数)

例如:查找“(第八期)马克思主义基本原理”只需要输入“马克思”

代码如下:

bro.switch_to.frame('frame_content')
page_text = bro.page_source
for i in range(1, 20):  # 遍历课程名
    i = str(i)
    tree = etree.HTML(page_text)
    name_list = tree.xpath(
        '/html/body/div[1]/div/div/div[2]/div/div[2]/div[2]/ul/li[' +
        i + ']/div[2]/h3/a/span/text()')
    sleep(1)
    name_string = name_list[0]
    if (name_string.__contains__(self.subjestName.get())):
        sbuject_xpath = '/html/body/div[1]/div/div/div[2]/div/div[2]/div[2]/ul/li[' + i + ']/div[2]/h3/a/span'
        bro.find_element_by_xpath(sbuject_xpath).click()
        break
    else:
        continue
sleep(3)

bro.switch_to.frame(‘frame_content’) 这句很重要,因为学习通的架构中用到了iframe,要获取框架里面的标签时必须切换frame,否则会报错“NoSuchElementException”

4.*切换窗口句柄

浏览器窗口句柄:当打开一个浏览器并打开了一个新标签页时,该标签页就会有一个句柄标识(句柄值)。直到你关闭了该标签页,该句柄标识(句柄值)才消失。
所以,当我们打开一个浏览器并打开了多个标签页时,关闭一个标签页不会影响其他标签页,就是因为每个标签页有了唯一的标识。

因此,当我们打开多个页面时需要切换窗口句柄

代码如下:

all_windows = bro.window_handles # 获取全部窗口句柄
bro.switch_to.window(all_windows[-1]) # 切换到最后打开的窗口

5.找到对应课时(和找课程类似,但只支持精确值)

代码如下:

for j in range(1, 20):  # 遍历章节
    for k in range(1, 15):
        try:
            j = str(j)
            k = str(k)
            tree = etree.HTML(page_text)
            course_num_list = tree.xpath(
                '/html/body/div[5]/div[1]/div[2]/div[3]/div[' + j +
                ']/div[' + k + ']/h3/a/span[1]/text()')
            course_num_string = course_num_list[0]
            if (course_num_string == self.courseNum.get()):
                course_xpath = '/html/body/div[5]/div[1]/div[2]/div[3]/div[' + j + ']/div[' + k + ']/h3/a/span[1]'
                bro.find_element_by_xpath(course_xpath).click()
                break
            else:
                continue
        except (IndexError):
            break
sleep(3)

6.自动看视频

bro.switch_to.default_content() 返回默认层,前面进入了iframe(“frame_content”)

代码如下:

bro.switch_to.default_content()	# 返回默认层
i = 1	
while True:
    bro.switch_to.frame('iframe') 	# 进入iframe
    try:
        num = bro.find_element_by_xpath(
            '//*[@id="ext-gen1043"]/div/div/p[1]/strong/span').text
    except (NoSuchElementException):
        num = "无法获取"		# 获取章节名称
    try:
        iframe2 = bro.find_element_by_xpath(
            '//iframe[contains(@class,"ans-insertvideo-online")][' +
            str(i) + ']')
    except (NoSuchElementException):
        if (num == '14.3实现共产主义是历史发展的必然趋势'):  # 结束章节
            bro.quit()
            break
        else:
            i = 1
            bro.switch_to.default_content()
            _item = bro.find_element_by_xpath(
                '//*[@id="mainid"]/div[1]/div[2]')
            bro.execute_script("arguments[0].click();", _item)
            sleep(2)
            continue
    bro.switch_to.frame(iframe2)
    i += 1
    try:
        _item = bro.find_element_by_xpath('//*[@id="video"]/button')
        bro.execute_script("arguments[0].click();", _item)
    except (NoSuchElementException):
        print(num, "未正常播放----")
        continue
    sleep(1)
    # 静音播放
    if (self.sound.get() == 1):
        bro.find_element_by_xpath(
            '//*[@id="video"]/div[5]/div[6]/button').click()
    # 调节倍速
    if (self.speed.get() == 1):
        speed = bro.find_element_by_xpath('//*[@id="video"]/div[5]/div[1]/button')
        for i in range(0, 3):
            speed.click()
            sleep(0.5)
    sleep(1)
    page_text = bro.page_source
    tree = etree.HTML(page_text)
    sleep(1)
    time_list = tree.xpath(
        '//*[@id="video"]/div[5]/div[4]/span[2]/text()')
    sleep(2)
    time_string = time_list[0]
    time_list = time_string.split(':')
    if time_list[-1][0] == "0":
        time_list[-1] = time_list[-1][1]
    vedio_time = eval(time_list[1]) + eval(time_list[0]) * 60
    print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
          end='----')
    print(num, "---------", "时长:", vedio_time, "秒------", "(",
          time_list[0], "分", time_list[1], "秒)")
    if (self.speed.get() == 1):
        sleep(vedio_time / 2 + 1)
    else:
        sleep(vedio_time + 2)
    bro.switch_to.default_content()

这里的重点是找到存放视频的那个iframe,通过解析源码找到了共同点,放视频的框架都有一个class属性含有ans-insertvideo-online,因此可以通过xpath解析'//iframe[contains(@class,"ans-insertvideo-online")][' + str(i) + ']',由于一个章节可能不止一个视频,因此需要继续寻找直到报错‘NoSuchElementException’,此时进入下一章节。
还有一个细节是学习通把播放按钮‘隐藏’起来了(去年还没有,今年网站更新了,防了一手),因此需要通过Javascript来点击。

_item = bro.find_element_by_xpath('//*[@id="mainid"]/div[1]/div[2]')
bro.execute_script("arguments[0].click();", _item)

其余代码都一个道理,便不再赘述。

7.完整代码请留言或私聊

总结

以上就是关于这个小工具的分享,代码中多处用到异常处理,这不是一个好的习惯,但在此只为实现功能,便也就无所谓了。由于这个小工具只是私用,因此代码比较简陋,仍有较多地方需要改进(懒得改了)。各位可以参考一下思路,斟酌采用,可以的话还请点个赞或留下您宝贵的意见!

你可能感兴趣的:(小工具,python,selenium,开发语言)