疫情期间肯定有很多小伙伴需要上网课,但是有些网课我们感觉十分的鸡肋,自己不感兴趣,又必须要学
几点说明:
1.此程序只供学习交流,请勿用于商业用途
2.当前只支持“兴趣课”的刷课,其他类型的课程还不支持
3.程序尚不完善,但是原理相通,举一反三,欢迎交流
python3.7+requests库+selenium库+火狐浏览器
python3.7和requests库的安装不必赘述下面来讲一下selenium库,这也是我第一次用这个库,记录一下
因为目标网站是经过js渲染的,不使用selenium库很难抓取想要的数据,selenium库可以模拟浏览器进行操作,同时可以配合各大主流浏览器,十分好用
安装:
selenium可以配合PhantomJS一起使用,PhantomJS可以创建无界面浏览器,使用起来要比浏览器高效,但是这回还是先从简单的用起来吧,而且调试还是很需要界面的
对于不同的浏览器,需要安装不同的驱动:
我使用的是火狐浏览器,所以直接下载Firefox的驱动:
下载解压后,将geckodriver.exe添加到python的根目录下,其他浏览器也是一样,添加到python根目录下即可
现在环境已经准备好了,开始研究刷课的原理
根据Firefox抓包可以发现:
经过实验,我发现当每次用户离开当前界面(例如播放下一个视频、关闭网页)的时候,js都会向服务器发送一个名为save2CCoursProgressV2的post请求,这个包的参数是这样的:
而且参数里面的uuid直接标注了用户的id,所以发这个包的时候甚至不需要cookie来认证,直接post就好了
经过查找我发现,videoid并不是静态的存在网页中,js只会解析出当前播放的视频的videoid,这一点我会在后面的实现过程中详细说明
所以我们的工作还包括一个收集videoid和lessonid的过程
了解了实现的原理,就只差实现过程了
首先要初始化一个firefox浏览器:
browser=webdriver.Firefox()尝试进入智慧树的学生主页:
没有验证码,这一步就很简单,用selenium把用户名和密码填上,然后模拟浏览器去点击登陆按钮即可
可以看到输入用户名这里,有一个id属性,值是lUsername,所以可以直接通过id定位用户名输入框,同理密码也是一样:
usrname=browser.find_element_by_id('lUsername')#定位输入框password=browser.find_element_by_id('lPassword')usrname.send_keys('XXXXXX')#输入自己的用户名和密码password.send_keys('XXXXXX')登陆按钮:
可以看到按钮的class属性为wall-sub-btn所以也可以直接定位然后模拟点击:
signin=browser.find_element_by_class_name('wall-sub-btn').click()做到这一步就可以直接进入学生主页了,可以看到自己选修的课程:
下一步就是点开我要上的课:
可以看到class属性值为courseName直接模拟点击就可以了:
等待的方法有很多种,我直接用了最简单暴力的sleep(因为其他的方法不会...)
time.sleep(5)watch=browser.find_element_by_class_name('courseName').click()等待五秒钟后再点击就好了,不过要是实在网速不行,5秒也是有可能失败的....
之后就会出现一个弹窗:
这里必须要把它点掉,也是和之前模拟点击按钮一样的操作
就可以定位到当前视频的videoid了,但是这个路径用之前找id属性或者class属性的话不是很好找,所以使用css选择器的方法find_element_by_css_selector定位到这里,
然后再用get_attribute方法得到dataid的值,也就是videoid
复制css选择器:
可以得到:.video-box>div:nth-child(1)
然后用这个值去定位,然后get参数即可:
videoid=browser.find_element_by_css_selector(".video-box>div:nth-child(1)").get_attribute("dataid")现在有了videoid,那么lessonid在哪呢?
直接看右边的视频选择栏的代码,我们可以看到所有的lessonid都整整齐齐的写在这里:
所以我们只需要遍历每一个class="lessonItem"的模块,获取lessonid后点击这个视频,再获取这个视频的videoid,这样最关键的两个id我们就都可以获得了:
classlist=browser.find_elements_by_class_name('lessonItem')fornowinclasslist:classid=now.get_attribute('id')classtitle=now.find_element_by_class_name("lessonName").textnow.click()time.sleep(1)videoid=browser.find_element_by_css_selector(".video-box>div:nth-child(1)").get_attribute("dataid")这里需要注意的,是第一行和第四行的find方法有略微的不同,第一行element后面还有一个s,这样可以抓取到到一个列表,否则是选择第一个
然后就可以直接构造post请求发送save2CCoursProgressV2包了
uuid也是通过查找save2CCoursProgressV2包获取的,不够智能化自动化,还需要好好打磨
若是学生选修了多门课程,那么在学生界面选择课程的语句也需要稍稍更改了,改成find_elements而不是find_element
若是觉得效率不够,可以选择加多线程或者是PhantomJS来提高效率~~