python3下使用requests模擬用戶登錄 —— 中級篇(百度雲俱樂部)

python3下使用requests模擬用戶登錄 —— 中級篇(百度雲俱樂部)

1. 背景

2. 環境

  • 系統:win7
  • python 3.6.1
  • requests 2.14.2 (通過pip list查看)

3. 模擬登錄百度雲俱樂部

3.1. 分析用戶登錄必要的信息

  • 值得一提的是,在分析這種複雜登錄請求時,需要保存登錄過程中,所有的頁面請求和響應情況,這樣才方便找到所有的登錄參數,回溯查找,尋找來源。 如果之前的信息丟失了,那重新打開的請求和上一次請求的參數是對不上的,就不方便查找數據來源了。
    這裏寫圖片描述

  • 點擊登錄,提取到的信息如下:

# General:
Request URL:http://www.51baiduyun.com/member.php?mod=logging&action=login&loginsubmit=yes&handlekey=login&loginhash=LyqBH&inajax=1
Request Method:POST
Status Code:200 OK
Remote Address:47.91.148.25:80
Referrer Policy:no-referrer-when-downgrade

# Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.9
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:216
Content-Type:application/x-www-form-urlencoded
Cookie:L3em_2132_saltkey=geeeP9NE; L3em_2132_lastvisit=1521177080; UM_distinctid=1622d6bb6c589-07e7bc1d9d570b-454c092b-1fa400-1622d6bb6c6609; L3em_2132_lastcheckfeed=1315026%7C1521180699; L3em_2132_nofavfid=1; L3em_2132_ulastactivity=406fKQfd1L5Bpyfb%2BwxJVkSKEP%2FeA%2FE0EKi0tL5iIiN0sPTQgPK7; L3em_2132_smile=1D1; Hm_lvt_79316e5471828e6e10f2df47721ce150=1521508740,1521515152,1521516204,1521712944; Hm_lvt_eaefab1768d285abfc718a706c1164f3=1521508740,1521515152,1521516204,1521712944; L3em_2132_st_p=0%7C1521785655%7C706b59df71621dff8c64747f9df9e6a1; L3em_2132_visitedfid=38D66D68D37D44D41D45; L3em_2132_viewid=tid_480044; CNZZDATA1253365484=727141784-1521177990-null%7C1521783379; CNZZDATA1253863031=1430494274-1521176587-null%7C1521787718; Hm_lpvt_79316e5471828e6e10f2df47721ce150=1521787672; Hm_lpvt_eaefab1768d285abfc718a706c1164f3=1521787672; L3em_2132_sid=AHqj37; L3em_2132_seccode=79666.06fdbff670d881c68f; L3em_2132_lastact=1521787976%09misc.php%09seccode
Host:www.51baiduyun.com
Origin:http://www.51baiduyun.com
Referer:http://www.51baiduyun.com/
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36

# Query String Parameters
mod:logging
action:login
loginsubmit:yes
handlekey:login
loginhash:LyqBH
inajax:1

# Form Data
formhash:7736cc00
referer:http://www.51baiduyun.com/
loginfield:username
username:aaaaaaaaaa
password:bbb12345678
questionid:0
answer:
seccodehash:cSAAHqj37
seccodemodid:member::logging
seccodeverify:eB3X
  • 我們嘗試用不同的用戶名登陸幾次,還記得之前說過,使用錯誤的密碼登錄吧,防止登陸成功,造成頁面跳轉,干擾分析。結果發現有幾個主要的登錄參數需要特別留意,每次都會不同,如下所示:
    • formhash
    • username:用戶名
    • password:密碼
    • seccodehash
    • seccodeverify:驗證碼
  • 其中 formhash 和 seccodehash的來源目前還不明朗,seccodeverify的獲取也需要技巧,接着看往下…

3.2. 尋找 formhash

  • 像這種類型的參數,一般來說有幾個來源:
    • 第一,網頁源代碼中攜帶。
    • 第二,藏在服務器返回過來的cookie中。
    • 第三,網頁js腳本計算生成。
    • 第四,登錄時,請求js腳本生成,或者同步產生的其他網頁中。
  • 比較幸運的是,直接在登錄頁的源代碼中找到了(同時找到的還有referer):
    這裏寫圖片描述
# Request URL:http://www.51baiduyun.com/member.php?mod=logging&action=login&infloat=yes&handlekey=login&inajax=1&ajaxtarget=fwin_content_login

