环境
- 操作系统:windows 10 x64
- 集成环境:Visual Studio Code
- Python版本:v3.10.5 64位
– 解码需要os、sqlite3、win32crypt、base64、cryptography、json
库
– 访问网页需要urllib、http
库
一、说明
这篇博文是一篇解题思路的记述文章,代码也是按照解决问题的思路一路写下去的,所以基本不存在函数、类等可以反复调用的对象,甚至其中还存在一些手动的动作。也即意味着如果你顺利的理解了这个思路,可以通过自己的修改让代码看起来更加的优雅整洁。
ps:为了保证代码环境的纯净,建议在实施本博文之前,先清除百度在Chrome中的所有cookie。
二、原理简述
浏览器是利用cookie实现免密码登录的。如果想实现通过python对百度的登录,需要先在浏览器中进行一次登录,然后获取cookie文件,最后基于获取的文件进行百度其他产品的访问。
三、步骤
1、通过Chrome访问百度,获取Cookie
用户首先需要在Chrome上正式访问一次百度的网页,如百度搜索首页www.baidu.com。如果未登录,需要登录成功一次以产生cookies。
Chrome浏览器(version≥103)的cookie是以SQlite库的形式存储的。在Windows文件资源管理器的地址栏中键入路径%localAppData%\Google\Chrome\User Data\Default\network\
,可以很容易的找到这个名为Cookies的SQlite文件。所以它的完整路径是:
%localAppData%\Google\Chrome\User Data\Default\network\Cookies
因为Cookies文件的本质是一个SQlite库文件,所以可以通过Navicat等数据库管理软件直接打开它。此库包含两张表:
- cookies
- meta
其中cookies表存储的就是与Cookie相关的数据。需要关注如下几个字段:
creation_utc(创建时间) | host_key(域名) | name(变量名) | encrypted_value(变量值) | path(路径) |
13302687838867670 | .baidu.com | BAIDUID | v10J�����q��Kp¶�3)Z���&1m� | / |
其中host_key
存储的是网页域名,可以被用于识别cookie的归属对象。name
就是变量名;encrypted_value
就是变量值。它是一个加密字段。其密钥存储在Local State
文件中,路径为:
%localAppData%\Google\Chrome\User Data\Local State
Local State
本质上是一个Json文件。接下来将尝试通过这个文件来解密变量值。
2、利用Python解密变量
2.1准备工作
将一些常量载入内容
import os
import sqlite3
import win32crypt
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import json
import time
'''
关于下面这个字典的来历:它出现在这里可能会有些跳跃,有些不合逻辑,因为这个是我根据结果倒推的。
里面的key是域名,value是cookie文件里面需要的参数。他们表示的含义我没研究出来。
可以先忽略这个字典,后面你就知道为啥存在它了。
'''
BAIDU_BOOL = {
'.baidu.com': ['TRUE', 'FALSE'],
'.passport.baidu.com': ['TRUE', 'TRUE'],
'passport.baidu.com': ['FALSE', 'FALSE'],
'www.baidu.com': ['FALSE', 'FALSE'],
'.hm.baidu.com': ['TRUE', 'FALSE'],
'.www.baidu.com': ['TRUE', 'FALSE'],
'.wenku.baidu.com': ['TRUE', 'TRUE'],
'.miao.baidu.com': ['TRUE', 'FALSE'],
'wenku.baidu.com': ['FALSE', 'TRUE'],
'.pan.baidu.com': ['FALSE', 'FALSE'],
'pan.baidu.com': ['FALSE', 'FALSE'],
'.jingyan.baidu.com': ['FALSE', 'FALSE'],
'.baike.baidu.com': ['FALSE', 'FALSE'],
'baike.baidu.com': ['FALSE', 'FALSE'],
'.b2b.baidu.com': ['FALSE', 'FALSE'],
'.tongji.baidu.com': ['FALSE', 'FALSE'],
'.bce.baidu.com': ['FALSE', 'FALSE'],
'.developer.baidu.com': ['FALSE', 'FALSE'],
'.lewan.baidu.com': ['FALSE', 'FALSE'],
'.haokan.baidu.com': ['FALSE', 'FALSE'],
'.tieba.baidu.com': ['FALSE', 'FALSE'],
'.live.baidu.com': ['FALSE', 'FALSE'],
'.zhidao.baidu.com': ['FALSE', 'FALSE']
}
# 存储cookie、密钥的文件路径
CHROME_COOKIE_PATH = r'\Google\Chrome\User Data\Default\network\Cookies'
CHROME_LOCALSTATE_PATH = r'\Google\Chrome\User Data\Local State'
# cookie文件头,最后一步手工生成cookie会用得到
COOKIE_HEADER = """# Netscape HTTP Cookie File\n# http://curl.haxx.se/rfc/cookie_spec.html\n# This is a generated file! Do not edit.\n\n"""
2.2 获取Cookie、LocalState两个文件的完整路径
path = os.environ['LOCALAPPDATA']
dbCookies = path + CHROME_COOKIE_PATH # cookie文件,本质是一个sqlite数据库
fileLocalState = path + CHROME_LOCALSTATE_PATH # 存储密钥的文件,本质是一个Json文件
2.3 将Cookies读入valCookies变量
conn = sqlite3.connect(dbCookies)
cur = conn.cursor()
sql = """select creation_utc,host_key,name,encrypted_value,path from cookies where host_key like '%baidu%'"""
cur.execute(sql)
valCookies = cur.fetchall()
cur.close()
conn.close()
2.4 将LocalState存储的密钥读入key变量
with open(fileLocalState, 'r', encoding='utf-8') as f:
jsonStr = json.load(f)
encryptedKey = jsonStr['os_crypt']['encrypted_key']
encrypted_key_with_header = base64.b64decode(encryptedKey)
encrypted_key = encrypted_key_with_header[5:]
key = win32crypt.CryptUnprotectData(encrypted_key, None, None, None, 0)[1]
2.5 对2.3
中valCookies列表中的[encrypted_value]字段进行解密
# 1、预解密
valCookieOut = [] # 这个用来存储解密后的cookie
# 2、解密并输入明文列表
for valCookieGet in valCookies:
# 将valCookies的每个元素赋值给变量。这里重点关注valEncrypted,需要解密的就是它
creation_utc, host_key, name, valEncrypted, path = valCookieGet
# 根据百度的结果,valEncrypted的构成分为三部分,都是固定长度
top = valEncrypted[:3] # ->这个切片没啥用,也可以删掉
nonce = valEncrypted[3:15]
cipherbytes = valEncrypted[15:]
# 百度的解密过程
aesgcm = AESGCM(key)
plainbytes = aesgcm.decrypt(nonce, cipherbytes, None)
value = plainbytes.decode('utf-8') # ->这个就是解密之后的值
# 将解密的文件存入列表
valCookieOut.append(tuple((creation_utc, host_key, name, value, path)))
3、基于解密的列表生成cookie
'''
cookie文件正文的结构是[host_key] [TRUE|FALSE] [path] [TRUE|FALSE] [creation_utc] [name] [value]
中间用制表符\t分隔
creation_utc那个实际上是存储有效时间戳的,用creation_utc也没啥影响,为了偷懒这里就用它占位了
文件的存储我是放在平行目录下的log文件夹,文件名加上了时间戳
'''
with open('../log/BaiduCookie'+str(int(time.time())), 'w') as f:
# 写入头
f.write(COOKIE_HEADER)
# 写入体
for valCookie in valCookieOut:
# 将valCookie的每个元素赋值给变量。
creation_utc, host_key, name, value, path = valCookie
cookieContent = str(host_key) + '\t' + str(BAIDU_BOOL[host_key][0]) + '\t' + str(path) + '\t' + \
str(BAIDU_BOOL[host_key][1]) + '\t' + str(creation_utc) + \
'\t' + str(name) + '\t' + str(value) + '\n'
f.write(cookieContent)
4、cookie预览
如果一切顺利,在做完上面所有步骤后,将会获得一个编辑好的cookie文件。它大致的内容如下:
头
# Netscape HTTP Cookie File # http://curl.haxx.se/rfc/cookie_spec.html # This is a generated file! Do not edit.
体
.passport.baidu.com TRUE / TRUE 13302687843561289 PTOKEN 6842356784ffb2db1236574567d8ee1d3 .baidu.com TRUE / FALSE 13302687839790384 BIDUPSID AG43E548AF7F091F824110AC4ABCD9DA5 .passport.baidu.com TRUE / TRUE 13302687843561303 HISTORY ffb2db1236574567d8ee1d3affd3 ... ...
5、访问网页
获取cookie后,下面的事情就简单了。首先载入cookie文件内容,其次携带cookie模拟浏览器登录百度,然后执行你所需要的操作。
5.1 准备工作
包括定义好headers、载入cookie、定义好需要访问的网址
from urllib.parse import *
from urllib.request import *
from http.cookiejar import *
import time
url = 'https://www.baidu.com'
# 这里是采集了我电脑上chrome的header,具体可以换成自己的
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'}
# cookie文件偷懒了,直接复制了想要的文件名。这里可以写个方法读取最新时间戳对应的cookie
cookie_jar = MozillaCookieJar('../log/BaiduCookie1658281995')
cookie_jar.load(ignore_discard=True, ignore_expires=True)
5.2 访问工作
cookie_processor = HTTPCookieProcessor(cookie_jar)
opener = build_opener(cookie_processor)
request = Request(url, headers=headers)
response = opener.open(request)
data = response.read().decode('utf8')
# 做你想做的事情
5.3 存储新的cookie
访问新的网页意味着会产生新的cookie,所以要及时存储这些cookie
cookie_jar.save('../log/BaiduCookie'+str(int(time.time())),ignore_discard=True, ignore_expires=True)
以上就是一个从0开始利用python登录百度账户的过程。
要注意cookie作为客户端与服务端通讯的密钥,其具有时效性。如果长时间不模拟登录,cookie就会失效。到时候需要重新将这个过程来过一次。