import os import argparse from playwright.sync_api import sync_playwright def save_login_state(auth_file): with sync_playwright() as p: os.makedirs(os.path.dirname(auth_file), exist_ok=True) # 1. 启动浏览器 # headless=False 表示显示浏览器窗口,方便用户扫码登录 browser = p.chromium.launch(headless=False) # 2. 创建浏览器上下文(Context) # Context 类似于一个独立的浏览器会话,包含 cookies、localStorage 等 context = browser.new_context() # 3. 创建新页面(Tab页) page = context.new_page() # ---------- 核心:设置网络请求监听 ---------- # 标记是否已登录成功(用于跨函数修改) login_success = False def on_response(response): """ 监听所有网络响应的回调函数 每当浏览器收到任何 HTTP 响应时,都会调用这个函数 Args: response: Playwright 的 Response 对象,包含响应的所有信息 """ # 使用 nonlocal 声明,允许修改外层函数的 login_success 变量 nonlocal login_success # 检查响应URL是否包含登录相关的关键词 # response.url 是请求的完整地址,如 "https://mail.qq.com/cgi-bin/login" if 'login' in response.url or 'auth' in response.url: # response.status 是 HTTP 状态码,200 表示成功 # 其他常见状态码:301(重定向)、302(临时重定向)、400(错误请求)、401(未授权)、403(禁止)、404(未找到)、500(服务器错误) if response.status == 200: print(f"[网络监听] 检测到登录API响应成功") print(f" - 请求地址: {response.url}") print(f" - 状态码: {response.status}") login_success = True # 将回调函数注册到 page 对象 # 每当有网络响应时,Playwright 会自动调用 on_response 函数 page.on('response', on_response) # 其他可用的事件:'request'(请求发送时)、'requestfailed'(请求失败时)、'requestfinished'(请求完成时) # ---------- 可选:监听 URL 变化 ---------- def on_url_change(frame): """ 监听页面导航(URL变化)的回调函数 每当页面跳转或刷新时都会调用 Args: frame: 页面框架对象 """ nonlocal login_success # 获取当前页面的完整URL current_url = page.url # 检查URL是否已进入邮箱系统 # 登录前是 'https://mail.qq.com',登录后通常会变成包含其他路径的URL # 例如:'https://mail.qq.com/cgi-bin/frame_html?sid=xxx' if 'mail' in current_url and current_url != 'https://mail.qq.com': print(f"[URL监听] 检测到页面已跳转到邮箱系统") print(f" - 当前URL: {current_url}") login_success = True # 注册 URL 变化监听器 # 'framenavigated' 事件在页面导航(跳转)时触发 page.on('framenavigated', on_url_change) # 4. 访问QQ邮箱首页 print("正在打开QQ邮箱登录页面...") page.goto('https://mail.qq.com') # goto() 会等待页面加载完成(直到网络空闲) # 5. 提示用户扫码登录 print("=" * 50) print("请使用手机QQ扫码登录") print("系统将自动检测登录状态,无需手动操作") print("等待登录中...") print("=" * 50) # 6. 等待登录成功或超时 try: # wait_for_function 会持续检查 JavaScript 表达式是否为真 # 这里检查两个条件: # 1. 页面是否出现"收件箱"文字(登录成功的典型标志) # 2. 页面是否出现"inbox"文字(英文版) # timeout=300000 表示最多等待 300 秒(5分钟) # 如果5分钟内条件满足,则继续执行;否则抛出异常 page.wait_for_function( 'document.body.innerText.includes("收件箱") || document.body.innerText.includes("inbox")', timeout=300000 # 毫秒单位,300000ms = 300秒 = 5分钟 ) # 如果 wait_for_function 正常返回,说明检测到了"收件箱"文字 print("[文本监听] 检测到'收件箱'文字,登录确认成功!") except Exception as e: # 超时或其他异常会执行这里 print(f"[警告] 未检测到'收件箱'文字,可能页面结构有变化或登录超时") print(f" 错误信息: {e}") # 7. 检查是否通过其他方式检测到登录成功 if login_success: print("\n✅ 已通过多种方式确认登录成功!") else: print("\n⚠️ 未明确检测到登录成功标志,但仍将保存当前状态") print(" 如果登录成功,状态应该是有效的") # 8. 额外等待2秒,确保状态稳定 # 目的:等待所有异步请求完成、cookies 完全写入、页面状态稳定 print("等待2秒,确保状态稳定...") page.wait_for_timeout(2000) # 2000毫秒 = 2秒 # 9. 保存登录状态(cookies、localStorage、sessionStorage 等) # storage_state 会保存当前 context 的所有存储数据 # 保存后,下次可以直接用 storage_state 参数加载,无需重新登录 context.storage_state(path=auth_file) print(f"✅ 登录状态已保存到: {auth_file}") # 10. 关闭浏览器,释放资源 browser.close() print("浏览器已关闭") def crawl_with_saved_state(auth_file): with sync_playwright() as p: # 加载之前保存的登录状态 browser = p.chromium.launch(headless=False) # 可以无头模式了 context = browser.new_context(storage_state=auth_file) page = context.new_page() # 直接访问需要登录的页面 page.goto('https://mail.qq.com') # 现在已经是登录状态了 print("当前URL:", page.url) # 获取页面内容 content = page.content() print("页面长度:", len(content)) # 可以使用 BeautifulSoup 解析 # from bs4 import BeautifulSoup # soup = BeautifulSoup(content, 'html.parser') page.wait_for_timeout(5000) browser.close() if __name__ == "__main__": parser = argparse.ArgumentParser(description='爬虫脚本') parser.add_argument('account', help='用户') # parser.add_argument('-p', '--password', help='密码(可选)') args = parser.parse_args() account = args.account print(f"用户名{account}") if not account: print("请输入用户名") exit(1) auth_file_path = "./auth/mail" file_path = f"{auth_file_path}/{account}.json" # 判断文件是否存在 if os.path.exists(file_path): crawl_with_saved_state(file_path) else: save_login_state(file_path)