<form method="post" autocomplete="off" name="login" id="loginform_LyqBH" class="cl" οnsubmit="pwdclear = 1;ajaxpost('loginform_LyqBH', 'returnmessage_LyqBH', 'returnmessage_LyqBH', 'onerror');return false;" action="member.php?mod=logging&amp;action=login&amp;loginsubmit=yes&amp;handlekey=login&amp;loginhash=LyqBH">
<div class="c cl">
<input type="hidden" name="formhash" value="7736cc00" />
<input type="hidden" name="referer" value="http://www.51baiduyun.com/" />
<div class="rfm">

3.3. 尋找 seccodehash

  • 這個我是在cookie中找到的。
    這裏寫圖片描述
# 很明顯能看到,是按如下方式構造的:
# seccodehash:cSAAHqj37
cSA + cookie中的 AHqj37
  • 經過分析,formhash 和 seccodehash的提取方案如下:
# 從網頁源碼和cookie中拿到登錄所需的參數
def getLoginArgs():
    # 首先第一步,從網頁源碼中獲得 formhash
    response = baiduyunSession.get("http://www.51baiduyun.com/member.php?mod=logging&action=login&infloat=yes&handlekey=login&inajax=1&ajaxtarget=fwin_content_login", headers=header)
    # print(f"statusCode = {response.status_code}, text = {response.text}")
    print(f"statusCode = {response.status_code}")
    '''
        <div class="c cl">
        <input type="hidden" name="formhash" value="7736cc00" />
        <input type="hidden" name="referer" value="http://www.51baiduyun.com/" />
    '''
    # formhashRe = re.search('name="formhash" value="(.*?)"', response.text, re.DOTALL)
    formhashRe = re.search('name="formhash" value="(\w+?)"', response.text, re.DOTALL)
    refererRe = re.search('name="referer" value="(.*?)"', response.text, re.DOTALL)
    print(f"formhashRe = {formhashRe}, refererRe = {refererRe}")
    if formhashRe:
        formhash = formhashRe.group(1)
    else:
        formhash = ""
    if refererRe:
        referer = refererRe.group(1)
    else:
        referer = ""

    # 第二步,從cookie中獲得 seccodehash
    cookieGet = baiduyunSession.cookies
    # print(f"cookieGet = {cookieGet}")
    seccodehash = ""
    for item in cookieGet:
        print(f"itemName = {item.name}, itemValue = {item.value}")
        if item.name.find('_sid') != -1:
            seccodehash = 'cSA' + item.value
    return {'formhash':formhash, 'referer':referer, 'seccodehash':seccodehash}

3.4. 探索驗證碼seccodeverify

  • 關於如何打碼,請參考文章:
  • 驗證碼的獲取是需要一點小技巧的,因爲驗證碼可以刷新,每次都可以重新獲取到。所以我們點擊” 刷新驗證碼 “,看看新的驗證碼是如何獲取到的:
    這裏寫圖片描述
  • 可以看到產生了兩個請求。我們找到返回驗證碼的這個請求,看看是什麼樣的請求。
    這裏寫圖片描述
# General:
Request URL:http://www.51baiduyun.com/misc.php?mod=seccode&update=66158&idhash=cSAAHqj37
Request Method:GET
Status Code:200 OK
Remote Address:47.91.148.25:80
Referrer Policy:no-referrer-when-downgrade

# Query String Parameters
mod:seccode
update:66158
idhash:cSAAHqj37
  • 多了一個未知的參數:update:66158。按照3.2節中的思路,發現這個參數來自於 跟它一起發出的另一個請求。看一下這個請求的信息:
    這裏寫圖片描述
    • 這個請求的信息就比較簡單了:
# General:
Request URL:http://www.51baiduyun.com/misc.php?mod=seccode&action=update&idhash=cSAAHqj37&0.8088204604265181&modid=undefined
Request Method:GET
Status Code:200 OK
Remote Address:47.91.148.25:80
Referrer Policy:no-referrer-when-downgrade

# Query String Parameters
mod:seccode
action:update
idhash:cSAAHqj37
0.8088204604265181:
modid:undefined
  • 這裏面同樣有一個未知的參數0.8088204604265181,不過直覺告訴我,這只是一個隨機數,用於生成驗證碼的隨機數,不需要什麼特別的算法,可以由程序直接生成…後面的登錄結果也證實了我的這個想法。

  • 這個刷新驗證碼的流程就比較清晰了

    • 第一步:發送第一個請求,去獲得update這個參數。
    • 第二步:使用這個update參數,去獲取真實的驗證碼圖片。
