記一次用Python爬取超星學習通課後小測驗並寫入Word文檔(完整思路)

前言

  • 開門見山,直接切入正題,先看最終效果
    在這裏插入圖片描述在這裏插入圖片描述
  • 最終效果很理想,但這個過程是非常殘酷的。

爬取流程

敲代碼前的試探

  • 先實驗能否直接得到題目。
import requests
courseId = 208420018
mHeaders = {
    'User-Agent': r'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  r'Chrome/45.0.2454.85 Safari/537.36 115Browser/6.0.3'
}
url = "https://mooc1.chaoxing.com/nodedetailcontroller/visitnodedetail?courseId=208255733&knowledgeId=263215264"

mHtml = requests.get(url,headers = mHeaders).content.decode('utf-8')
if '人類進入21世紀' in mHtml:
    print('甩了')
else:
    print('不妥')
  • 輸出結果——不妥,說明並不能夠通過靜態頁面直接爬取題目。一般經驗肯定是認爲是接口傳輸的,於是開始找接口。。。。
    在這裏插入圖片描述
  • 但是,網絡傳輸的數據除了圖片就剩html,css和js了,根本找不到傳輸題目的接口。
  • 當我把指針選中所有題目時,我發現了一個以我的前端知識理解不了的東西。
    在這裏插入圖片描述
  • Html中又鑲嵌了一個Html,emmm…這就觸及到我的知識盲區了。而且,點擊下面的iframe標籤裏的src會跳轉到一個新的頁面,而這個頁面正是所有的測驗題目!!!
    在這裏插入圖片描述
  • 用postman簡單Get一下,發現能夠得到所有的題目!
    在這裏插入圖片描述
  • 接下來就改分析url了,總共有7個參數,其中下圖打√的是必須的,其他幾個可以不加。方法:用postman挨個請求就行。對於這三個參數,courseId是本來就知道的,那麼我們的目標就是找到workId和knowledgeid即可。
    在這裏插入圖片描述
  • 一番尋找後,發現knowledgeid通過靜態頁面就能獲取。
    在這裏插入圖片描述
  • 而workId在剛纔找到的iframe標籤的測驗地址裏,有人可能會問,那爲啥不直接用測驗地址呢,這我也試過,但是postman請求後的結果發現,得到的iframe標籤裏並沒有地址,但是有個data屬性,其中基本全是html轉義符。
    在這裏插入圖片描述
  • 用Python的html庫反轉義後可得到workId。(後來發現,反轉義與否不影響獲取,直接用正則表達式就可以得到)
    在這裏插入圖片描述

通過試探,總結一下編程思路。

  1. 首先得到課程ID(courseId),並組成url供後續訪問得到章節ID(knowledgeId);
  2. 其次,通過訪問主頁面得到每一個knowledgeId,將knowledgeId和courseId組成url供後續獲取workId;
  3. 然後,訪問用courseId和knowledgeId組成的url,得到章節的workId,並將courseId,knowledgeId和workId組成最終的小測驗地址;
  4. 對小測驗地址進行訪問,挨個爬取題目、選項並寫入Word文檔。

思路有了,上代碼!

  1. 課程ID直接給,組成url並訪問,得到頁面。
def getHtml():  #得到頁面
    mUrl = url.replace('{{courseId}}',courseId)
    response = requests.get(mUrl,headers=mHeaders)
    response.encoding = 'utf-8'
    mHtml = html.unescape(response.text)        #Html反轉義
    #response.encoding = 'utf-8'
    print(mHtml)
    return mHtml
  1. 得到每一個小節的knowledgeId並和courseId組成url,並對爬取的地址進行試探是否是章節的地址。
def getCourseUrlList(zhtml):  #得到可以用的每一個課時的Url
    divList = []
    re_rule = 'courseId=' + courseId + '&knowledgeId=(.*?)">'
    # for i in re.findall(re_rule,html):
    #     divList.append(i)
    divList = re.findall(re_rule,zhtml)
    urlList = []
    for i in divList:
        mUrl = 'https://mooc1.chaoxing.com/nodedetailcontroller/visitnodedetail?courseId='+courseId+'&knowledgeId='+i
        print(mUrl)
        try:
            response = requests.get(mUrl,headers=mHeaders,timeout=1)
            if response.status_code == 200:
                if courseId in response.text:
                     urlList.append(mUrl)
                     print('訪問成功')
                else:
                    print('非課程網頁')
        except Exception as e:
            print('訪問失敗')

    return urlList
  1. 得到workId並和courseId、knowledgeId組成小測驗的url。
