模擬登陸CSDN -- Python爬蟲練習之正則表示式和cookie
這周學習的主題是正則表示式和cookie,原本是計劃每天晚上11點下班到家,練上一兩個鍾就把這部分過了,結果這周各種事情和不再狀態,所以沒整完,直至今天才把相關問題過掉。其實這部分也挺不錯的,也並沒有想象中容易,所以好事多磨。這周練習的綜合習題就是模擬登陸CSDN,實現過程不難,最終實現程式碼在最後面。
1. 登陸的步驟 –> 請求和頁面分析
第一步我們需要清楚登陸流程以及需要提交哪些資料到伺服器以完成登陸認證。進入CSDN登入介面, 我們實際登陸下,並通過F12進入chrome DevTools抓下相關資料,需要注意的是除錯前我們要勾選如下選項,保證登陸成功頁面跳轉時,資料能保留下來,不被清空。
輸入賬號和密碼後點擊登陸。在眾多請求中,我們可以找到有且僅有的一個POST請求,這個也就是我們相關登陸資訊所在的請求了。
通過DevTools可以看到請求的主體如下。因此,POST到伺服器的資料有五項,username,password,lt,execution和_eventId。前兩項比較好理解,即為我們輸入的賬號和密碼,那下面的三項是哪裡蹦出來的?有果必有因,我們分析下頁面原始碼。
Ctrl + Shift + C(檢查頁面元素)選中密碼輸入框,Chrome頁面分析工具跳到Element頁如下位置。從這裡我們可以看到如下資訊,紅框中三項也就是我們需要的資料了。通過註釋(我最喜歡寫註釋這麼幹脆的孩子!!),我們可以知道LT這個引數是用於賬號登陸時相應請求處理的流水號。如果沒有這個流水號,請求會被判定非法並強制重新登陸。這樣我們又學習到一個新的反爬手段了,然而我們此次就是要繞過這個機制——提取這個資料附到POST資料上。
除了如上的請求資料外,我們還需要針對cookie做相關處理,因為我們登陸後的狀態是記錄在cookie中。
2. 如何模擬 –> 流程程式碼實現
1) Cookie的引入
HTTP協議是一種不儲存狀態(stateless)的協議,即不對通訊中請求和響應之間的通訊狀態做儲存。每次新請求都會有新響應,因此我們通訊過程中的相關資訊得不到儲存,登陸狀態也不會被記錄下來,這會導致我們每訪問一個頁面就需要重新登陸一次以鑑別身份。但,實際並不是這樣,這是因為有cookie技術的引入。如下,客戶端的request header中Cookie欄位帶有本地的cookie資訊,訪問時發給伺服器,作為一種身份標示和認證。而伺服器會根據實際需要發出Set-Cookie的響應,此時客戶端會對應將cookie儲存下來,後續訪問伺服器資源時,再附上,供伺服器做處理。
具體實現如下,其中我加入了檔案用於存放相關cookie資訊,現在的目的是方便debug看情況。在實際應用中,可以通過直接讀取cookie檔案資訊,然後進行http訪問,可免去重複登入的操作。
'''for cookie function'''
CookieFile = "cookie.txt"
CookieJar = http.cookiejar.MozillaCookieJar(CookieFile) # 建立cookie物件
CookieProcessor = urllib.request.HTTPCookieProcessor(CookieJar) # 建立cookie處理器
Opener = urllib.request.build_opener(CookieProcessor, urllib.request.HTTPHandler) # 以對應的cookie處理器建立opener物件
urllib.request.install_opener(Opener) #安裝opener為全域性
如果不想加入檔案操作的話,可以參考如下實現,Reference:
import http.cookiejar, urllib.request
cj = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
urllib.request.install_opener(Opener) #安裝opener為全域性
2) 反爬蟲
Headers = {
'Connection' :'keep-alive',
'Cache-Control' :'max-age=0',
'User-Agent' :'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'Accept' :'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
# 'Accept-Encoding' :'gzip, deflate, br',
'Accept-Language' :'zh-CN,zh;q=0.8,en;q=0.6',
'Upgrade-Insecure-Request':'1',
}
CSDN的反爬繞過是最簡單的新增header的方式,可參考反爬蟲繞過初級——新增http header和gzip解壓處理。CSDN貌似只檢測User-Agent,因此新增這個header之後,再訪問登陸頁面就不會出現403 forbidden的提示。我一般直接採用下面這個header,避免伺服器通過檢測多個欄位做反爬時,需要去查是哪些欄位,再去新增。
可以看到下面這個headers被我註釋掉了Accept-Encoding欄位。因為這個欄位應該是Transparent Negotiation的,也就是客戶端如果請求有宣告支援gzip,CSDN伺服器就會採用gzip傳輸,否則就直接傳輸未經壓縮的原始檔,為了少去解壓縮這部分工作,所以我直接註釋掉了。
3) 資料提取
'''data for login'''
LoginUrl = "https://passport.csdn.net/account/login?ref=toolbar"
LoginData ={
"username" : "",
"password" : ""
}
RegExp_Template = "(?<=name=\"%s\" value=\")%s(?=(\">)?)" # tempalate for Regular Expression
Lt_RegExp = RegExp_Template % ( "lt", "LT-\d{6}-\w+" )
Execution_RegExp = RegExp_Template % ( "execution", "\w+" )
EventId_RegExp = RegExp_Template % ( "_eventId", "\w+" )
'''pretreatment for login'''
req = urllib.request.Request(LoginUrl, None, Headers)
String = urllib.request.urlopen(req).read().decode('utf-8')
CookieJar.save(ignore_discard=True, ignore_expires=True)
'''To get the data we need to post to Login Server'''
LoginData['lt'] = re.search(Lt_RegExp, String).group()
LoginData['execution'] = re.search(Execution_RegExp, String).group()
LoginData['_eventId'] = re.search(EventId_RegExp, String).group()
除了賬號和密碼我們可直接輸入外,lt、execution、_eventId這三個需要在網頁中抓取。首先需要請求下登陸頁面–> 抓取響應頁面 –>分析頁面內容獲取三個資料。
網上介紹的多是通過beautifulsoup分析頁面資料,我看了下相關實現,感覺確實方便很多。不過既然是練習正則表示式了,那我還是採用正則抓取相關內容,這部分的正則也是比較簡單的。關於正則表示式的,推薦通過正則表示式30分鐘入門教程學習。我測試正則一般都是將頁面原始碼複製下來,然後在notepad++中測試是否能正確匹配,然後才置於原始碼中,這樣可以避開一些坑。
通過對下面原始碼的解析,我們可以看到他們有一定的共性,也就是下面這條表示式:
RegExp_Template = “(?<=name=\”%s\” value=\”)%s(?=(\”>)?)”
變數只是兩個’%s’部分,為此我寫成template方便程式設計和看code。這個表示式可分三部分,第一部分是(?<=name=\”%s\” value=\”),是一個零寬度正回顧後發斷言,只匹配不佔用字元;第二部分是我們需要的字元的正則,也就是第二個%s;第三部分是零寬度負回顧先行斷言,也是隻匹配不佔用字元。根據下面的場景,我們可以知道,第三部分其實可以不用加上去,因為目標字元結尾都是\w,因此到點即會停止匹配。
這三個資料中,execution這個比較有趣,e後面這個數字就是訪問次數,如果你的cookie為新時,那這個值為1,後續採用同個cookie訪問登陸頁,依次加一,所以可以用於檢測cookie是否正常連續工作。
4) 模擬登陸
'''log in'''
PostData = urllib.parse.urlencode(LoginData).encode('utf-8')
req = urllib.request.Request(LoginUrl, PostData, Headers)
UrlData = urllib.request.urlopen(req)
CookieJar.save(ignore_discard=True, ignore_expires=True)
data = UrlData.read()
print('\n' + '='*10 + 'login response headers' + '='*10 + '\n', UrlData.info(), '\n'+'='*42 )
with open('LoginPage.html','wb') as hfile:
hfile.write(data)
在集齊username,password,lt,execution和_eventId五項資料後,我們可以開始模擬登陸CSDN了。
urllib.parse.urlencode(LoginData).encode(‘utf-8’)這部分是將資料轉換成網路訪問的格式,我們設定的資料是dictionary的,但實際POST到伺服器的資料是下面這樣的,因此需要做轉換。
轉換後,可以通過Request將POST data和Request Headers傳送到對應的URL,登陸CSDN。此時相關資訊也會被cookie記錄下來,在下面的code中CookieJar.save(ignore_discard=True, ignore_expires=True)這個是儲存cookie資料,在這裡我們是儲存到檔案中,其中ignore_discard表示即便cookie即將被棄用也儲存下來,而ignore_expires表示若該cookie已存在則直接覆蓋。下面是一個cookie檔案的例項。
登陸和儲存cookie後,還有返回一個跳轉頁面,這個頁面很重要,有多重要呢,請看下圖,大量紅圈資料,用心體會下,哈哈哈哈。
4) 登陸後確認
3. 總結
總的來說,這個作為入門練習也是一個挺好玩的專案。這次debug和抓資訊,感覺自己在整個訪問過程中,就是處於裸奔狀態,雖說csdn的登陸頁面是https協議的,對外還好,但如果你的瀏覽器中加裝其他外掛呢?像一些廣告去除外掛,這種直接對訪問資料進行攔截過濾的,如果直接過濾採集你的登陸和相關頁面訪問資訊呢,細思極恐。最後附上整體實現程式碼,我菜雞一枚,所以程式碼難免有考慮不足的地方,請多多指教。
#!/usr/local/bin/python3
#-*- coding: utf-8 -*-
import urllib.request
import urllib.parse
import http.cookiejar
import re
'''data for login'''
LoginUrl = "https://passport.csdn.net/account/login?ref=toolbar"
VisitUrl = "http://my.csdn.net/?ref=toolbar"
LoginData ={
"username" : "",
"password" : ""
}
Headers = {
'Connection' :'keep-alive',
'Cache-Control' :'max-age=0',
'User-Agent' :'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'Accept' :'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
# 'Accept-Encoding' :'gzip, deflate, br',
'Accept-Language' :'zh-CN,zh;q=0.8,en;q=0.6',
'Upgrade-Insecure-Request':'1',
}
'''please test the following regular expression before actual use'''
RegExp_Template = "(?<=name=\"%s\" value=\")%s(?=(\">)?)" # tempalate for Regular Expression
Lt_RegExp = RegExp_Template % ( "lt", "LT-\d{6}-\w+" )
Execution_RegExp = RegExp_Template % ( "execution", "\w+" )
EventId_RegExp = RegExp_Template % ( "_eventId", "\w+" )
def Login_Func(LoginData):
'''for cookie function'''
CookieFile = "cookie.txt"
CookieJar = http.cookiejar.MozillaCookieJar(CookieFile)
CookieProcessor = urllib.request.HTTPCookieProcessor(CookieJar)
Opener = urllib.request.build_opener(CookieProcessor, urllib.request.HTTPHandler)
urllib.request.install_opener(Opener)
'''pretreatment for login'''
req = urllib.request.Request(LoginUrl, None, Headers)
String = urllib.request.urlopen(req).read().decode('utf-8')
CookieJar.save(ignore_discard=True, ignore_expires=True)
'''To get the data we need to post to Login Server'''
LoginData['lt'] = re.search(Lt_RegExp, String).group()
print("[DEBUG] Get LT: %s "%Lt_RegExp, LoginData.get('lt'))
LoginData['execution'] = re.search(Execution_RegExp, String).group()
print("[DEBUG] Get execution: %s "%Execution_RegExp, LoginData.get('execution'))
LoginData['_eventId'] = re.search(EventId_RegExp, String).group()
print("[DEBUG] Get eventId: %s "%EventId_RegExp, LoginData.get('_eventId'))
'''log in'''
PostData = urllib.parse.urlencode(LoginData).encode('utf-8')
req = urllib.request.Request(LoginUrl, PostData, Headers)
UrlData = urllib.request.urlopen(req)
CookieJar.save(ignore_discard=True, ignore_expires=True)
data = UrlData.read()
print('\n' + '='*10 + 'login response headers' + '='*10 + '\n', UrlData.info(), '\n'+'='*42 )
with open('LoginPage.html','wb') as hfile:
hfile.write(data)
'''Check whether log in sucessfully'''
req = urllib.request.Request(VisitUrl,None,Headers)
UrlData = urllib.request.urlopen(req)
data = UrlData.read()
print('\n' + '='*10 + 'Visit response headers'+'='*10 + '\n', UrlData.info(), '\n'+'='*42 )
with open('VisitPage.html','wb') as hfile:
hfile.write(data)
if __name__ == '__main__':
Login_Func(LoginData)