# 刷新驗證碼
def get_captcha(seccodehash):
    # 第一步:發送第一個請求,獲取“update” 的參數值
    import random
    randomFloat = random.uniform(0, 1)
    url = f"http://www.51baiduyun.com/misc.php?mod=seccode&action=update&idhash={seccodehash}&{randomFloat}&modid=undefined"
    response = baiduyunSession.get(url = url, headers = header)
    print(f"statusCode = {response.status_code}, text = {response.text}")
    # src="misc.php?mod=seccode&update=22285&idhash=cSAD3vAf3"
    if response.status_code == 200:
        updateRe = re.search('update=(\d+?)&', response.text, re.DOTALL)
        print(f"updateRe = {updateRe}")
        if updateRe:
            update = int(updateRe.group(1))
        else:
            update = 0
        print(f"update = {update}")

    # 第二步:拿到update參數之後,用這些參數去請求驗證碼,然後保存成一張圖片
    # http://www.51baiduyun.com/misc.php?mod=seccode&update=88800&idhash=cSAY3fpK6
    captchaUrl = f"http://www.51baiduyun.com/misc.php?mod=seccode&update={update}&idhash={seccodehash}"
    t = baiduyunSession.get(captchaUrl, headers=header)
    # print(f"t = {t.text}")    # 打印結果可以看出是一張圖片
    with open("captcha51baiduyun.jpg", "wb") as f:
        f.write(t.content)
        f.close()

    # 在這裏,爲了讓邏輯簡單,暫時採用手動輸入驗證碼的方式。
    # 如果想讓程序自動打碼,可以參考文章:https://blog.csdn.net/zwq912318834/article/details/78616462
    from PIL import Image
    try:
        imObj = Image.open('captcha51baiduyun.jpg')
        imObj.show()
        imObj.close()
    except:
        pass
    captcha = input("輸入驗證碼\n>")
    return captcha

3.5. 最終,帶着所有的數據信息,進行模擬登錄。

  • 在這裏尤其需要注意到一點,因爲這些參數都有着千絲萬縷的聯繫,也就是屬於同一個會話中,所以我們需要全程使用session進行請求,不能使用requests。
  • 整個代碼如下:
# -*- coding: utf-8 -*-

import requests
import re

# python2 和 python3的兼容代碼
try:
    # python2 中
    import cookielib
    print(f"user cookielib in python2.")
except:
    # python3 中
    import http.cookiejar as cookielib
    print(f"user cookielib in python3.")

# session代表某一次連接
baiduyunSession = requests.session()
# 因爲原始的session.cookies 沒有save()方法,所以需要用到cookielib中的方法LWPCookieJar,這個類實例化的cookie對象,就可以直接調用save方法。
baiduyunSession.cookies = cookielib.LWPCookieJar(filename = "baiduyunCookies.txt")

userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
header = {
    "Referer": "http://www.51baiduyun.com/",
    'User-Agent': userAgent,
}

# 從網頁源碼和cookie中拿到登錄所需的參數
def getLoginArgs():
    # 首先第一步,從網頁源碼中獲得 formhash
    response = baiduyunSession.get("http://www.51baiduyun.com/member.php?mod=logging&action=login&infloat=yes&handlekey=login&inajax=1&ajaxtarget=fwin_content_login", headers=header)
    # print(f"statusCode = {response.status_code}, text = {response.text}")
    print(f"statusCode = {response.status_code}")
    '''
        <div class="c cl">
        <input type="hidden" name="formhash" value="7736cc00" />
        <input type="hidden" name="referer" value="http://www.51baiduyun.com/" />
    '''
    # formhashRe = re.search('name="formhash" value="(.*?)"', response.text, re.DOTALL)
    formhashRe = re.search('name="formhash" value="(\w+?)"', response.text, re.DOTALL)
    refererRe = re.search('name="referer" value="(.*?)"', response.text, re.DOTALL)
    print(f"formhashRe = {formhashRe}, refererRe = {refererRe}")
    if formhashRe:
        formhash = formhashRe.group(1)
    else:
        formhash = ""
    if refererRe:
        referer = refererRe.group(1)
    else:
        referer = ""

    # 第二步,從cookie中獲得 seccodehash
    cookieGet = baiduyunSession.cookies
    # print(f"cookieGet = {cookieGet}")
    seccodehash = ""
    for item in cookieGet:
        print(f"itemName = {item.name}, itemValue = {item.value}")
        if item.name.find('_sid') != -1:
            seccodehash = 'cSA' + item.value
    return {'formhash':formhash, 'referer':referer, 'seccodehash':seccodehash}


