研究思路
如果阅读此文章的小伙伴之前研究过贴吧相关的接口的话,就会知道,那些需要登录才能实现的功能接口,你去爬虫的话要携带一个Cookie BDUSS,有了它就会通过身份验证。这个BDUSS也可以通过浏览器访问贴吧页面,从而获得,但在同一个浏览器中如果更换帐号登录,那么你从这个浏览器中拿到的之前那个BDUSS就会失效(不可能这个浏览器永远挂着这个帐号,并且保证不会去修改密码)。为了获得一个永不失效的BDUSS,我们可以通过登录方法来获取BDUSS,但我们不可能会使用这个BDUSS去做退出和切换帐号操作,所以它一般拿到后就不会失效(除非改了密码)。
探索过程
-
不管是贴吧首页,还是其他地方的贴吧登录页面,都会有这个二维码登录的地方。我们第一步是看怎么去拿到这个二维码图片地址。
-
思路:爬取这个页面,从页面元素中找到这个二维码连接,结果就是,二维码连接不是响应页面加载出来的,那就说明是通过后续的接口返回的然后添加到页面中的。所以我们接下来要去寻找加载二维码的接口。
-
寻找接口:打开F12,切换到Network,刷新页面,你会看到一条类型为png的记录,点开看到响应正是这张二维码图片
-
查看请求相关内容:地址,参数,类型。Get请求,有三个参数分别是sign,lp和qrloginfrom,看到后两个参数的值都是pc。盲猜应该可以是固定值pc(电脑端)。所以现在就只剩下一个参数需要搞清楚,那就是sign。
-
遵循代码从上往下运行的规则,这个sign肯定是在二维码加载前就获取到了,所以我们要在它的上面的记录里寻找一下。经过一番寻找,最终在“https://passport.baidu.com/v2/api/getqrcode?lp=pc&qrloginfrom=pc&gid=1527EF2-1333-475F-BA75-319AF40E53E7&callback=tangram_guid_1611304323880&apiver=v3&tt=1611304324028&tpl=tb&_=1611304324032”的响应中找到了sign,当然响应中有整个图片链接。
-
不过坑爹啊,这个请求中也有好多个参数。不过看到这个参数,发现有好几个都是无意义的。tt和_我们看长度可以推断出应该是时间戳,经过验证,确实是当时时间的时间戳。所以我们主要需要攻破的就是gid与callback。还是老样子,先找gid,去上面找但是就是找不到。看gid像是那种唯一值,python中叫uuid。既然找不到,那就可能是前端生成的,所以我们可以去找这个请求发起的js。看看它这个参数到底是怎么来的。在F12 Source中按 Ctrl+Shift+F全局搜索“v2/api/getqrcode”。
-
通过搜索我们发现确实是前端生成的随机唯一值。那我们就可以放心大胆的自己去生成了,当然要生成出来的格式一样。现在就只剩下callback了。正当我苦思冥想找不到的时候,真的想破口而出:怎么这么难找啊,tmd烦死了。我后面发现,嗯它后面那一串数字跟下面的时间戳基本一样。好家伙,这都能算一个参数,我是真的服。这样最后一个参数我们也能靠自己生成。这个接口拿下。
-
到现在我们已经实现了,(1)请求返回二维码地址的接口。(2)返回二维码图片的地址。相当于我们做好了获取BDUSS的第一步。(长舒一口气)
-
现在我们要进行第二部,扫码后进行怎样的处理。
扫码登录原理:网页生成二维码后,后面轮询进行一个请求,来返回当前二维码的状态。
当二维码被扫后,访问二维码内容,服务端会进行处理(是否登录,做登录操作)
当然如果一直没有扫码请求,那么就一直循环请求,直到二维码过期为止。
- 查看Network后续内容,发现确实有一个接口在不停的请求。查看参数发现,都是我们玩剩下的,除了channel_id是图片链接中的一个参数换了名字外,其他我们都有办法获得。
- 我们现在要做的就是模拟浏览器进行轮询请求,当请求返回正确登录的响应的时候,发现它返回了
**tangram_guid_1611307815857({"errno":0,"channel_id":"v1_cf81fb2823935db841b5395152de2","channel_v":"{\"status\":0,\"v\":\"3f1a8f93cca689cead144ad405b03945\",\"u\":\"\"}"}) **
这跟登录有毛关系啊,发现后面它又调用了一个借口,看这名字就知道了,就是它了。 - /v3/login/main/qrbdusslogin
- 发现了联系,bduss是刚才轮询接口返回的。其他参数看着无意义的都直接用它的。先调用再说。如果调用成功,返回的是一个JSON数据,但是并没有BDUSS。tmd,都忘了我们去拿BDUSS的是去哪拿的吗。恍然大悟,Cookie啊。果然,从响应中,看到写入浏览器了Cookie。
- 至此是通过二维码登录拿到BDUSS的全部过程。虽然看上去简单,但是实际去操作的时候,还是有一些坑的。回过头来看看,那些坑也是必经之路吧,坑走过一遍后,就更加印象深刻。
思路总结
总结一下: 基本上是从人的思考方式下手。从二维码从哪来开始,其实也就是一直不断的在调接口,找参数,处理响应。不管哪一步,基本都离不开这三样。当然这次的最重要的是理解二维码登录的实际原理,只有理解了它的原理才能去做好对策。
代码实现
所需技术
python request模块,json模块,re模块,time, uuid
代码
"""
QrCodeLogin.py 二维码登录
"""
"""
1、访问首页,获取二维码
2、请求响应接口
3、。。。。
"""
import requests, re, time, json, uuid
class qrcodeLogin:
def __init__(self):
self.get_qrcode_url = "https://passport.baidu.com/v2/api/getqrcode"
self.unicast_url = "https://passport.baidu.com/channel/unicast"
self.login_url = "https://passport.baidu.com/v3/login/main/qrbdusslogin"
self.qrcode = ""
self.gid = str(uuid.uuid4()).upper()
self.callback = ""
self.sign = ""
self.bduss = ""
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0"
}
def get_qrcode(self):
self.callback = "tangram_guid_{}".format(str(int(round(time.time() * 1000))))
parms = {
"lp":"pc",
"qrloginfrom": "pc",
"gid": self.gid,
"callback": self.callback,
"apiver": "v3",
"tt": int(round(time.time() * 1000)),
"tpl": "tb",
"-": int(round(time.time() * 1000))
}
html = requests.get(url=self.get_qrcode_url, params=parms, headers=self.headers, verify=False).content.decode('utf-8', errors='ignore')
# print(html)
p = re.compile('"imgurl":"(.*?)"')
p2 = re.compile('"sign":"(.*?)"')
qrcode = p.findall(html)
self.sign = p2.findall(html)
if len(qrcode) == 0:
return None
else:
print("https://"+qrcode[0].replace("\\", ""))
self.qrcode = "https://"+qrcode[0].replace("\\", "")
def unicast(self):
start = time.time()
errno = 1
status = 1
flag = True
pattern = re.compile('({.*})')
while(True):
end = time.time()
if (end - start) > 300:
break
parms = {
"channel_id": self.sign,
"qrloginfrom": "pc",
"gid": self.gid,
"callback": self.callback,
"apiver": "v3",
"tt": int(round(time.time() * 1000)),
"tpl": "tb",
"-": int(round(time.time() * 1000))
}
jsons = requests.get(url=self.unicast_url, params=parms, headers=self.headers, verify=False)
html = jsons.content.decode('utf-8', errors="ignore").replace('\\', '').replace('"{', "{").replace('}"', "}")
try:
if errno or status:
message = json.loads(re.search(pattern, html).group())
errno = message['errno']
if errno == 0 and flag:
flag = False
elif errno == 0:
status = message['channel_v']['status']
if not errno and not status:
message = json.loads(re.search(pattern, html).group())
self.bduss = message['channel_v']['v']
BDUSS = self.login()
print(BDUSS)
return BDUSS
except Exception as e:
print(e)
break
return None
def login(self):
parm = {
'v': int(round(time.time() * 1000)),
'u': 'https://tieba.baidu.com/index.html',
'bduss': self.bduss,
'loginVersion': 'v4',
'qrcode': '1',
'tpl': 'tb',
'apiver': 'v3',
'tt': int(round(time.time() * 1000)),
'traceid': None,
'time': round(time.time()),
'alg': 'v3',
'sig': 'EsdgfadNMU5rQVl4NWhSDFSDCVSDGFSdfsdfsf8xcVEveFNqanFGK2ZRdDBiejdXQXVhK1ZlRDZKMzsdfDSES==',
'elapsed': 3,
'shaOne': '00edf2343d07csdf23478b6ccd34f9a92290d3tg',
'callback': 'bd__cbs__o5224l',
}
login_r = requests.get(self.login_url, headers=self.headers, params=parm, verify=False)
str_html = login_r.content.decode('utf-8')
# print(str_html)
for i in login_r.cookies:
if i.name == "BDUSS":
print('登陆成功')
pname = re.compile('"displayName": "(.*?)"')
name_list = pname.findall(str_html)
# print(name_list)
if len(name_list) > 0:
name = name_list[0]
return (i.value, name)
return (i.value, '')
return None
def getImg(self):
img = requests.get(url=self.qrcode, headers=self.headers, verify=False)
# print(img.content)
# with open("tieba.gif", 'wb') as w:
# w.write(img.content)
return img.content
if __name__ == '__main__':
s = qrcodeLogin()
s.get_qrcode()
s.getImg()
s.unicast()
总结
这一步是打开贴吧自动化大门的关键,有了它我们就能轻松做很多事情了。
贴上自己做的一些统计工具的截图,如果有小伙伴感兴趣,或者需要帮做小功能的,可以评论私信哦。
附上下载地址:地址