0x00 寫在前面
疫情期間肯定有很多小伙伴需要上網課,但是有些網課我們感覺十分的雞肋,自己不感興趣,又必須要學
所以我寫了這個刷網課的小程序,一方面是鍛煉自己的爬蟲技術,另一方面也給同學們節約寶貴的時間
幾點說明:
1.此程序只供學習交流,請勿用於商業用途
2.當前只支持“興趣課”的刷課,其他類型的課程還不支持
3.程序尚不完善,但是原理相通,舉一反三,歡迎交流
0x01 環境准備
python3.7+requests庫+selenium庫+火狐瀏覽器
python3.7和requests庫的安裝不必贅述 下面來講一下selenium庫,這也是我第一次用這個庫,記錄一下
因為目標網站是經過js渲染的,不使用selenium庫很難抓取想要的數據,selenium庫可以模擬瀏覽器進行操作,同時可以配合各大主流瀏覽器,十分好用
安裝:
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的用法,也是受益匪淺