# 刷新驗證碼
def get_captcha(seccodehash):
    # 第一步:發送第一個請求,獲取“update” 的參數值
    import random
    randomFloat = random.uniform(0, 1)
    url = f"http://www.51baiduyun.com/misc.php?mod=seccode&action=update&idhash={seccodehash}&{randomFloat}&modid=undefined"
    response = baiduyunSession.get(url = url, headers = header)
    print(f"statusCode = {response.status_code}")
    # src="misc.php?mod=seccode&update=22285&idhash=cSAD3vAf3"
    if response.status_code == 200:
        updateRe = re.search('update=(\d+?)&', response.text, re.DOTALL)
        print(f"updateRe = {updateRe}")
        if updateRe:
            update = int(updateRe.group(1))
        else:
            update = 0
        print(f"update = {update}")

    # 第二步:拿到update參數之後,用這些參數去請求驗證碼,然後保存成一張圖片
    # http://www.51baiduyun.com/misc.php?mod=seccode&update=88800&idhash=cSAY3fpK6
    captchaUrl = f"http://www.51baiduyun.com/misc.php?mod=seccode&update={update}&idhash={seccodehash}"
    t = baiduyunSession.get(captchaUrl, headers=header)
    # print(f"t = {t.text}")    # 打印結果可以看出是一張圖片
    with open("captcha51baiduyun.jpg", "wb") as f:
        f.write(t.content)
        f.close()

    # 在這裏,爲了讓邏輯簡單,暫時採用手動輸入驗證碼的方式。
    # 如果想讓程序自動打碼,可以參考文章:https://blog.csdn.net/zwq912318834/article/details/78616462
    from PIL import Image
    try:
        imObj = Image.open('captcha51baiduyun.jpg')
        imObj.show()
        imObj.close()
    except:
        pass
    captcha = input("輸入驗證碼\n>")
    return captcha


def baiduyunLogin(account, password, argsData, captcha):
    # 百度雲模仿 登錄
    print("開始模擬登錄百度雲俱樂部")
    postUrl = "http://www.51baiduyun.com/member.php?mod=logging&action=login&loginsubmit=yes&handlekey=login&loginhash=Lpd1b&inajax=1"
    '''
        formhash:eb6fc0ed
        referer:http://www.51baiduyun.com/
        loginfield:username
        username:aaaaaa
        password:abc123456
        questionid:0
        answer:
        seccodehash:cSAY3fpK6
        seccodemodid:member::logging
        seccodeverify:ejwe
    '''
    postData = {
        "formhash": argsData['formhash'],
        "referer": argsData['referer'],
        "loginfield": 'username',
        "username": account,
        "password": password,
        "questionid": '0',
        "answer": '',
        "seccodemodid": 'member::logging',
        "seccodeverify": captcha,
    }
    # 使用session直接post請求
    responseRes = baiduyunSession.post(postUrl, data = postData, headers = header)
    # 無論是否登錄成功,狀態碼一般都是 statusCode = 200
    print(f"statusCode = {responseRes.status_code}")
    print(f"text = {responseRes.text}")
    # 登錄成功之後,將cookie保存在本地文件中,好處是,以後再去獲取馬蜂窩首頁的時候,就不需要再走baiduyunLogin的流程了,因爲已經從文件中拿到cookie了
    baiduyunSession.cookies.save()

def isLoginStatus():
    # 通過訪問個人中心頁面的返回狀態碼來判斷是否爲登錄狀態
    # 參考模擬登錄篇獲取做法,這兒略過
    pass


if __name__ == "__main__":
    # # 從返回結果來看,有登錄成功
    account = "13756567832"
    password = "000000001"
    argsData = getLoginArgs()
    print(f"argsData = {argsData}")
    captcha = get_captcha(argsData['seccodehash'])
    baiduyunLogin(account, password, argsData, captcha)
  • 登錄成功的結果:
