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" onsubmit="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能保证登录成功。

猜你喜欢

转载自blog.csdn.net/zwq912318834/article/details/79665863