https://weibo.com
根据上面的手动操作流程,我们要分析出网站的http请求逻辑。
首先,打开谷歌浏览器开发者调试工具,查看在请求首页面时,请求回的响应是否包含cookie
,也即是看首页面的响应头中是否包含set-cookie
。如果包含,那么这个请求是登录过程中必须的。经过查看,发现在首页面的响应头中,包含set-cookie
,这个请求是登录的第一个请求。
根据经验,在请求页面的html
之后,还会去请求页面中的js
,css
,图片等信息。css
,图片信息我们可以排除,一般情况,不会是登录的必须请求。一种常见的手法是,将后续请求需要用到的参数放到js中,然后通过js异步请求来完成登录。所以,我们在Network
中过滤出所有的js请求。然后一个请求一个请求的查看,查看请求返回的响应内容,排除一些功能性的js,例如jquery之类,找出可能是参数的js,一般你能看懂的,大概率是参数。没有特别有效的方法,只能一条一条的看,凭借经验,以及url的命名。果然,找到了一条可疑请求,url为:https://login.sina.com.cn/sso/prelogin.php
这是一条jsonp的请求,其中包含了几个关键参数pubkey
,nonce
,rsakv
,这是rsa
加密用的参数,在后面的请求中一定会用到。分析请求参数,发现除了最后有个时间戳解决浏览器缓存之外,其他参数都是固定的。
除了查看js请求外,还应该,查看ajax
请求,调试工具里,也可以过滤出,选择XHR
过滤选项。
经过判断,这两个请求是必要请求的可能性不大。
根据流程,可以先实现代码此部分的代码。
import re
import time
import json
import requests
session = requests.session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36'
})
# 1.请求首页面
session.get('https://weibo.com')
# 2.请求pre_login 获取参数
params = {
'entry': 'weibo',
'callback': 'sinaSSOController.preloginCallBack',
'su': '',
'rsakt': 'mod',
'client': 'ssologin.js(v1.4.19)',
'_': int(time.time())
}
res = session.get(url='https://login.sina.com.cn/sso/prelogin.php', params=params)
data = json.loads(re.findall(r'\((.*?)\)', res.text)[0])
print(data)
先清空调试工具中的请求历史,然后勾选Preserver log
和Disable cache
,保证抓包的正确。
点击登录,根据请求url,以及响应头中是否有有设置cookie,找到第一个设置cookie的请求。请求url为:https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)
分析其请求参数。
可以发送多次请求,然后比较参数,筛选出固定参数。然后去分析非固定参数。
上图红框中是几个比较关键的参数,在其中,没有发现username
,password
的字样,但是nonce
,pwencode
,rsakv
,以及sp
的值都明确指明了参数被js加密了。需要分析页面js,最笨的办法是在查找此条请求之前的所有请求中的js,然后找到处理参数的js。有时候,笨办法是最好的办法。谷歌浏览器提供的开发者调试工具中有个js调试工具,它可以定位页面元组的js事件,然后通过断点的方式,一步一步定位到关键代码处。
退回到点击登录按钮的那一步。点击开发者工具Elements
选项卡,点击左上角的选中按钮
,然后用鼠标左定位页面中的登录按钮。
再点击开发工具窗口中的右边的子窗口,选中Events Listeners
, 找到click
事件,从而定位到js代码。
然后再点击,右边的js代码,会打开调试工具的sources
窗口,它由3个子窗口组成,从左往右依次为,文件目录窗口,js代码窗口和调试窗口。大多数情况,为了减少带宽的消耗,js会被压缩,点击代码窗口左下角的{}
可以格式化js。
点击格式化后,js代码会自动跳到上面click
监听时间执行的入口函数处,它会高亮黄色显示。在对于的行号出用鼠标左键点击,给当前代码处打上断点,行号显示为蓝色箭头,代表打上了断点。
打好断点后,就可以开始调试了,点击登录按钮,js会暂停到刚才断点处,然后使用调试工具,一步一步进行调试,分析js代码,直到找到关键代码。
对于微博登录的案例来说,通过着这种调试方法定位关键代码非常困难,因为代码调用层级太多。有些时候,必须一步一步调试,知道找到关键代码,js调试需要极有耐心。
但是,有一种情况,可以换另外一种方法定位。那就是如果你要找某个请求的触发函数,那么可以直接在networking
选项窗口直接定位。输入错误密码,然后点击登录,请求的Initiator
字段显示了,发送这条请求的发起对象。
点击发起对象字段的js文件名,会跳到发起请求的js处。打上断点,然后进行调试。
当前这个函数e.submit
是发起http请求的函数,这不是分析目标,分析的目标是请求参数的构造。查看当前函数loginByIframe
,发现没有参数构造的步骤。通过函数栈图,找到调用这个函数的函数loginByConfig
,在栈图中点击这个函数,可以跳到loginByIframe
函数调用处,在此处打上断点。
分析js代码,发现登录函数login在196行,打上断点,然后刷新页面,再次点击登录。一步一步开始分析。
调试模式,会实时显示函数中的变量的值,也可以在右边的Scope
选项中查看当前变量。
函数login的,形参a
是用户名,形参b
是密码。经过分析找到如下代码
发现用户名处理为参数su
,处理方式为,先urlencode
再base64encode
。继续往下调试
可以看出urlencode
就是对用户名进行url编码,python中使用如下代码
import base64
from urllib import parse
username = '[email protected]'
res = parse.quote(username)
print(res)
# 输出:
112064149%40qq.com
继续调试,得到username编码后的值
通过python代码实现如下:
import base64
from urllib import parse
username = '[email protected]'
res = parse.quote(username)
res = base64.b64encode(res.encode())
print(res.decode('utf-8'))
# 输出
MTEyMDY0MTQ5JTQwcXEuY29t
仔细观察代码,发现参数entry
,service
,nonce
,pwencode
,rsakv
,以及密码sp
和它的加密方式。
继续按照这样的分析方式可以找到所有剩余参数的构造方法。
继续上面的python代码,实现这一步操作。
# 2.提交登录请求
def get_username(username):
res = parse.quote(username)
res = base64.b64encode(res.encode())
return res.decode('utf-8')
def get_password(pubkey, password):
# publickey = rsa.PublicKey(int(pubkey, 16), int('10001', 16))
# res = rsa.encrypt(password.encode(), publickey)
rsa = Rsa(pubkey=pubkey)
res = rsa.encrypt(password.encode())
return binascii.b2a_hex(res).decode()
t2 = str(int(time.time()*1000))
form_data = {
'entry': 'weibo',
'gateway': '1',
'from': '',
'savestate': '7',
'qrcode_flag': 'false',
'useticket': '1',
'pagerefer': '',
'vsnf': 1,
'su': get_username('[email protected]'),
'service': 'miniblog',
'servertime': data['servertime'],
'nonce': data['nonce'],
'pwencode': 'rsa2',
'rsakv': data['rsakv'],
'sp': get_password(data['pubkey'], str(data['servertime']) + '\t' + data['nonce']+'\n'+'pythonvip123'),
'sr': '1920*1080',
'encoding': 'UTF-8',
'prelt': '49',
'url': 'https://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
'returntype': 'META'
}
login_url = 'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)'
res = session.post(url=login_url, data=form_data)
res.encoding = 'gbk'
print(res.text)
运行结果:
返回的结果是一个html页面,结构比较简单,js代码中有一个location.replace的跳转语句,说明接下来浏览器会发送这个请求。通过正则表达式提取url,然后发送请求。
next_url = re.findall(r'replace\("(.*?)"\)', res.text)[0]
# print(parse.unquote(next_url, encoding='gbk'))
response = session.get(next_url)
response.encoding = 'gbk'
print(response.text)
运行结果:
仍然是一个html页面,分析其中的js,发现一个arrURL的列表,观察其中的url,对照浏览器抓包,发现都有请求,通过代码提取,然后依次请求。最后访问home页面https://weibo.com/?wvr=5&lf=reg
。调试后发现,arrURL中除了第一个请求必须之外,其他的请求都可以不发送。到此微博登录成功实现。
response = session.get(next_url)
response.encoding = 'gbk'
arrurl = re.findall(r'setCrossDomainUrlList\((.*?)\)', response.text)[0]
arrurl = json.loads(arrurl)['arrURL']
res = session.get(arrurl[0])
res.encoding = 'gbk'
print(res.text)
# res = session.get(arrurl[1])
# res.encoding = 'gbk'
# print(res.text)
# res = session.get(arrurl[2])
# res.encoding = 'gbk'
# print(res.text)
# res = session.get(arrurl[3])
# res.encoding = 'gbk'
# print(res.text)
res = session.get('https://weibo.com/?wvr=5&lf=reg')
print(res.url)
print(res.text)