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