- 操作系统:windows 10 x64
- 集成环境:Visual Studio Code
- Python版本:v3.10.5 64位
-----访问网页需要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两个文件的完整路径
- 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.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、访问网页
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就会失效。到时候需要重新将这个过程来过一次。