You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Selenium手动注入WhatsApp Web会话以保持登录?

问题:手动保存/恢复WhatsApp Web会话数据,避免重复扫码登录

我正在开发基于Selenium的Python工具,用于WhatsApp Web收发消息,希望避免每次运行都扫码登录。

已知WhatsApp Web的会话数据存储在名为wawc的IndexedDB,并同步到localStorage。我编写了extract_session.js和inject_session.js分别提取、注入会话数据,通过Chrome DevTools看注入成功,但刷新页面后会跳转到登出/扫码界面,IndexedDB数据被清空。

我知道用ChromeOptions的user-data-dir可以解决,但更倾向于手动仅保存WhatsApp Web的用户数据。尝试过在页面加载前用execute_cdp_cmd设置IndexedDB和LocalStorage,依然出现登出页面,推测遗漏了其他存储位置的会话数据。

附相关代码:

extract_session.js

function requestPromise(request) {
    return new Promise((resolve, reject) => {
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

async function openDatabase(dbName) {
    const request = indexedDB.open(dbName);
    return requestPromise(request);
}

async function getAllFromStore(db, storeName) {
    const transaction = db.transaction(storeName, "readonly");
    const objectStore = transaction.objectStore(storeName);
    return requestPromise(objectStore.getAll());
}

try {
    const db = await openDatabase(arguments[0]);
    const data = await getAllFromStore(db, arguments[1]);
    return JSON.stringify(data);
} catch (error) {
    console.log("Failed to extract session:", error)
    return null;
}

inject_session.js

function requestPromise(request) {
    return new Promise((resolve, reject) => {
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

async function openDatabase(dbName) {
    const request = indexedDB.open(dbName);
    return requestPromise(request);
}

async function putInStore(db, storeName, data) {
    const transaction = db.transaction(storeName, "readwrite");
    const objectStore = transaction.objectStore(storeName);
    for (const item of data) {
        const request = objectStore.put(item);
        await requestPromise(request);
    }
}

try {
    const session = JSON.parse(arguments[2]);
    const db = await openDatabase(arguments[0]);
    await putInStore(db, arguments[1], session);
    localStorage.clear()
    for (const item of session) {
        localStorage.setItem(item.key, item.value);
    }
} catch (error) {
    console.error("Failed to inject session:", error);
}

Python复现代码

from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By

import os

def _handle_await(parent, locator, timeout, poll_freq):
    try:
        return WebDriverWait(parent,
                             timeout,
                             poll_frequency=poll_freq
                             ).until(EC.presence_of_element_located(locator))
    except TimeoutException:
        return None

def await_element(parent, locator, timeout=0, poll_freq=0.05):

    if timeout == 0:
        while True:
            res = _handle_await(parent, locator, timeout, poll_freq)
            if res is not None:
                return res
    else:
        return _handle_await(parent, locator, timeout, poll_freq)

QR_CODE = (By.CSS_SELECTOR, "canvas[role='img']")
LOGGED_IN = (By.CSS_SELECTOR, "div[title='Chats']")

options = ChromeOptions()

driver = Chrome(options=options)
driver.get("https://web.whatsapp.com/")

await_element(driver, QR_CODE)

if os.path.isfile("session.wa"):

    js_code = open("inject_session.js", "r").read()
    with open("session.wa", "r", encoding="utf-8") as file:
        driver.execute_script(js_code, "wawc", "user", file.read())

    driver.refresh()
    input()

else:

    await_element(driver, LOGGED_IN)

    js_code = open("extract_session.js", "r").read()
    session_data = driver.execute_script(js_code, "wawc", "user")

    with open("session.wa", "w", encoding="utf-8") as file:
        file.write(str(session_data))

    input()

问题现象:首次运行生成session.wa,再次运行注入数据后刷新,出现登出界面并跳转至扫码页。


问题分析与解决方案

1. 核心问题:遗漏关键存储位置

WhatsApp Web的会话验证不仅依赖IndexedDB和localStorage,还需要:

  • Cookie:包含会话标识的核心验证Cookie(如wa_session系列)
  • Service Worker缓存:存储会话相关的资源与状态,用于快速验证

2. 修正后的提取/注入逻辑

完善extract_session.js(新增Cookie和缓存提取)

async function requestPromise(request) {
    return new Promise((resolve, reject) => {
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

async function openDatabase(dbName) {
    const request = indexedDB.open(dbName);
    return requestPromise(request);
}

async function getAllFromStore(db, storeName) {
    const transaction = db.transaction(storeName, "readonly");
    const objectStore = transaction.objectStore(storeName);
    return requestPromise(objectStore.getAll());
}

// 提取Cookie
function getCookies() {
    return document.cookie.split(';').map(cookie => {
        const [key, ...valueParts] = cookie.trim().split('=');
        return {key, value: valueParts.join('=')};
    });
}

// 提取Service Worker缓存
async function getServiceWorkerCache() {
    if (!caches) return [];
    const cacheNames = await caches.keys();
    const cacheData = [];
    for (const name of cacheNames) {
        const cache = await caches.open(name);
        const keys = await cache.keys();
        for (const key of keys) {
            const response = await cache.match(key);
            cacheData.push({
                cacheName: name,
                url: key.url,
                response: await response.text()
            });
        }
    }
    return cacheData;
}

try {
    const db = await openDatabase(arguments[0]);
    const indexedDbData = await getAllFromStore(db, arguments[1]);
    const cookies = getCookies();
    const cacheData = await getServiceWorkerCache();
    
    return JSON.stringify({
        indexedDb: indexedDbData,
        localStorage: Object.fromEntries(Object.entries(localStorage)),
        cookies: cookies,
        cache: cacheData
    });
} catch (error) {
    console.log("Failed to extract session:", error)
    return null;
}

完善inject_session.js(新增Cookie和缓存注入)

async function requestPromise(request) {
    return new Promise((resolve, reject) => {
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

async function openDatabase(dbName) {
    const request = indexedDB.open(dbName);
    return requestPromise(request);
}

async function putInStore(db, storeName, data) {
    const transaction = db.transaction(storeName, "readwrite");
    const objectStore = transaction.objectStore(storeName);
    // 先清空原有数据避免冲突
    await requestPromise(objectStore.clear());
    for (const item of data) {
        const request = objectStore.put(item);
        await requestPromise(request);
    }
}

// 注入Cookie
function setCookies(cookies) {
    cookies.forEach(cookie => {
        document.cookie = `${cookie.key}=${cookie.value}; path=/; domain=.web.whatsapp.com`;
    });
}

// 注入Service Worker缓存
async function setServiceWorkerCache(cacheData) {
    if (!caches) return;
    for (const entry of cacheData) {
        const cache = await caches.open(entry.cacheName);
        await cache.put(new Request(entry.url), new Response(entry.response));
    }
}

try {
    const session = JSON.parse(arguments[2]);
    
    // 注入IndexedDB
    const db = await openDatabase(arguments[0]);
    await putInStore(db, arguments[1], session.indexedDb);
    
    // 注入localStorage
    localStorage.clear();
    Object.entries(session.localStorage).forEach(([key, value]) => {
        localStorage.setItem(key, value);
    });
    
    // 注入Cookie
    setCookies(session.cookies);
    
    // 注入Service Worker缓存
    await setServiceWorkerCache(session.cache);
    
} catch (error) {
    console.error("Failed to inject session:", error);
}

3. 修正Python代码(调整注入时机)

必须在打开WhatsApp Web前完成注入,否则页面会提前执行验证逻辑覆盖数据:

from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By

import os
import json

def _handle_await(parent, locator, timeout, poll_freq):
    try:
        return WebDriverWait(parent,
                             timeout,
                             poll_frequency=poll_freq
                             ).until(EC.presence_of_element_located(locator))
    except TimeoutException:
        return None

def await_element(parent, locator, timeout=0, poll_freq=0.05):
    if timeout == 0:
        while True:
            res = _handle_await(parent, locator, timeout, poll_freq)
            if res is not None:
                return res
    else:
        return _handle_await(parent, locator, timeout, poll_freq)

QR_CODE = (By.CSS_SELECTOR, "canvas[role='img']")
LOGGED_IN = (By.CSS_SELECTOR, "div[title='Chats']")

options = ChromeOptions()
# 禁用Service Worker缓存自动清理
options.add_argument("--disable-service-worker-cache")

driver = Chrome(options=options)

if os.path.isfile("session.wa"):
    # 先打开空白页,避免WhatsApp提前加载验证
    driver.get("about:blank")
    
    # 注入会话数据
    js_code = open("inject_session.js", "r").read()
    with open("session.wa", "r", encoding="utf-8") as file:
        session_data = json.load(file)
        driver.execute_script(js_code, "wawc", "user", json.dumps(session_data))
    
    # 打开WhatsApp Web
    driver.get("https://web.whatsapp.com/")
    # 等待登录状态
    await_element(driver, LOGGED_IN, timeout=10)
    print("会话恢复成功")
else:
    driver.get("https://web.whatsapp.com/")
    await_element(driver, LOGGED_IN)
    
    # 提取会话数据
    js_code = open("extract_session.js", "r").read()
    session_data = driver.execute_script(js_code, "wawc", "user")
    
    with open("session.wa", "w", encoding="utf-8") as file:
        file.write(session_data)
    
    print("会话已保存")

input("按任意键退出...")
driver.quit()

4. 关键注意事项

  • 注入时机:必须在打开WhatsApp Web前完成所有数据注入
  • Cookie域名:确保Cookie设置的域名为.web.whatsapp.com,避免作用域错误
  • IndexedDB清空:注入前清空原有数据,避免新旧数据冲突
  • Service Worker:部分版本的WhatsApp Web依赖其缓存,建议保留相关逻辑

内容的提问来源于stack exchange,提问作者akrentz6

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 15:02:06