使用爬蟲爬取超星學習通的作業時間並且通過郵件提醒!
簡介
因為本人十分愛忘記做作業,因此,想通過爬蟲,爬取超星的學習通作業時間,並且進行定時提醒。
環境
- 阿里雲輕量級伺服器
- Centos7
- Anaconda3
先看效果展示
爬取過程
傳送郵件
過程
需要了解的知識
- http的訪問機制
- cookies,session是用來幹嘛的
- 驗證碼登入的流程
- 頁面的機制
- python幾個包的使用
- requetst(網頁請求包)
- lxml,etree(網頁介面處理包)
- email,smtplib(郵件處理包)
- muggle-ocr(驗證碼識別)
- datetime和time(時間包)
- Centos下的anaconda的使用
- stmp郵件協議講解
- Centos如何進行定時任務
- Centos關於郵件傳送的埠
開始
(對於部分模組,有些部落格寫的非常好,我就不進行詳述,但是會提供連結)
1.http的訪問機制
一文搞懂HTTP協議(帶圖文)
2.cookies,session是用來幹嘛的
cookie和session的區別
session和cookies的區別
3. 驗證碼登入的流程
驗證碼的原理及作用
簡單來說(以超星學習為例子):
- 每次進入登入頁面,它會先請求一個驗證碼的網址(不用管它code?***是什麼。,它只是一個通過js的datetime函式得到的一個時間戳(不明白的同學可以去搜一搜)。不要太在意這部分,一開始,我就走入歧途,想通過這個來獲取驗證碼,其實思路就錯了,引以為戒)
進入該連結之後,得到一張圖片
此時,表面上得到的是一張圖片,實際上,在伺服器端,它還生成了與之匹配的cookies資訊,並且返回給了登入頁面。而之後我們客戶端就必須攜帶這個cookies以及賬號密碼資訊進行訪問。
4. 頁面的機制
使用的chrome瀏覽器,開啟F12就可以瞭解整個頁面的轉換過程。主要分為:
- html靜態網頁(容易獲取)
- js後臺操作函式(通過相關函式來進行動態載入顯示頁)
就比如這裡的驗證碼圖片,並不是靜態載入的,因此直接獲取到的html中並沒有該圖片的連結,反而是通過一個函式進行動態載入。
開啟F12後主要頁面如下:
在最後的圖中的formdata就是本次登入的資訊(賬號,密碼(base64加密),驗證碼等)
程式碼
#coding=UTF-8 #File name :爬蟲超星 #Author:龍文漢 #Data:2020.10.16 #Description:使用爬蟲爬取超星的作業詳情,獲取作業的截至時間 import time import json import requests from lxml import etree import matplotlib.pyplot as plt import matplotlib.image as mpimg import os import base64 import datetime import smtplib from email.header import Header from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart import threading import sys import muggle_ocr class PaChaongxin(): #main_function def __init__(self,username,password,emai): self.username = username self.password = password self.to = emai self.code = 0 self.code_status = None #驗證碼正確還是失敗 self.user_pas_status = None #賬戶名和密碼 self.sender_mail = 'xxxx@xxx'#傳送者郵件 self.sender_pass = 'xxxxxxx' # 郵箱的stm密碼 self.session = requests.session() self.header = { 'User-Agent': 'xxxxxxxx'#自己的user_agent } def User_Pas(self): #輸入賬號,密碼 # self.username = input("請輸入學號:") # self.password = input("請輸入密碼:") # self.to = input("請輸入郵箱:") #self.username = xxxxxxx #self.password = 'xxxxx' #self.to = 'xxxxxx' return def Get_code(self): #獲取驗證碼,以及攜帶的cookies code_url = 'https://passport2.chaoxing.com/num/code'#超星驗證碼網址 path_path = 'vari_code.png' code_response = self.session.get(code_url) #儲存驗證碼 img = open(path_path,'wb') img.write(code_response.content) img.close() #顯示驗證碼,並且初始化,人為輸入,不適用識別程式 # img_open = Image.open('vari_code.png') # img = mpimg.imread('vari_code.png',0) # plt.imshow(img) # 顯示圖片 # plt.axis('off') # 不顯示座標軸 # plt.show() # self.code = input("請輸入驗證碼:") print("befor:",self.code) self.code = self.Code_Verifed() print("after:", self.code) os.remove(path_path) def Load_Page(self): #使用session進入登入介面驗證 self.password = base64.b64encode(self.password.encode("utf-8")) # 被編碼的引數必須是二進位制資料 param = { 'fid': 'xxxx', 'uname': self.username, 'numcode': self.code, 'password': self.password, 'refer': 'http%3A%2F%2Fi.chaoxing.com', 't': 'true' } header = { 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,und;q=0.7', 'Connection': 'keep-alive', 'Content-Length': '109', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Host': 'passport2.chaoxing.com', 'Origin': 'https://passport2.chaoxing.com', 'Referer': 'https://passport2.chaoxing.com/login?loginType=3&newversion=true&fid=-1&refer=http%3A%2F%2Fi.chaoxing.com', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin', 'User-Agent': 'xxxxxxxx',#注意自己更改 'X-Requested-With': 'XMLHttpRequest', } load_url = 'https://passport2.chaoxing.com/unitlogin?' load_response = self.session.post(load_url,headers=header,data=param) load_response_msg = json.loads(load_response.text) print(load_response_msg,load_response_msg.keys()) if 'mes' in load_response_msg.keys(): if load_response_msg['mes'] == '驗證碼錯誤': print('驗證碼錯誤') self.code_status = True while(self.code_status): self.Get_code() param['numcode'] = self.code load_response = self.session.post(load_url, headers=header, data=param) load_response_msg = json.loads(load_response.text) if 'mes' not in load_response_msg.keys() or load_response_msg['mes'] != '驗證碼錯誤': self.code_status = False if 'mes' in load_response_msg.keys() and load_response_msg['mes'] == '使用者名稱或密碼錯誤': print('使用者名稱或密碼錯誤') self.user_pas_status = True while(self.user_pas_status): self.User_Pas() self.Get_code() param['numcode'] = self.code param['uname'] = self.username param['password'] = self.password load_response = self.session.post(load_url, headers=header, data=param) load_response_msg = json.loads(load_response.text) if 'mes' not in load_response_msg.keys() and load_response_msg['mes'] != '使用者名稱或密碼錯誤': self.code_status = False #上面返回的應該是登入成功,接下來,帶著新的cookies訪問主頁 self_page_url = 'http://i.mooc.chaoxing.com' self_page_response = self.session.get(url=self_page_url,headers = self.header) #個人空間的主頁面,這裡直接提取課程的部分,要在左側的按鈕裡找到相對應的連線, #該課程的頁面直接鑲嵌在本頁面,所以帶著session直接訪問也可以 # self_page_html = etree.HTML(self_page.text) # return self_page_html def Get_Class_View(self): #進入所有課程的介面 class_view_url = 'http://mooc1-2.chaoxing.com/visit/courses' class_view = self.session.get(class_view_url,headers = self.header) class_view_html = etree.HTML(class_view.text) #直接返回整個頁面的編碼,方便後續的查詢 return class_view_html def Go_to_work(self,Singel_Class_Url_after): #通過外面直接傳來的課程網址,直接跳轉 #進入單個課程的介面的作業模組 Singel_Class_Url = 'https://mooc1-2.chaoxing.com'+Singel_Class_Url_after single_class_response = self.session.get(Singel_Class_Url,headers=self.header) single_class_page = etree.HTML(single_class_response.text) url_after = single_class_page.xpath("/html/body/div[4]/div/div/div[2]/ul/li[6]/a/@data") if len(url_after) == 0: open_zuoye_url = 'https://mooc1-2.chaoxing.com' + \ single_class_page.xpath("/html/body/div[2]/div/div/div[2]/ul/li[6]/a/@data")[0] else: open_zuoye_url = 'https://mooc1-2.chaoxing.com' + \ single_class_page.xpath("/html/body/div[4]/div/div/div[2]/ul/li[6]/a/@data")[0] work_xml_response = self.session.get(open_zuoye_url,headers=self.header) work_xml = etree.HTML(work_xml_response.text) #這裡的open_zuoye直接轉到了作業的介面 #呼叫每個作業的函式,方便多執行緒 single_class_text = self.Get_work_time(work_xml) return single_class_text def Get_work_time(self,work_xml): # 以上就是關於頁面跳轉的函式,接下來就是作業的擷取 #作業的資訊提取 work_num = len(work_xml.xpath('//*[@id="RightCon"]/div/div/div[2]/ul/li')) class_name = work_xml.xpath('/html/body/div[2]/div/h1/span[1]/@title')[0] #郵件的內容:[[課程],[作業名稱,截至時間,剩餘時間]*n]的列表 #無作業的課程直接返回 if work_num == 0: return None work_text_info = []#有作業的課程 work_text_info.append(class_name)#新增課程名 #對每個專案進行整理 for i in range(1,work_num+1): work_name = work_xml.xpath('//*[@id="RightCon"]/div/div/div[2]/ul/li['+str(i)+']/div[1]/p/a/text()')[0].strip() work_status = work_xml.xpath('/html/body/div[3]/div[1]/div/div/div/div[2]/ul/li['+str(i)+']/div[1]/span[3]/strong/text()')[0].strip() work_end_time = work_xml.xpath('//*[@id="RightCon"]/div/div/div[2]/ul/li['+str(i)+']/div[1]/span[2]/text()') # print(work_end_time) if len(work_end_time) == 0:#無截至日期的課程 break else: work_end_time = work_end_time[0] print(class_name,work_name,work_end_time) if work_status != '已完成' and work_status != "待批閱":#存在未完成的課程 work_text_info.append([]) #增加作業名 work_text_info[-1].append(work_name) #使用datatime,計算時間差 current_time = time.strftime("%Y-%m-%d %H:%M", time.localtime()) #轉化位datatime的格式 current_time_tran = datetime.datetime.strptime(current_time ,"%Y-%m-%d %H:%M") work_end_time_tran = datetime.datetime.strptime(work_end_time ,"%Y-%m-%d %H:%M") time_mul = work_end_time_tran - current_time_tran time_mul_day = time_mul.days if time_mul_day <= 1: time_m, time_s = divmod(time_mul.seconds, 60) time_h, time_m = divmod(time_m, 60) lea_time = '0天:'+str(time_h)+'小時:'+str(time_m)+'分鐘' #增加剩餘時間 work_text_info[-1].append(lea_time) else: lea_time = str(time_mul_day)+'天' work_text_info[-1].append(lea_time) print(class_name,"已經結束") #返回單課程的作業資訊 return work_text_info def Code_Verifed(self): sdk = muggle_ocr.SDK(model_type=muggle_ocr.ModelType.Captcha) with open(r'vari_code.png', 'rb') as f: captcha_bytes = f.read() code = sdk.predict(image_bytes=captcha_bytes) return code def send_email_by_qq(self,text): # 利用郵箱發郵件提醒 # 設定總的郵件體物件,物件型別為mixed msg_root = MIMEMultipart('mixed') # 郵件新增的頭尾資訊等 msg_root['From'] = 'xxxx@xxxxx<xxxx@xxxx>' msg_root['To'] = self.to # 郵件的主題,顯示在接收郵件的預覽頁面 subject = '快到作業截止時間了!' msg_root['subject'] = Header(subject, 'utf-8') # 構造文字內容 text_inf = "未完成作業總覽:\n" for i in range(0,len(text)): text_inf += text[i][0]+"\n"#新增標題 for x in range(1,len(text[i])): text_inf += '\t\t' text_inf += str(text[i][x]) text_inf += "\n\n" text_sub = MIMEText(text_inf, 'plain', 'utf-8') print(text_inf) msg_root.attach(text_sub) # # 構造超文字 # url = "https://blog.csdn.net/chinesepython" # html_info = """ # <p>點選以下連結,你會去向一個更大的世界</p> # <p><a href="%s">click me</a></p> # <p>i am very galsses for you</p> # """% url # html_sub = MIMEText(html_info, 'html', 'utf-8') # # 如果不加下邊這行程式碼的話,上邊的文字是不會正常顯示的,會把超文字的內容當做文字顯示 # html_sub["Content-Disposition"] = 'attachment; filename="csdn.html"' # # 把構造的內容寫到郵件體中 # msg_root.attach(html_sub) # # 構造圖片 # image_file = open(r'D:\python_files\images\test.png', 'rb').read() # image = MIMEImage(image_file) # image.add_header('Content-ID', '<image1>') # # 如果不加下邊這行程式碼的話,會在收件方方面顯示亂碼的bin檔案,下載之後也不能正常開啟 # image["Content-Disposition"] = 'attachment; filename="red_people.png"' # msg_root.attach(image) # # 構造附件 # txt_file = open(r'D:\python_files\files\hello_world.txt', 'rb').read() # txt = MIMEText(txt_file, 'base64', 'utf-8') # txt["Content-Type"] = 'application/octet-stream' # #以下程式碼可以重新命名附件為hello_world.txt # txt.add_header('Content-Disposition', 'attachment', filename='hello_world.txt') # msg_root.attach(txt) try: sftp_obj = smtplib.SMTP_SSL('smtp.qq.com', 465) sftp_obj.login(self.sender_mail, self.sender_pass) sftp_obj.sendmail(self.sender_mail, self.to, msg_root.as_string()) sftp_obj.quit() print('sendemail successful!') except Exception as e: print('sendemail failed next is the reason') print(e) def Begin(self): self.User_Pas()#獲取資訊 self.Get_code()#獲取驗證碼以及cookies資訊,用session進行儲存 self.Load_Page()#進入個人中心,更新cookies class_view_html = self.Get_Class_View()#進入所有作業的單頁面,並且返該介面 len_class = len(class_view_html.xpath('/html/body/div/div[2]/div[3]/ul/li'))#計算一共有多少個課程 all_text = []#總的郵件資訊 for i in range(1,len_class):#開始遍歷每門課 print('開始第',i) Singel_Class_Url_after = class_view_html.xpath('/html/body/div/div[2]/div[3]/ul/li['+str(i)+']/div[2]/h3/a/@href')[0] # print(Singel_Class_Url_after) # all_text.append([threading.Thread(target=self.Go_to_work,args=(Singel_Class_Url_after)).start()]) work_text = self.Go_to_work(Singel_Class_Url_after) if work_text == None or len(work_text) <= 1: continue else: all_text.append(work_text) #將沒有作業的課程刪除 return all_text if __name__ == '__main__': #while 1: #time_list = time.strftime("%H:%M:%S", time.localtime()).split(":") # if time_list[0] == "08" and time_list[1] == "00" and time_list[2] == "00": # pachong = PaChaongxin(201821094053,"Mmgh774109","1766446371@qq.com") # work_email = pachong.Begin() # pachong.send_email_by_qq(work_email) # pachong = PaChaongxin(201821094049,"wcsba8102","2651447403@qq.com") # work_email = pachong.Begin() # pachong.send_email_by_qq(work_email) pachong = PaChaongxin(學號,密碼,郵箱) work_email = pachong.Begin() pachong.send_email_by_qq(work_email) pachong = PaChaongxin(xxxxxxx,"xxxxxxx","xxxxx@xxxxx") work_email = pachong.Begin() pachong.send_email_by_qq(work_email)
總結
果然任務驅動加上興趣的學習,能更加擴充套件知識面,本次例項,都瞭解了:爬蟲知識,網路知識,密碼學,雲伺服器的使用,郵件知識,python的使用。總體來說,受益匪淺。希望大家能共同進步
PS:如有需要Python學習資料的小夥伴可以加點選下方連結自行獲取
相關文章
- 從零開始學爬蟲(3):通過MongoDB資料庫獲取爬蟲資料爬蟲MongoDB資料庫
- 【Python學習】爬蟲爬蟲爬蟲爬蟲~Python爬蟲
- Python爬蟲學習(5): 簡單的爬取Python爬蟲
- node.js爬取資料並定時傳送HTML郵件Node.jsHTML
- 爬蟲作業一爬蟲
- Python爬蟲學習(6): 爬取MM圖片Python爬蟲
- 爬蟲 Scrapy框架 爬取圖蟲圖片並下載爬蟲框架
- 爬蟲學習筆記:練習爬取多頁天涯帖子爬蟲筆記
- Python爬蟲學習筆記(1)爬取知乎使用者資訊Python爬蟲筆記
- python爬蟲學習01--電子書爬取Python爬蟲
- 爬蟲可以通過代理ip收集哪些資料?爬蟲
- 如何學習 Python 包並實現基本的爬蟲過程Python爬蟲
- 一起學爬蟲——使用Beautiful Soup爬取網頁爬蟲網頁
- 爬蟲練習——爬取縱橫中文網爬蟲
- 房產資料爬取、智慧財產權資料爬取、企業工商資料爬取、抖音直播間資料python爬蟲爬取Python爬蟲
- 爬蟲學習之基於Scrapy的網路爬蟲爬蟲
- 爬蟲作業03-爬取解密大資料專欄下的所有文章爬蟲解密大資料
- 爬蟲學習之一個簡單的網路爬蟲爬蟲
- 小白學 Python 爬蟲(25):爬取股票資訊Python爬蟲
- 爬蟲爬取微信小程式爬蟲微信小程式
- 爬蟲之股票定向爬取爬蟲
- 爬蟲學習-初次上路爬蟲
- nodeJS 爬蟲,通過Puppeteer實現滾動載入NodeJS爬蟲
- python爬蟲---網頁爬蟲,圖片爬蟲,文章爬蟲,Python爬蟲爬取新聞網站新聞Python爬蟲網頁網站
- python爬蟲學習:爬蟲QQ說說並生成詞雲圖,回憶滿滿Python爬蟲
- Laravel 後臺與爬蟲互動-通過 Redis 的頻道訂閱來通訊Laravel爬蟲Redis
- 提高爬蟲爬取效率的辦法爬蟲
- 爬取資料時防止爬蟲被限制的四種方法爬蟲
- [python爬蟲] 招聘資訊定時系統 (一).BeautifulSoup爬取資訊並儲存MySQLPython爬蟲MySql
- Node.js爬取妹子圖-crawler爬蟲的使用Node.js爬蟲
- [爬蟲] 利用 Python 的 Selenium 庫爬取極客時間付費課程並儲存為 PDF 檔案爬蟲Python
- Python爬蟲學習(9):Selenium的使用Python爬蟲
- Python爬蟲學習(1): urllib的使用Python爬蟲
- Python爬蟲學習(11):Beautiful Soup的使用Python爬蟲
- Java爬蟲批量爬取圖片Java爬蟲
- 如何合理控制爬蟲爬取速度?爬蟲
- Java爬蟲-爬取疫苗批次資訊Java爬蟲
- 什麼是爬蟲?學習Python爬蟲難不難?爬蟲Python