E:\Miniconda\python.exe E:/PyCharmCode/ArticleSpider/ArticleSpider/utils/51baiduyunLogin.py
user cookielib in python3.
statusCode = 200
formhashRe = <_sre.SRE_Match object; span=(650, 682), match='name="formhash" value="9da3d01e"'>, refererRe = <_sre.SRE_Match object; span=(708, 757), match='name="referer" value="http://www.51baiduyun.com/">
itemName = L3em_2132_lastact, itemValue = 1521791100%09member.php%09logging
itemName = L3em_2132_lastvisit, itemValue = 1521787500
itemName = L3em_2132_saltkey, itemValue = RbtB6p6O
itemName = L3em_2132_sid, itemValue = xJn0kL
argsData = {'formhash': '9da3d01e', 'referer': 'http://www.51baiduyun.com/', 'seccodehash': 'cSAxJn0kL'}
statusCode = 200
updateRe = <_sre.SRE_Match object; span=(1079, 1092), match='update=30204&'>
update = 30204
輸入驗證碼
>E9CJ
開始模擬登錄百度雲俱樂部
statusCode = 200
text = <?xml version="1.0" encoding="utf-8"?>
<root><![CDATA[<script type="text/javascript" reload="1">if(typeof succeedhandle_login=='function') {succeedhandle_login('http://www.51baiduyun.com/', '歡迎您回來,見習會員 13756567832,現在將轉入登錄前頁面', {'username':'13756567832','usergroup':'見習會員','uid':'1315026','groupid':'23','syn':'0'});}hideWindow('login');showDialog('歡迎您回來,見習會員 13756567832,現在將轉入登錄前頁面', 'right', null, function () { window.location.href ='http://www.51baiduyun.com/'; }, 0, null, null, null, null, null, 3);</script>]]></root>
  • 驗證碼錯誤的結果:
updateRe = <_sre.SRE_Match object; span=(1079, 1092), match='update=90864&'>
update = 90864
輸入驗證碼
>1111
開始模擬登錄百度雲俱樂部
statusCode = 200
text = <?xml version="1.0" encoding="utf-8"?>
<root><![CDATA[抱歉,驗證碼填寫錯誤<script type="text/javascript" reload="1">if(typeof errorhandle_login=='function') {errorhandle_login('抱歉,驗證碼填寫錯誤', {});}</script>]]></root>
  • 用戶名或密碼錯誤的結果:
update = 72482
輸入驗證碼
>CPWK
開始模擬登錄百度雲俱樂部
statusCode = 200
text = <?xml version="1.0" encoding="utf-8"?>
<root><![CDATA[登錄失敗,您還可以嘗試 4 次<script type="text/javascript" reload="1">if(typeof errorhandle_login=='function') {errorhandle_login('登錄失敗,您還可以嘗試 4 次', {'loginperm':'4'});}</script>]]></root>

4. 重點說明

  • 在上面刷新驗證碼時,有個非常關鍵的地方,就是使用session,而不是requests,但是具體的原因是什麼呢?
    • 首先我們需要知道session,一次session就是一次會話,如果一開始我通過session訪問一個網站,後面再拿這個session再去請求這個網站,它實際上會把這個網站帶給我們的cookie,或者說網站放到header裏面的session等信息,完全的給我們帶過去,這裏面的cookie非常重要,因爲在訪問百度雲俱樂部的時候,不管我們有沒有登錄,網站都會在我們的header裏面放一些值,這個cookie裏面就包含了很多值,包括我們登錄,請求驗證碼等操作要用到的一些參數,比如說L3em_2132_sid。
    • 我們需要明白的是爲什麼瀏覽器能實現用戶登錄,就是在我們訪問百度雲俱樂部的時候,服務器給我們放了一些值,存在瀏覽器中(cookie中),這個時候我們再去請求驗證碼圖片,會原模原樣的把這些值帶給服務器,這個圖片請求url,在後臺裏面會跟這些信息做一個關聯,那新的驗證碼圖片就會自動和cookie中的信息關聯起來。
    • 如果我們不用session去請求的話,我們拿request去請求時,實際上會單獨再建立一個連接(session的會話),這兩個session之間,cookie值是不一樣的,這樣這些參數就和新拿到的驗證碼圖片就不匹配了。
    • 所以只有session能保證登錄成功。
發佈了73 篇原創文章 · 獲贊 244 · 訪問量 69萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章