python 爬蟲抓取 MOOC 中國課程的討論區內容


一:selenium 庫

selenium 每次模擬瀏覽器打開頁面,xpath 匹配需要抓取的內容。可以,但是特別慢,相當慢。作為一個對技術有追求的爬蟲菜雞,狂補了一些爬蟲知識。甚至看了 scrapy 框架,驚呆了,真棒!

網上很多關於 selenium 庫的詳細介紹,這里略過此方法。

二: requests 庫

編寫一個爬蟲小腳本,requests 庫極為方便。接下來進入正題,如何抓取 MOOC 中國上課程的討論內容!

1. 分析網頁數據

打開你需要抓取數據的課程頁面,點擊討論區之后頁面加載討論的主題內容。F12 ---> Network ---> 刷新頁面。會看到里面有很多請求的內容,討論區內容肯定是數據包,類型的話 json 文件或 xhr 文件等。

 

 

 

2. 找到討論區內容的包

按名稱分析 xhr 文件,很快就會發現跟討論區相關的文件:PostBean.getAllPostsPagination.dwr,鼠標點擊文件看到該文件的詳細情況。

 

 

 點擊 Preview ,看到的是一大段 JS 代碼,是否是我們需要的內容呢,得進行驗證才可以得知。

3. 分析內容包 URL進行請求

閱讀里面的內容,發現 .title .nickname 等字段信息,但是都是 Unicode 編碼的。試着把 .title="" 的內容復制出來直接粘貼在 python 解釋器里面就會出現該編碼的中文字。

 

 對比討論區主題,發現是我們需要抓取的內容,

但是當我們復制 Request URL 到瀏覽器中進行訪問時,是得不到需要的內容的,怎么辦呢?

4. 根據響應去匹配需要的內容進行保存

繼續分析請求頭部的信息,最下面是 Request Payload , 存放了一些看不懂的數據內容,它的作用是瀏覽器發送請求時發送到服務器端的數據信息,和 Data Form 有些區別。但我們擼代碼的時候一概作為附帶的數據包發送給服務器就行了。其中幾個關鍵的字段在代碼里都會有注釋信息理解,包括頁碼,每頁數據的大小等。

