0x00 写在前面
疫情期间确定有不少小伙伴须要上网课,可是有些网课咱们感受十分的鸡肋,本身不感兴趣,又必需要学css
因此我写了这个刷网课的小程序,一方面是锻炼本身的爬虫技术,另外一方面也给同窗们节约宝贵的时间html
几点说明:python
1.此程序只供学习交流,请勿用于商业用途git
2.当前只支持“兴趣课”的刷课,其余类型的课程还不支持github
3.程序尚不完善,可是原理相通,触类旁通,欢迎交流web
0x01 环境准备
python3.7+requests库+selenium库+火狐浏览器chrome
python3.7和requests库的安装没必要赘述 下面来说一下selenium库,这也是我第一次用这个库,记录一下小程序
由于目标网站是通过js渲染的,不使用selenium库很难抓取想要的数据,selenium库能够模拟浏览器进行操做,同时能够配合各大主流浏览器,十分好用api
安装:浏览器
pip install selenium
官网:http://www.seleniumhq.org
中文文档:http://selenium-python-zh.readthedocs.io
selenium能够配合PhantomJS一块儿使用,PhantomJS能够建立无界面浏览器,使用起来要比浏览器高效,可是这回仍是先从简单的用起来吧,并且调试仍是很须要界面的
对于不一样的浏览器,须要安装不一样的驱动:
Chrome的驱动chromedriver 下载地址:http://chromedriver.storage.googleapis.com/index.html
Firefox的驱动geckodriver 下载地址:https://github.com/mozilla/geckodriver/releases/
IE的驱动IEdriver 下载地址:https://www.nuget.org/packages/Selenium.WebDriver.IEDriver/
我使用的是火狐浏览器,因此直接下载Firefox的驱动:
下载解压后,将geckodriver.exe添加到python的根目录下,其余浏览器也是同样,添加到python根目录下便可
0x02 核心原理
如今环境已经准备好了,开始研究刷课的原理
根据Firefox抓包能够发现:
通过实验,我发现当每次用户离开当前界面(例如播放下一个视频、关闭网页)的时候,js都会向服务器发送一个名为save2CCoursProgressV2的post请求,这个包的参数是这样的:
这些参数直接看名字就能知道是什么含义,最重要的参数就是learnTime和totalTime,应该是你观看视频的时间和待在当前界面的时间
因此只要咱们构造这个save2CCoursProgressV2包,而后把相关的参数都填好,把learnTime和totalTime设置为一个很大的数,这样服务器就会认为你学习了很长很长时间
并且参数里面的uuid直接标注了用户的id,因此发这个包的时候甚至不须要cookie来认证,直接post就行了
可是须要注意的是,咱们从哪里获取videoid和lessonid呢?若是id不对的话也是没法记录时间的
通过查找我发现,videoid并非静态的存在网页中,js只会解析出当前播放的视频的videoid,这一点我会在后面的实现过程当中详细说明
因此咱们的工做还包括一个收集videoid和lessonid的过程
这就是本程序的核心原理,直接构造统计视频观看时长的数据包(其中相关参数须要收集),发送到服务器,从而避免浪费大量的时间来观看视频
0x03 实现过程
了解了实现的原理,就只差实现过程了
首先要初始化一个firefox浏览器:
browser = webdriver. Firefox()
尝试进入智慧树的学生主页:
browser.get('https://onlineh5.zhihuishu.com/onlineWeb.html#/studentIndex')
发现要模拟登录,不过幸运的是,智慧树登录不须要验证码,能够直接用selenium进行登录,不然的话就须要拿到cookie再发送请求了:
没有验证码,这一步就很简单,用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 直接模拟点击就能够了:
watch=browser.find_element_by_class_name('courseName').click()
可是须要注意的是,在这个语句以前,须要加上一个等待时间,必须等到网页加载完成了以后才能点击,不然有可能根本就找不到这个按钮
等待的方法有不少种,我直接用了最简单暴力的sleep(由于其余的方法不会...)
time.sleep(5) watch=browser.find_element_by_class_name('courseName').click()
等待五秒钟后再点击就行了,不过要是实在网速不行,5秒也是有可能失败的....
以后就会出现一个弹窗:
这里必需要把它点掉,也是和以前模拟点击按钮同样的操做
signin=browser.find_element_by_class_name('know').click()
点击完以后,就能够搜集咱们想要的东西了(这里最好也加个sleep,给浏览器一点反应的时间)
首先是videoid,videoid怎么找呢?直接ctrl+F:
就能够定位到当前视频的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') for now in classlist: classid=now.get_attribute('id') classtitle=now.find_element_by_class_name("lessonName").text now.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包了
ps:save2CCoursProgressV2包的最后一个参数是毫秒级时间戳,可是time方法得到的是秒级的时间戳,须要转化一下:
import time t = time.time() #秒级时间戳 T=int(round(t * 1000)) #毫秒级时间戳
post请求(注意这里的url和以前的不同,能够经过分析save2CCoursProgressV2包来得到):
post_url='https://b2cpush.zhihuishu.com/b2cpush/courseDetail/save2CCoursProgressV2' post_data = { 'courseId': '2068219', #courseid能够直接在当前url里面找到 'videoId':videoid, 'lessonId':classid, 'learnTime':'1000', 'chapterName':classtitle, 'sourceType':'3', 'totalTime':'1000', 'studyMode':'1', 'uuid':'XXXXX', #用户id,但不是用户名 'dateFormate':int(round(t * 1000)) #毫秒级时间戳 } r=requests.post(post_url,post_data) print(r.status_code) #输出状态码
这样就大功告成了!
0x04 最终代码
from selenium import webdriver import time import requests post_url='https://b2cpush.zhihuishu.com/b2cpush/courseDetail/save2CCoursProgressV2' browser = webdriver. Firefox() browser.get('https://onlineh5.zhihuishu.com/onlineWeb.html#/studentIndex') usrname=browser.find_element_by_id('lUsername') password=browser.find_element_by_id('lPassword') usrname.send_keys('xxxxxx') #用户名和密码 password.send_keys('xxxxxx') signin=browser.find_element_by_class_name('wall-sub-btn').click() time.sleep(5) #停一下 等页面加载完毕 watch=browser.find_element_by_class_name('courseName').click() time.sleep(2) signin=browser.find_element_by_class_name('know').click() time.sleep(2) videoid=browser.find_element_by_css_selector(".video-box > div:nth-child(1)").get_attribute("dataid") classlist=browser.find_elements_by_class_name('lessonItem') for now in classlist: classid=now.get_attribute('id') classtitle=now.find_element_by_class_name("lessonName").text now.click() time.sleep(1) videoid=browser.find_element_by_css_selector(".video-box > div:nth-child(1)").get_attribute("dataid") t = time.time() post_data = { 'courseId':'2068219', #能够根据url得到 'videoId':videoid, 'lessonId':classid, 'learnTime':'1000', #设置为足够大 'chapterName':classtitle, #视频标题 'sourceType':'3', 'totalTime':'1000', 'studyMode':'1', 'uuid':'xxxx', #uuid能够经过找其余save2CCoursProgressV2包来得到 'dateFormate':int(round(t * 1000)) #毫秒级时间戳 } r=requests.post(post_url,post_data) print(r.status_code)
0x05 总结
这个程序写的仍是比较简陋的,只支持了“兴趣课”,其余的课程因为网页格式不同,应该是不适用的,并且courseId还须要手动看url来得到:
uuid也是经过查找save2CCoursProgressV2包获取的,不够智能化自动化,还须要好好打磨
如果学生选修了多门课程,那么在学生界面选择课程的语句也须要稍稍更改了,改为find_elements而不是find_element
不过这都是细节问题了,核心的登陆、收集id信息、发送统计时长都作出来了,也亲测有效:
如果以为效率不够,能够选择加多线程或者是PhantomJS来提升效率~~
此次学习到了不少selenium的用法,也是受益不浅
原文出处:https://www.cnblogs.com/dyhaohaoxuexi/p/12503153.html