def getZuoYeUrl(urlList):       #得到測驗的Url
    tUrlList = []
    for i in urlList:
        response = requests.get(i,headers=mHeaders).content.decode('utf-8')
        res = re.findall('workid":"(.*?)",', response)
        if len(res):
            for i in res:
                tUrl = "https://mooc1.chaoxing.com/api/selectWorkQuestion?workId="+i+"&ut=null&classId=0&courseId="+courseId
                tUrlList.append(tUrl)
    return tUrlList
  1. 訪問小測驗的地址並爬取標題、題幹、選項寫入Word文檔。
def writeDocx(urlList):          #從測驗Url中讀取題目並寫入Word文檔
    for url in urlList:
        mHtml = requests.get(url, headers=mHeaders).content.decode("utf-8")
        file = docx.Document()
        h3 = re.findall('<h3>(.*?)</h3>', mHtml)
        Title = ""
        for i in h3:
            Title = html.unescape(i)
            file.add_heading(Title)

        text = html.unescape(mHtml)
        mHtml = etree.HTML(text)  # 將html轉換爲xml
        timuList = mHtml.xpath('//div[@class="TiMu"]')  # 找到每一個題目及其所有選項
        for i in timuList:
            time.sleep(0.05)
            mStr = etree.tostring(i).decode('utf-8')  # 將xml樹結點讀出並轉換爲utf-8格式
            res = html.unescape(mStr)  # 解碼xml
            tType = re.findall('(【.*?】)', res)
            tRType = []
            for a in tType:
                p_rule = '<.*?>'	
                tRType.append(re.sub(p_rule,'',str(a)))	#刪除所有的html標籤
            tGan = re.findall('】<?p?>?(.*?)</p>', res)
            if not len(tGan):
                tGan = re.findall('<div class="Zy_TItle_p">(.*?)</div>', res)
            if not len(tGan):
                tGan = re.findall('】(.*?)</div>',res)
            tRGan = []
            for a in tGan:
                p_rule = '<.*?>'
                tRGan.append(re.sub(p_rule,'',str(a)))
            file.add_paragraph(tRType + tRGan)
            '''
                for j in tType:
                print(j)
                file.add_paragraph(j)
            for j in tGan:
                print(j)
                file.add_paragraph(j)
            '''
            XuanXiang = etree.HTML(res)
            tAny = XuanXiang.xpath('//li[@class="clearfix"]')
            for j in tAny:
                tStr = etree.tostring(j).decode('utf-8')
                tRes = html.unescape(tStr)
                tXuan = re.findall('<i class="fl">(.*?)</i>.*?none;"><?p?>?(.*?)<?/?p?>?</a></li>', tRes)
                tRXuan = []
                for a in tXuan:
                    tRRXuan = ""
                    for b in a:
                        p_rule = '<.*?>'
                        tRRXuan = tRRXuan + re.sub(p_rule, '', str(b))
                    tRXuan.append(tRRXuan)
                for k in tRXuan:
                    file.add_paragraph(k)

        file.save("D:\\"+Title+".docx")
        print(Title+'爬取完成')
        time.sleep(0.3)
  • 其餘代碼
  1. 全局變量及導庫(放在開頭)
# coding=utf-8
from lxml import etree
import docx
import requests
import re
import html
import time

url = "https://mooc1.chaoxing.com/course/{{courseId}}.html"
mHeaders = {
    'User-Agent': r'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  r'Chrome/45.0.2454.85 Safari/537.36 115Browser/6.0.3'
}
  1. 主函數
if __name__ == "__main__":
    courseId = "208255733"	#通過改變courseId可以實現爬取不同的課程,也可以課程號自加循環爬取,但課程量太大,就不一一編寫。
    zHtml = getHtml()
    canUseUrl = getCourseUrlList(zHtml)
    zuoYeUrl = getZuoYeUrl(canUseUrl)
    writeDocx(zuoYeUrl)

後記

  • 經實驗,本程序適用於大部分超星學習通課程,課程號可以在訪問該課程時的地址欄url中獲取。
  • 不要臉的推薦本人的其餘爬蟲文章:記一次用Python統計全國女性Size
  • 如複製出錯,可去博主Github取完整代碼點擊直達,記得點個star哦,感激不盡。
  • 如有大佬發現文章錯誤,或代碼錯誤,煩請指正,感激不盡。
  • 創作不易,轉載請註明出處,感謝。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章