5. 代碼實現

  1 import requests
  2 import json
  3 import time
  4 import re
  5 import random
  6 
  7 def get_title_reply(uid, fi, http):
  8     url = 'https://www.icourse163.org/dwr/call/plaincall/PostBean.getPaginationReplys.dwr'
  9     headers = {
 10         'accept': '*/*',
 11         'accept-encoding': 'gzip, deflate, br',
 12         'accept-language': 'zh-CN,zh;q=0.9',
 13         'content-length': '249',
 14         'content-type': 'text/plain',
 15         'cookie': '',
 16         'origin': 'https://www.icourse163.org',
 17         'referer': 'https://www.icourse163.org/learn/WHUT-1002576003?tid=1206076258',
 18         'sec-fetch-mode': 'cors',
 19         'sec-fetch-site': 'same-origin',
 20         'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
 21     }
 22     data = {
 23         'httpSessionId': '611437146dd0453d8a7093bfe8f44f17',
 24         'scriptSessionId': '${scriptSessionId}190',
 25         'c0-scriptName': 'PostBean',
 26         'c0-methodName': 'getPaginationReplys',
 27         'c0-id': 0,
 28         'callCount': 1,
 29         # 根據主題樓主的 id 檢索回復內容
 30         'c0-param0': 'number:' + str(uid),
 31         'c0-param1': 'string:2',
 32         'c0-param2': 'number:1',
 33         'batchId': round(time.time() * 1000),
 34     }
 35     res = requests.post(url, data=data, headers=headers, proxies=http)
 36     # js 代碼末尾給出回復總數,當前頁碼等信息。
 37     totle_count = int(re.findall("totalCount:(.*?)}", res.text)[0])
 38     try:
 39         if totle_count:
 40             begin_reply = int(re.findall("list:(.*?),", res.text)[0][1:]) + 1
 41             for i in range(begin_reply, begin_reply + totle_count):
 42                 content_re ='s{}.content="(.*?)";'.format(i)
 43                 content = re.findall(content_re, res.text)[0]
 44                 # print(content.encode().decode('unicode-escape'))
 45                 fi.write('\t' + content.encode().decode('unicode-escape') + '\n')
 46                 # time.sleep(1)
 47     except Exception:
 48         print('回復內容寫入錯誤!')
 49 
 50 
 51 
 52 def get_response(course_name, url, page_index):
 53 
 54     headers = {
 55         'accept': '*/*',
 56         'accept-encoding': 'gzip, deflate, br',
 57         'accept-language': 'zh-CN,zh;q=0.9',
 58         'content-length': '333',
 59         'content-type': 'text/plain',
 60         'cookie': '',
 61         'origin': 'https://www.icourse163.org',
 62         'referer': 'https://www.icourse163.org/learn/WHUT-1002576003?tid=1206076258',
 63         'sec-fetch-mode': 'cors',
 64         'sec-fetch-site': 'same-origin',
 65         'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
 66 
 67     }
 68     data = {
 69         'httpSessionId': '611437146dd0453d8a7093bfe8f44f17',
 70         'scriptSessionId': '${scriptSessionId}190',
 71         'c0-scriptName': 'PostBean',
 72         'c0-methodName': 'getAllPostsPagination',
 73         'c0-id': 0,
 74         'callCount': 1,
 75         # 課程 id
 76         'c0-param0': 'number:1206076258',
 77         'c0-param1': 'string:',
 78         'c0-param2': 'number:1',
 79         # 當前頁碼
 80         'c0-param3': 'string:' + str(page_index),
 81         # 頁碼內容量
 82         'c0-param4': 'number:20',
 83         'c0-param5': 'boolean:false',
 84         'c0-param6': 'null:null',
 85         # 毫秒級時間戳
 86         'batchId': round(time.time() * 1000),
 87     }
 88     # 代理 IP
 89     proxy = [
 90         {
 91             'http': 'http://119.179.132.94:8060',
 92             'https': 'https://221.178.232.130:8080',
 93         },
 94         {
 95             'http': 'http://111.29.3.220:8080',
 96             'https': 'https://47.110.130.152:8080',
 97         },
 98         {
 99             'http': 'http://111.29.3.185:8080',
100             'https': 'https://47.110.130.152:8080',
101         },
102         {
103             'http': 'http://111.29.3.193:8080',
104             'https': 'https://47.110.130.152:8080',
105         },
106         {
107             'http': 'http://39.137.69.10:8080',
108             'https': 'https://47.110.130.152:8080',
109         },
110     ]
111     http = random.choice(proxy)
112     is_end = False
113     try:
114         res = requests.post(url, data=data, headers=headers, proxies=http)
115         # 評論從 S** 開始,js 代碼末尾信息分析
116         response_result = re.findall("results:(.*?)}", res.text)[0]
117     except Exception:
118         print('開頭就錯,干啥!')
119     if response_result == 'null':
120         is_end = True
121     else:
122         try:
123             begin_title = int(response_result[1:]) + 1
124             with open(course_name+'.txt', 'a', encoding='utf-8') as fi:
125                 for i in range(begin_title, begin_title + 21):
126                     user_id_re = 's{}.id=([0-9]*?);'.format(str(i))
127                     title_re = 's{}.title="(.*?)";'.format(str(i))
128                     title_introduction_re = 's{}.shortIntroduction="(.*?)"'.format(str(i))
129                     title = re.findall(title_re, res.text)
130                     if len(title):
131                         user_id = re.findall(user_id_re, res.text)
132                         title_introduction = re.findall(title_introduction_re, res.text)
133                         # print(f'user_id={user_id[0]},title={(title[0]).encode().decode("unicode-escape")}')
134                         fi.write((title[0]).encode().decode("unicode-escape") + '\n')
135                         # 主題可能未進行描述
136                         if len(title_introduction):
137                             # print(title_introduction[0].encode().decode("unicode-escape"))
138                             fi.write('\t' + (title_introduction[0]).encode().decode("unicode-escape") + '\n')
139                             get_title_reply(user_id[0], fi, random.choice(proxy))
140         except Exception:
141             print('主題寫入錯誤!')
142     return is_end
143 
144 def get_pages_comments():
145     url = 'https://www.icourse163.org/dwr/call/plaincall/PostBean.getAllPostsPagination.dwr'
146     page_index = 1
147     course_name = "lisanjiegou"
148     while(True):
149         # time.sleep(1)
150         is_end = get_response(course_name, url, page_index)
151         if is_end:
152             break
153         else:
154             print('第{}頁寫入完成!'.format(page_index))
155             page_index += 1
156 
157 if __name__ == '__main__':
158     start_time = time.time()
159     print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time)))
160     get_pages_comments()
161     end_time = time.time()
162     print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time)))
163     print('用時{}秒!'.format(end_time - start_time))
requests 版
  1 from selenium import webdriver
  2 from bs4 import BeautifulSoup
  3 import time
  4 from selenium.webdriver.chrome.options import Options
  5 import requests
  6 
  7 def get_connect():
  8     chrome_driver = 'C:/Program Files (x86)/Google/Chrome/Application/chromedriver.exe'
  9     browser = webdriver.Chrome(executable_path=chrome_driver)
 10     url_head = 'https://www.icourse163.org/learn/WHUT-1002576003#/learn/forumindex'
 11     # 加載網頁
 12     browser.get(url_head)
 13     # 獲取課程標題
 14     title_link = browser.find_element_by_class_name('courseTxt')
 15     # 模擬點擊進入詳情頁
 16     title_link.click()
 17     content = browser.page_source
 18     soup = BeautifulSoup(content, 'lxml')
 19     print(soup.text)
 20 def get_connect_slow():
 21     chrome_driver = 'C:/Program Files (x86)/Google/Chrome/Application/chromedriver.exe'
 22     browser = webdriver.Chrome(executable_path=chrome_driver)
 23     url_head = 'https://www.icourse163.org/learn/WHUT-1002576003#/learn/forumindex'
 24     # 加載網頁
 25     browser.get(url_head)
 26     pages = browser.find_elements_by_class_name('zpgi')
 27     totle_page = int(pages[-1].text) + 1
 28     browser.close()
 29     with open('comments.txt', 'w', encoding='utf-8') as fi:
 30         for i in range(1, 2):
 31             browser = webdriver.Chrome(executable_path=chrome_driver)
 32             url = url_head + '?t=0&p=' + str(i)
 33             browser.get(url)
 34             # 多條內容
 35             comments = browser.find_elements_by_class_name('j-link')
 36             for comment in comments:
 37                 fi.write(comment.text + '\n')
 38             print('第{}頁評論寫入成功!'.format(i))
 39             browser.close()
 40 def get_connect_slow_1(course_url, course_name):
 41     chrome_driver = 'C:/Program Files (x86)/Google/Chrome/Application/chromedriver.exe'
 42     browser_1 = webdriver.Chrome(executable_path=chrome_driver)
 43     url_head = course_url
 44     # 加載網頁
 45     browser_1.implicitly_wait(3)
 46     browser_1.get(url_head)
 47     pages = browser_1.find_elements_by_class_name('zpgi')
 48     totle_page = 0
 49     if pages:
 50         for pg in range(len(pages)-1, 0, -1):
 51             if pages[pg].text.isdigit():
 52                 totle_page = int(pages[pg].text) + 1
 53                 break
 54 
 55     print('評論主題共{}頁!'.format(totle_page))
 56     with open(course_name + '.txt', 'w', encoding='utf-8') as fi:
 57         for i in range(1, totle_page):
 58             try:
 59                 browser = webdriver.Chrome(executable_path=chrome_driver)
 60                 browser.implicitly_wait(3)
 61                 url = url_head + str(i)
 62                 browser.get(url)
 63                 content = browser.page_source
 64                 soup = BeautifulSoup(content, 'lxml')
 65                 # course_title = soup.find('h4', class_='courseTxt')
 66                 # fi.write(course_title.text + '\n')
 67                 comment_lists = soup.find_all('li', class_='u-forumli')
 68                 for comment in comment_lists:
 69                     reply_num = comment.find('p', class_='reply')
 70                     reply_num = int(reply_num.text[3:])
 71                     if reply_num > 0:
 72                         try:
 73                             comment_detail = comment.find('a', class_='j-link')
 74                             fi.write(comment_detail.text + '\n')
 75                             a_link = comment_detail.get('href')
 76                             reply_link = url.split('#')[0] + a_link
 77                             browser_reply = webdriver.Chrome(executable_path=chrome_driver)
 78                             browser_reply.implicitly_wait(3) #隱式等待 3 秒
 79                             browser_reply.get(reply_link)
 80                             test_ = browser_reply.find_element_by_class_name('m-detailInfoItem')
 81                             reply_soup = BeautifulSoup(browser_reply.page_source, 'lxml')
 82                             # 樓主對主題的描述
 83                             own_reply = reply_soup.find('div', class_='j-post')
 84                             own_reply = own_reply.find('div', class_='j-content')
 85                             # 有樓主對主題省去描述
 86                             if own_reply.text:
 87                                 fi.write('\t' + own_reply.text + '\n')
 88 
 89                             # 別人對該主題的評論回復
 90                             reply_list = reply_soup.find_all('div', class_='m-detailInfoItem')
 91                             for reply_item in reply_list:
 92                                 write_text = reply_item.find('div', class_='j-content')
 93                                 fi.write('\t' + write_text.text + '\n')
 94                             browser_reply.close()
 95                         except Exception :
 96                             print('評論回復抓取失敗!')
 97                     else:
 98                         fi.write(comment.find('a', class_='j-link').text + '\n')
 99                 print('第{}頁評論寫入成功!'.format(i))
100             except Exception:
101                 print('第{}頁評論抓取失敗!'.format(i))
102 
103 def run():
104 
105     print('https://www.icourse163.org/learn/WHUT-1002576003?tid=1206076258#/learn/forumindex?t=0&p=')
106     course_url = input('輸入課程地址,輸入網址后空格再回車,如上:')
107     course_url = course_url.split(' ')[0]
108     course_name = input('輸入課程名:')
109     start_time = time.time()
110     get_connect_slow_1(course_url, course_name)
111     end_time = time.time()
112     print('共用時{}秒!'.format(end_time - start_time))
113 
114 
115 
116 if __name__ == '__main__':
117     run()
118     # 76 頁評論
119     # 75-150頁
120     # 共用時13684.964568138123秒!
selenium 版

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM