【深度技術】小試牛刀:使用Python模擬登錄知乎

作者:劉帝偉(微博:@拾毅者)

原文鏈接:點擊這裡

BitTiger尊重原創版權,轉載已經過授權。

最近突然對爬蟲興趣倍增,主要是自己想從網上爬點數據來玩玩。前陣子從某房屋出售網爬取了長沙地區的房價以及2016年的成交額,只有幾千條數據,量較少,沒勁o(╯□╰)o,因此將目標成功地轉移到了社交網上,難度顯然大了點。爬取社交網站比較鮮明的特點就是需要登錄,否則很多東西都無法獲取。做了幾個小Demo之後發現,人人網的登錄還比較簡單,驗證碼的都不用就可以成功登錄;知乎雖然攜帶驗證碼,但難度算是適中;微博的登錄難度稍微大點,因為不僅有驗證碼,還在傳遞參數的時候對用戶名進行了base64加密。在這篇博文里,主要是以知乎為例,模擬知乎登錄,至於數據爬取部分咱們就暫且不談吧。

環境與開發工具

模擬知乎登錄前,先看看本次案例使用的環境及其工具:

  • Windows 7 + Python 2.75
  • Chrome + Fiddler: 用來監控客戶端與伺服器的通訊情況,以及查找相關參數的位置。

Github源碼下載:GitHub - csuldw/WSpider: a spider project to scratch web data..

模擬過程概述

  1. 使用Google瀏覽器結合Fiddler來監控客戶端與服務端的通訊過程;
  2. 根據監控結果,構造請求伺服器過程中傳遞的參數;
  3. 使用Python模擬參數傳遞過程。

客戶端與服務端通信過程的幾個關鍵點:

  • 登錄時的url地址。
  • 登錄時提交的參數【params】,獲取方式主要有兩種:第一、分析頁面源代碼,找到表單標籤及屬性。適應比較簡單的頁面。第二、使用抓包工具,查看提交的url和參數,通常使用的是Chrome的開發者工具中的Network, Fiddler等。
  • 登錄後跳轉的url。

在抓包的時候,開始使用的是Chrome開發工具中的Network,結果沒有抓到,後來使用Fiddler成功抓取數據。下面逐步來細化上述過程。

參數探索

首先看看這個登錄頁面(https//www.zhihu.com),也就是我們登錄時的url地址。

看到這個頁面,我們也可以大概猜測下請求伺服器時傳遞了幾個欄位,很明顯有:用戶名、密碼、驗證碼以及「記住我」這幾個值。那麼實際上有哪些呢?下面來分分析下。

首先查看一下HTML源碼,Google里可以使用CTRL+U查看,然後使用CTRL+F輸入input看看有哪些欄位值,詳情如下:

通過源碼,我們可以看到,在請求伺服器的過程中還攜帶了一個隱藏欄位」_xsrf」。那麼現在的問題是:這些參數在傳遞時是以什麼名字傳遞的呢?這就需要借用其他工具抓包進行分析了。筆者是Windows系統,這裡使用的是Fiddler(當然,你也可以使用其他的)。

抓包過程比較繁瑣,因為抓到的東西比較多,很難快速的找到需要的信息。關於fiddler,很容易使用,有過不會,可以去百度搜一下。為了防止其他信息干擾,我們先將fiddler中的記錄清除,然後輸入用戶名(筆者使用的是郵箱登錄)、密碼等信息登錄,相應的在fiddler中會有如下結果:

備註:如果是使用手機登錄,則對應fiddler中的url是「/login/phone_num」。

為了查看詳細的請求參數,我們左鍵單機「/login/email」,可以看到下列信息:

請求方式為POST,請求的url為首頁。而從From Data可以看出,相應的欄位名稱如下:

  • _xsrf
  • captcha
  • email
  • password
  • remember

對於這五個欄位,代碼中email、password以及captcha都是手動輸入的,remember初始化為true。剩下的_xsrf則可以根據登錄頁面的源文件,取input為_xsrf的value值即可。

對於驗證碼,則需要通過額外的請求,該鏈接可以通過定點查看源碼看出:

鏈接為zhihu.com/captcha.gif?,這裡省略了ts(經測試,可省略掉)。現在,可以使用代碼進行模擬登錄。

溫馨提示:如果使用的是手機號碼進行登錄,則請求的url為首頁,同時email欄位名稱將變成「phone_num」。

模擬源碼

在編寫代碼實現知乎登錄的過程中,筆者將一些功能封裝成了一個簡單的類WSpider,以便復用,文件名稱為WSpider.py。

# -*- coding: utf-8 -*-"""

Created on Thu Nov 02 14:01:17 2016@author: liudiwei"""import urllibimport urllib2import cookielibimport logging class WSpider(object): def __init__(self): #init params self.url_path = None self.post_data = None self.header = None self.domain = None self.operate = None #init cookie self.cookiejar = cookielib.LWPCookieJar() self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookiejar)) urllib2.install_opener(self.opener) def setRequestData(self, url_path=None, post_data=None, header=None): self.url_path = url_path self.post_data = post_data self.header = header def getHtmlText(self, is_cookie=False): if self.post_data == None and self.header == None: request = urllib2.Request(self.url_path) else: request = urllib2.Request(self.url_path, urllib.urlencode(self.post_data), self.header) response = urllib2.urlopen(request) if is_cookie: self.operate = self.opener.open(request) resText = response.read() return resText """ Save captcha to local """ def saveCaptcha(self, captcha_url, outpath, save_mode="wb"): picture = self.opener.open(captcha_url).read() #用openr訪問驗證碼地址,獲取cookie local = open(outpath, save_mode) local.write(picture) local.close() def getHtml(self, url): page = urllib.urlopen(url) html = page.read() return html """ 功能:將文本內容輸出至本地 @params content:文本內容 out_path: 輸出路徑 """ def output(self, content, out_path, save_mode="w"): fw = open(out_path, save_mode) fw.write(content) fw.close() """#EXAMPLE logger = createLogger("mylogger", "temp/logger.log") logger.debug("logger debug message") logger.info("logger info message") logger.warning("logger warning message") logger.error("logger error message") logger.critical("logger critical message") """ def createLogger(self, logger_name, log_file): # 創建一個logger logger = logging.getLogger(logger_name) logger.setLevel(logging.INFO) # 創建一個handler,用於寫入日誌文件 fh = logging.FileHandler(log_file) # 再創建一個handler,用於輸出到控制台 ch = logging.StreamHandler() # 定義handler的輸出格式formatter formatter = logging.Formatter("%(asctime)s | %(name)s | %(levelname)s | %(message)s") fh.setFormatter(formatter) ch.setFormatter(formatter) # 給logger添加handler logger.addHandler(fh) logger.addHandler(ch) return logger

關於模擬登錄知乎的源碼,保存在zhiHuLogin.py文件,內容如下:

# -*- coding: utf-8 -*-"""Created on Thu Nov 02 17:07:17 2016@author: liudiwei"""import urllibfrom WSpider import WSpiderfrom bs4 import BeautifulSoup as BSimport getpassimport jsonimport WLogger as WLog"""2016.11.03 由於驗證碼問題暫時無法正常登陸2016.11.04 成功登錄,期間出現下列問題驗證碼錯誤返回:{ "r": 1, "errcode": 1991829, "data": {"captcha":"驗證碼錯誤"}, "msg": "驗證碼錯誤" }驗證碼過期:{ "r": 1, "errcode": 1991829, "data": {"captcha":"驗證碼回話無效 :(","name":"ERR_VERIFY_CAPTCHA_SESSION_INVALID"}, "msg": "驗證碼回話無效 :(" }登錄:{"r":0, "msg": "登錄成功"}"""def zhiHuLogin(): spy = WSpider() logger = spy.createLogger("mylogger", "temp/logger.log") homepage = r"https://www.zhihu.com/" html = spy.opener.open(homepage).read() soup = BS(html, "html.parser") _xsrf = soup.find("input", {"type":"hidden"}).get("value") #根據email和手機登陸得到的參數名不一樣,email登陸傳遞的參數是『email』,手機登陸傳遞的是『phone_num』 username = raw_input("Please input username: ") password = getpass.getpass("Please input your password: ") account_name = None if "@" in username: account_name = "email" else: account_name = "phone_num" #保存驗證碼 logger.info("save captcha to local machine.") captchaURL = r"https://www.zhihu.com/captcha.gif?type=login" #驗證碼url spy.saveCaptcha(captcha_url=captchaURL, outpath="temp/captcha.jpg") #temp目錄需手動創建 #請求的參數列表 post_data = { "_xsrf": _xsrf, account_name: username, "password": password, "remember_me": "true", "captcha":raw_input("Please input captcha: ") } #請求的頭內容 header ={ "Accept":"*/*" , "Content-Type":"application/x-www-form-urlencoded; charset=UTF-8", "X-Requested-With":"XMLHttpRequest", "Referer":"https://www.zhihu.com/", "Accept-Language":"en-GB,en;q=0.8,zh-CN;q=0.6,zh;q=0.4", "Accept-Encoding":"gzip, deflate, br", "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36", "Host":"www.zhihu.com" } url = r"https://www.zhihu.com/login/" + account_name spy.setRequestData(url, post_data, header) resText = spy.getHtmlText() jsonText = json.loads(resText) if jsonText["r"] == 0: logger.info("Login success!") else: logger.error("Login Failed!") logger.error("Error info ---> " + jsonText["msg"]) text = spy.opener.open(homepage).read() #重新打開主頁,查看源碼可知此時已經處於登錄狀態 spy.output(text, "out/home.html") #out目錄需手動創建if __name__ == "__main__": zhiHuLogin()

關於源碼的分析,可以參考代碼中的註解。

運行結果

在控制台中運行python zhiHuLogin.py,然後按提示輸入相應的內容,最後可得到以下不同的結果(舉了三個實例):

結果一:密碼錯誤

結果二:驗證碼錯誤

結果三:成功登錄

通過代碼,可以成功的登錄到知乎,接著如果要爬取知乎裡面的內容,就比較方便了。

說了這麼多,不想自己試一試嗎?歡迎報名參加太閣Python課程:點擊這裡

更多精彩,盡在矽谷高端線上教育社區BitTiger:請猛戳我

公眾號:論碼農的自我修養

知乎專欄:太閣實驗室

微博:@太閣BitTiger

今日頭條:太閣BitTiger

更新日誌:

11/10/16:突然發現圖片掛了,重新更新了一遍。


推薦閱讀:

認準目標,前進
Python3實現TCP埠掃描器
使用文本挖掘實現站點個性化推薦
Python黑帽編程 3.4 跨越VLAN

TAG:Python | Python入门 | Python开发 |