From 8db81de42a91480bb379a4b0ee6600c01c0facfe Mon Sep 17 00:00:00 2001 From: bwstudio Date: Sat, 18 Oct 2025 22:26:14 +0800 Subject: [PATCH] home1019 --- README.md | 4 +- config.json | 206 +++-------------- config.py | 100 ++++++-- login_manager.py | 581 ++++++++++++++++++++++++++++------------------- main.py | 100 ++------ 开发文档.md | 14 +- 6 files changed, 495 insertions(+), 510 deletions(-) diff --git a/README.md b/README.md index 9dea2c1..bfa5989 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,12 @@ autoLogon/ ```json { + "duration": 30, "browser_path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", "sites": [ { "name": "网站名称", "url": "网站URL", - "duration": 30, "cookies": [ { "name": "cookie名称", @@ -105,7 +105,7 @@ python main.py ## 注意事项 - 确保浏览器路径正确配置,默认使用Edge浏览器 -- 配置文件中的`duration`参数表示网站打开后保持的时间(秒) +- 配置文件中的`duration`参数表示处理每个网站时的延时时间(秒),默认30秒 - 首次运行时,应用会为没有cookies的网站创建空的cookies配置 - 日志文件保存在`log`目录下,按日期命名 diff --git a/config.json b/config.json index 1ff9d15..70e7f78 100644 --- a/config.json +++ b/config.json @@ -1,86 +1,14 @@ { - "browser_path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "duration": 30, + "AutoQuit": true, "sites": [ { "name": "百度", "url": "https://www.baidu.com", - "duration": 30, "cookies": [ - { - "name": "BD_HOME", - "value": "1", - "domain": "www.baidu.com", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "BA_HECTOR", - "value": "20252k858l240g858hag848g2kcg811kf6cpe25", - "domain": ".baidu.com", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "PAD_BROWSER", - "value": "1", - "domain": "www.baidu.com", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "BIDUPSID", - "value": "118F20D8D3C3135F00559E5F3510FBB5", - "domain": ".baidu.com", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "BD_UPN", - "value": "12314753", - "domain": "www.baidu.com", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "PSTM", - "value": "1760768814", - "domain": ".baidu.com", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "ZFY", - "value": "R:BWN9Jj8RMAPiaPxY4bTL7jFZJ:AvWzWoS2wj:BdtPdNI:C", - "domain": ".baidu.com", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "H_PS_PSSID", - "value": "60275_64980_65216_65249_65311_65422_65456_65571_65596_65619_65598_65635_65664_65665_65682_65711_65733_65392_65759_65774_65791_65838_65866", - "domain": ".baidu.com", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, { "name": "BAIDUID_BFESS", - "value": "16F9B2349AAE73EDA898BA50E44AE46D:FG=1", + "value": "07286BA0A030422D44083F3B4F2DAB27:FG=1", "domain": ".baidu.com", "path": "/", "expires": 0, @@ -92,92 +20,19 @@ { "name": "新浪", "url": "https://www.sina.com.cn", - "duration": 30, "cookies": [ + { + "name": "BAIDUID_BFESS", + "value": "07286BA0A030422D44083F3B4F2DAB27:FG=1", + "domain": ".baidu.com", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + }, { "name": "Hm_lpvt_90c40f528e0b2106bc03da5aadec190f", - "value": "1760771854", - "domain": ".www.sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "NowDate", - "value": "Sat Oct 18 2025 15:17:33 GMT+0800 (中国标准时间)", - "domain": "www.sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "name", - "value": "sinaAds", - "domain": "www.sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "post", - "value": "massage", - "domain": "www.sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "Apache", - "value": "120.227.58.197_1760769194.496218", - "domain": ".sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "CLICK_ENHANCER_LAST_ACTIVATION", - "value": "1", - "domain": "www.sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "ULV", - "value": "1760769329201:4:4:4:120.227.58.197_1760769194.496218:1760769193685", - "domain": ".sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "UOR", - "value": "192.168.100.100,www.sina.com.cn,", - "domain": ".sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "SINAGLOBAL", - "value": "120.227.58.197_1760768783.259381", - "domain": ".sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false - }, - { - "name": "HMACCOUNT", - "value": "84FEB82BA2589EE5", + "value": "1760797352", "domain": ".www.sina.com.cn", "path": "/", "expires": 0, @@ -186,32 +41,37 @@ }, { "name": "Hm_lvt_90c40f528e0b2106bc03da5aadec190f", - "value": "1760759893,1760768784,1760769195", + "value": "1760794976,1760795033,1760795177,1760795343", "domain": ".www.sina.com.cn", "path": "/", "expires": 0, "secure": false, "httpOnly": false - }, - { - "name": "SGUID", - "value": "1760768783361_32567590", - "domain": ".sina.com.cn", - "path": "/", - "expires": 0, - "secure": false, - "httpOnly": false } ] }, - { - "name": "示例网站2", - "url": "http://192.168.100.1/", - "duration": 30, + { + "name": "示例网站5", + "url": "http://192.168.100.100:3000/#/login", + "cookies": [ + { + "name": "MoviePilot", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjA3OTkxNTAsImlhdCI6MTc2MDc5NzM1MCwic3ViIjoiMSIsInVzZXJuYW1lIjoiYWRtaW4iLCJzdXBlcl91c2VyIjp0cnVlLCJsZXZlbCI6MiwicHVycG9zZSI6InJlc291cmNlIn0.hrce7pqwIUTzuYMSe4QXM80tepOvv5dKkDfLL8OuQo4", + "domain": "192.168.100.100", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + } + ] + }, + { + "name": "示例网站6", + "url": "http://192.168.100.1", "cookies": [ { "name": "sysauth_http", - "value": "b05d9c8feea4a5a931e8ef687ac119de", + "value": "788d6c33e71fbe7dd24947876759ac71", "domain": "192.168.100.1", "path": "/", "expires": 0, diff --git a/config.py b/config.py index eff81ad..51da3f8 100644 --- a/config.py +++ b/config.py @@ -17,12 +17,18 @@ class ConfigManager: """ self.config_file = config_file self.default_config = { - 'sites': [] + 'duration': 30, + 'sites': [ + {'name': '百度', 'url': 'https://www.baidu.com'}, + {'name': '新浪', 'url': 'https://www.sina.com.cn'} + ] } + # 在初始化时加载配置并保存到实例属性 + self._config = self._load_config_from_file() - def load_config(self) -> Dict: + def _load_config_from_file(self) -> Dict: """ - 加载配置文件 + 从文件加载配置的内部方法 Returns: dict: 配置数据 @@ -32,23 +38,68 @@ class ConfigManager: with open(self.config_file, 'r', encoding='utf-8') as f: config = json.load(f) logger.info(f"成功加载配置文件: {self.config_file}") + + # 确保配置中包含duration字段 + if 'duration' not in config: + logger.info("配置中没有duration字段,使用默认值30秒") + config['duration'] = self.default_config['duration'] + + # 检查是否存在网站配置,如果不存在则使用默认示例网站并保存 + sites = config.get('sites', []) + if not sites: + logger.warning("配置中没有设置任何网站,使用默认示例网站") + config['sites'] = self.default_config['sites'] + # 保存默认网站到配置中 + try: + self._save_config_to_file(config) + logger.info("已保存默认网站配置") + except Exception as e: + logger.error(f"保存默认网站配置失败: {e}") + return config else: logger.warning(f"配置文件不存在: {self.config_file}") logger.info("使用默认配置") - return self.default_config.copy() + + # 创建并保存默认配置 + default_config = self.default_config.copy() + try: + self._save_config_to_file(default_config) + logger.info("已创建默认配置文件") + except Exception as e: + logger.error(f"保存默认配置文件失败: {e}") + + return default_config except json.JSONDecodeError as e: logger.error(f"配置文件格式错误: {e}") logger.info("使用默认配置") - return self.default_config.copy() + + # 使用默认配置并保存 + default_config = self.default_config.copy() + try: + self._save_config_to_file(default_config) + logger.info("已覆盖错误配置文件") + except Exception as e: + logger.error(f"保存默认配置文件失败: {e}") + + return default_config except Exception as e: logger.error(f"加载配置文件时发生错误: {e}") logger.info("使用默认配置") return self.default_config.copy() - def save_config(self, config: Dict) -> bool: + def load_config(self) -> Dict: """ - 保存配置到文件 + 获取当前配置(从实例属性中返回) + + Returns: + dict: 配置数据 + """ + return self._config.copy() # 返回配置的副本,避免外部直接修改 + + def _save_config_to_file(self, config: Dict) -> bool: + """ + 保存配置到文件的内部方法 Args: config (dict): 要保存的配置数据 @@ -70,6 +121,22 @@ class ConfigManager: logger.error(f"保存配置文件时发生错误: {e}") return False + def save_config(self, config: Dict) -> bool: + """ + 保存配置并更新实例属性 + + Args: + config (dict): 要保存的配置数据 + + Returns: + bool: 保存是否成功 + """ + if self._save_config_to_file(config): + # 保存成功后更新实例属性 + self._config = config + return True + return False + def get_sites(self) -> List[Dict]: """ 获取所有网站配置 @@ -77,10 +144,9 @@ class ConfigManager: Returns: list: 网站配置列表 """ - config = self.load_config() - sites = config.get('sites', []) + sites = self._config.get('sites', []) logger.info(f"获取到 {len(sites)} 个网站配置") - return sites + return sites.copy() # 返回副本以避免直接修改内部数据 def add_site(self, site_config: Dict) -> bool: """ @@ -96,15 +162,15 @@ class ConfigManager: logger.error("网站配置格式错误,必须包含name和url字段") return False - config = self.load_config() - sites = config.get('sites', []) - # 检查是否已存在同名网站 - for site in sites: + for site in self._config.get('sites', []): if site.get('name') == site_config.get('name'): logger.warning(f"网站 '{site_config.get('name')}' 已存在") return False + # 创建配置副本并添加新网站 + config = self._config.copy() + sites = config.get('sites', []) sites.append(site_config) config['sites'] = sites return self.save_config(config) @@ -120,7 +186,8 @@ class ConfigManager: Returns: bool: 更新是否成功 """ - config = self.load_config() + # 创建配置副本 + config = self._config.copy() sites = config.get('sites', []) for i, site in enumerate(sites): @@ -145,7 +212,8 @@ class ConfigManager: Returns: bool: 删除是否成功 """ - config = self.load_config() + # 创建配置副本 + config = self._config.copy() sites = config.get('sites', []) new_sites = [site for site in sites if site.get('name') != site_name] diff --git a/login_manager.py b/login_manager.py index 8096c35..39bd1f0 100644 --- a/login_manager.py +++ b/login_manager.py @@ -1,7 +1,7 @@ import os import time -from typing import Dict, List, Any -from DrissionPage import ChromiumPage, ChromiumOptions +from typing import Dict, List, Any, Optional +from DrissionPage import ChromiumPage, ChromiumOptions, Chromium from logger_config import global_logger as logger from config import ConfigManager @@ -13,287 +13,396 @@ class LoginManager: def __init__(self): """ - 初始化登录管理器 + 初始化登录管理器,创建浏览器对象 """ - self.page = None + self.browser = None self.config_manager = ConfigManager() - - def login(self, site_url: str, cookies: List[Dict[str, Any]] = None) -> bool: - """ - 登录网站 - - Args: - site_url (str): 网站URL - cookies (List[Dict[str, Any]], optional): 预先设置的cookies记录列表 - - Returns: - bool: 登录是否成功 - """ - logger.info(f"开始登录网站: {site_url}") + self.sites = [] # 存储包含网站和页面信息的字典列表 + # 初始化浏览器对象 try: - # 根据DrissionPage 4.x文档,使用ChromiumOptions配置浏览器 - logger.info("正在配置浏览器...") - # 创建浏览器配置对象 co = ChromiumOptions() - # 从配置文件中读取浏览器路径,如果不存在则使用默认路径 + # 从配置文件中读取浏览器路径 config = self.config_manager.load_config() edge_path = config.get('browser_path', r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe') if os.path.exists(edge_path): - # 根据文档,使用set_browser_path方法设置浏览器路径 + # 设置浏览器路径 co.set_browser_path(edge_path) - logger.info(f"已配置Edge浏览器路径: {edge_path}") + logger.info(f"已配置浏览器路径: {edge_path}") else: - logger.warning(f"Edge浏览器路径不存在: {edge_path}") + logger.warning(f"浏览器路径不存在: {edge_path}") logger.info("将使用默认配置,让DrissionPage自动查找浏览器") - # 使用配置创建ChromiumPage实例 - # 根据文档,在DrissionPage 4.x中,应该使用正确的方式初始化 - self.page = ChromiumPage(co) + # 用该配置创建浏览器对象 + self.browser = Chromium(addr_or_opts=co) + logger.info("浏览器对象初始化成功") + except Exception as e: + logger.error(f"初始化浏览器失败: {e}") + self.browser = None + + + + + + + def save_cookies(self) -> List[Dict[str, Any]]: + """ + 保存所有打开网站的cookies到配置文件 + + Returns: + List[Dict[str, Any]]: 包含所有网站cookies的列表 + """ + logger.info("开始保存所有网站的cookies到配置文件") + + all_sites_cookies = [] + + if not self.sites: + logger.warning("没有打开的网站,无法获取cookies") + return all_sites_cookies + + try: + # 加载配置文件 + config = self.config_manager.load_config() - # 访问网站 - logger.info(f"正在加载网站: {site_url}") - self.page.get(site_url) - - # 如果提供了cookies,设置它们 - if cookies: - logger.info(f"设置 {len(cookies)} 条cookies记录") - - # 检查cookies类型,支持列表格式和字典格式 - if isinstance(cookies, dict): - # 兼容旧的字典格式,转换为列表格式 - cookies_list = [] - for key, value in cookies.items(): - cookies_list.append({ - 'name': key, - 'value': value, - 'domain': '', - 'path': '/', - 'expires': 0, - 'secure': False, - 'httpOnly': False + # 遍历所有网站获取cookies + for index, site_info in enumerate(self.sites): + try: + page = site_info['page'] + site_name = site_info.get('name', f'网站_{index+1}') + site_url = getattr(page, 'url', site_info.get('url', f'网站_{index+1}')) + page_title = getattr(page, 'title', f'网站_{index+1}') + logger.info(f"获取网站 {index+1}/{len(self.sites)} 的cookies: {site_name} - {site_url}") + + # 获取页面的cookies + cookies_list = self._get_page_cookies(page, site_name, site_url) + + # 将网站信息和cookies添加到结果列表 + if cookies_list: + all_sites_cookies.append({ + 'url': site_url, + 'title': page_title, + 'cookies': cookies_list }) - cookies = cookies_list - logger.info(f"已将字典格式cookies转换为列表格式") - - # 尝试多种方式设置cookies - try: - # 首先尝试使用set.cookies()方法(DrissionPage 4.x推荐方式) - if hasattr(self.page, 'set') and hasattr(self.page.set, 'cookies'): - if hasattr(self.page.set.cookies, 'clear'): - self.page.set.cookies.clear() - self.page.set.cookies(cookies) - logger.info(f"成功使用set.cookies()方法设置cookies") - # 备选方案1:使用set_cookies方法 - elif hasattr(self.page, 'set_cookies'): - self.page.set_cookies(cookies) - logger.info("成功使用set_cookies方法设置cookies") - # 备选方案2:使用page.cookies属性 - elif hasattr(self.page, 'cookies'): - self.page.cookies = cookies - logger.info("成功使用page.cookies属性设置cookies") + logger.info(f"成功获取网站 {site_url} 的 {len(cookies_list)} 条cookies记录") + + # 更新配置文件中的cookies - 使用name属性作为匹配条件 + for config_site in config.get('sites', []): + if config_site.get('name') == site_name: + config_site['cookies'] = cookies_list + logger.info(f"已更新配置文件中网站 {site_name} 的cookies") + break else: - logger.error("无法找到设置cookies的有效方法") + logger.warning(f"未能获取网站 {site_name} - {site_url} 的cookies") + except Exception as e: - logger.error(f"设置cookies失败: {e}") - - # 刷新页面以应用cookies + logger.error(f"处理网站时发生错误: {e}") + continue + + # 使用ConfigManager保存更新后的配置 + if self.config_manager.save_config(config): + logger.info("已将更新后的配置保存到配置文件") + else: + logger.error("保存配置文件失败") + + except Exception as e: + logger.error(f"保存cookies到配置文件时发生错误: {e}") + + logger.info(f"成功获取并保存 {len(all_sites_cookies)} 个网站的cookies") + return all_sites_cookies + + def close(self, quit_browser: bool = None) -> None: + """ + 关闭浏览器页面,可选择是否同时关闭浏览器对象 + + Args: + quit_browser (bool): 是否关闭浏览器对象,None表示根据配置决定 + """ + # 关闭所有页面 + if self.sites: + for page_info in reversed(self.sites): try: - self.page.refresh() + page = page_info['page'] + if hasattr(page, 'close'): + page.close() + logger.info("一个标签页已关闭") except Exception as e: - logger.warning(f"刷新页面时出错: {e}") + logger.error(f"关闭浏览器页面时发生错误: {e}") + # 清空sites列表 + self.sites = [] + else: + logger.warning("没有活动的浏览器页面需要关闭") + + # 决定是否关闭浏览器对象 + should_quit = quit_browser + + # 只有当调用者没有明确指定时,才从配置中读取 + if should_quit is None: + try: + config = self.config_manager.load_config() + should_quit = config.get('AutoQuit', False) + except Exception as e: + logger.error(f"读取配置时出错: {e}") + should_quit = False + + # 如果需要关闭浏览器对象 + if should_quit and self.browser: + try: + self.browser.quit() + logger.info("浏览器对象已关闭") + self.browser = None + except Exception as e: + logger.error(f"关闭浏览器对象时发生错误: {e}") + + def _add_page_metadata(self, page: Any, name: str, url: str, cookies: Optional[List[Dict[str, Any]]] = None) -> None: + """ + 添加页面元数据到sites列表 + + Args: + page: 页面对象 + name: 网站名称 + url: 网站URL + cookies: cookies列表 + """ + self.sites.append({ + 'page': page, + 'name': name, + 'url': url, + 'original_cookies': cookies if cookies else [] + }) + + def _set_cookies_to_page(self, page: Any, cookies: List[Dict[str, Any]] or Dict[str, str], site_name: str) -> bool: + """ + 为页面对象设置cookies + + Args: + page: 页面对象 + cookies: cookies列表或字典 + site_name: 网站名称 - # 等待页面加载完成 - time.sleep(2) + Returns: + bool: 设置是否成功 + """ + logger.info(f"为网站 '{site_name}' 设置 {len(cookies)} 条cookies记录") + + try: + # 检查cookies类型,支持列表格式和字典格式 + if isinstance(cookies, dict): + # 兼容旧的字典格式,转换为列表格式 + cookies_list = [] + for key, value in cookies.items(): + cookies_list.append({ + 'name': key, + 'value': value, + 'domain': '', + 'path': '/', + 'expires': 0, + 'secure': False, + 'httpOnly': False + }) + cookies = cookies_list + logger.info(f"已将字典格式cookies转换为列表格式") - # 验证是否登录成功 - current_url = self.page.url - logger.info(f"当前URL: {current_url}") + # 尝试多种方式设置cookies + if hasattr(page, 'set') and hasattr(page.set, 'cookies'): + if hasattr(page.set.cookies, 'clear'): + page.set.cookies.clear() + page.set.cookies(cookies) + logger.info(f"成功使用set.cookies()方法设置cookies") + elif hasattr(page, 'set_cookies'): + page.set_cookies(cookies) + logger.info("成功使用set_cookies方法设置cookies") + elif hasattr(page, 'cookies'): + page.cookies = cookies + logger.info("成功使用page.cookies属性设置cookies") + else: + logger.error("无法找到设置cookies的有效方法") + return False - # 如果能成功加载页面,暂时认为登录成功 + # 刷新页面以应用cookies + page.refresh() + logger.info(f"网站 '{site_name}' 页面已刷新以应用cookies") return True except Exception as e: - error_msg = str(e) - logger.error(f"登录网站时发生错误: {error_msg}") - - # 针对浏览器路径错误提供详细指导 - if "无法找到浏览器可执行文件路径" in error_msg: - logger.warning("===== 浏览器配置错误指导 =====") - logger.warning("DrissionPage无法找到系统中的浏览器") - logger.warning("请尝试以下解决方法:") - logger.warning("1. 确保Microsoft Edge浏览器已正确安装") - logger.warning("2. Edge浏览器默认安装路径: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe") - logger.warning("3. 检查Edge浏览器是否在系统PATH环境变量中") - logger.warning("4. 或者尝试安装Chrome浏览器") - logger.warning("5. 确保用户有足够的权限运行浏览器") - logger.warning("==========================") - - # 安全关闭浏览器 - if self.page: - try: - self.page.close() - except: - pass - self.page = None - + logger.error(f"为网站 '{site_name}' 设置cookies失败: {e}") return False - def maintain_login(self, duration: int = 120) -> None: + def _get_page_cookies(self, page: Any, site_name: str, page_url: Optional[str] = None) -> List[Dict[str, Any]]: """ - 保持登录状态 + 获取指定页面的cookies Args: - duration (int): 保持时间(秒) + page: 页面对象 + site_name: 网站名称 + page_url: 页面URL(可选) + + Returns: + List[Dict[str, Any]]: cookies列表 """ - if not self.page: - logger.warning("没有活动的浏览器页面,无法保持登录状态") - return + cookies_list = [] + cookies = None - logger.info(f"保持登录状态 {duration} 秒...") + # 构建日志标识 + log_id = f"网站 '{site_name}'" if not page_url else f"页面 {site_name} - {page_url}" + + # 尝试多种方式获取cookies try: - # 定期刷新页面以保持会话活跃 - interval = min(30, duration) # 最多每30秒刷新一次 - elapsed = 0 - - while elapsed < duration: - time.sleep(interval) - elapsed += interval + if hasattr(page, 'get_cookies'): + cookies = page.get_cookies() + elif hasattr(page, 'cookies'): + if callable(page.cookies): + cookies = page.cookies() + else: + cookies = page.cookies + except Exception as api_e: + logger.warning(f"获取{log_id}的cookies时出错: {api_e}") + + # 处理获取到的cookies + if cookies is not None: + if isinstance(cookies, list): + for cookie in cookies: + if 'name' in cookie and 'value' in cookie: + standard_cookie = { + 'name': cookie.get('name'), + 'value': cookie.get('value'), + 'domain': cookie.get('domain', ''), + 'path': cookie.get('path', '/'), + 'expires': cookie.get('expires', 0), + 'secure': cookie.get('secure', False), + 'httpOnly': cookie.get('httpOnly', False) + } + cookies_list.append(standard_cookie) + elif isinstance(cookies, dict): + for key, value in cookies.items(): + cookies_list.append({ + 'name': key, + 'value': value, + 'domain': '', + 'path': '/', + 'expires': 0, + 'secure': False, + 'httpOnly': False + }) + + # 如果还是没有获取到cookies,尝试使用JavaScript + if not cookies_list: + try: + if hasattr(page, 'run_js'): + js_cookies = page.run_js('return document.cookie') + elif hasattr(page, 'execute_script'): + js_cookies = page.execute_script('return document.cookie') + else: + js_cookies = None - # 刷新页面 - try: - self.page.refresh() - logger.info(f"页面已刷新,已保持登录 {elapsed} 秒") - except Exception as e: - logger.error(f"刷新页面时发生错误: {e}") - break - - except KeyboardInterrupt: - logger.info("保持登录状态被用户中断") - except Exception as e: - logger.error(f"保持登录状态时发生错误: {e}") + if js_cookies: + cookie_pairs = js_cookies.split(';') + for pair in cookie_pairs: + pair = pair.strip() + if '=' in pair: + try: + key, value = pair.split('=', 1) + cookies_list.append({ + 'name': key.strip(), + 'value': value.strip(), + 'domain': '', + 'path': '/', + 'expires': 0, + 'secure': False, + 'httpOnly': False + }) + except Exception as parse_e: + logger.warning(f"解析cookie对失败: {pair}, 错误: {parse_e}") + except Exception as js_e: + logger.warning(f"使用JavaScript获取{log_id}的cookies时出错: {js_e}") + + return cookies_list - def get_cookies(self) -> List[Dict[str, Any]]: + def get_duration(self) -> int: """ - 获取当前页面的cookies + 获取配置中的延时时间 Returns: - List[Dict[str, Any]]: cookies记录列表 + int: 延时秒数 """ - if not self.page: - logger.warning("没有活动的浏览器页面,无法获取cookies") - return [] + try: + return self.config_manager.load_config().get('duration', 30) + except Exception as e: + logger.error(f"获取duration配置时发生错误: {e}") + return 30 # 返回默认值 + + def run(self) -> None: + """ + 从配置文件中读取网站信息,为每个网站新建标签页并加载cookies + """ + logger.info("开始运行自动登录流程") + + # 检查浏览器对象是否初始化 + if not self.browser: + logger.error("浏览器对象未初始化,无法运行自动登录流程") + return try: - cookies_list = [] + # 从配置文件中读取网站信息 + config = self.config_manager.load_config() + sites = config.get('sites', []) - # 根据DrissionPage文档,尝试多种方式获取cookies - cookies = None + logger.info(f"获取到 {len(sites)} 个网站配置") - # 首先尝试使用标准API获取cookies - try: - # 尝试使用get_cookies方法 - if hasattr(self.page, 'get_cookies'): - logger.info("尝试使用page.get_cookies()方法获取cookies") - cookies = self.page.get_cookies() - # 尝试使用page.cookies属性 - elif hasattr(self.page, 'cookies'): - logger.info("尝试使用page.cookies属性获取cookies") - # 检查是否是可调用对象 - if callable(self.page.cookies): - cookies = self.page.cookies() - else: - cookies = self.page.cookies - else: - logger.error("无法找到获取cookies的有效方法") - return [] - except Exception as api_e: - logger.warning(f"使用标准API获取cookies失败: {api_e},尝试其他方法") + if not sites: + logger.warning("配置文件中没有网站信息") + return - # 处理获取到的cookies - if cookies is not None: - if isinstance(cookies, list): - # 如果已经是列表格式,确保每条记录包含必要字段 - for cookie in cookies: - # 确保cookie记录包含必要的字段 - if 'name' in cookie and 'value' in cookie: - # 标准化cookie记录格式 - standard_cookie = { - 'name': cookie.get('name'), - 'value': cookie.get('value'), - 'domain': cookie.get('domain', ''), - 'path': cookie.get('path', '/'), - 'expires': cookie.get('expires', 0), - 'secure': cookie.get('secure', False), - 'httpOnly': cookie.get('httpOnly', False) - } - cookies_list.append(standard_cookie) - elif isinstance(cookies, dict): - # 如果是字典格式,转换为列表格式 - for key, value in cookies.items(): - cookies_list.append({ - 'name': key, - 'value': value, - 'domain': '', - 'path': '/', - 'expires': 0, - 'secure': False, - 'httpOnly': False - }) + logger.info(f"找到 {len(sites)} 个网站需要登录") - # 如果还是没有获取到cookies,尝试使用execute_script方法 - if not cookies_list: + # 存储所有创建的标签页 + tabs = [] + + # 为每个网站创建标签页并设置cookies + for site in sites: try: - logger.info("尝试使用JavaScript获取cookies") - if hasattr(self.page, 'run_js'): - js_cookies = self.page.run_js('return document.cookie') - elif hasattr(self.page, 'execute_script'): - js_cookies = self.page.execute_script('return document.cookie') - else: - logger.warning("无法找到执行JavaScript的方法") - js_cookies = None + site_name = site.get('name', '未命名网站') + site_url = site.get('url', '') + site_cookies = site.get('cookies', []) - if js_cookies: - # 解析JavaScript返回的cookie字符串 - cookie_pairs = js_cookies.split(';') - for pair in cookie_pairs: - pair = pair.strip() - if '=' in pair: - try: - key, value = pair.split('=', 1) - cookies_list.append({ - 'name': key.strip(), - 'value': value.strip(), - 'domain': '', - 'path': '/', - 'expires': 0, - 'secure': False, - 'httpOnly': False - }) - except Exception as parse_e: - logger.warning(f"解析cookie对失败: {pair}, 错误: {parse_e}") - except Exception as js_e: - logger.warning(f"使用JavaScript获取cookies失败: {js_e}") + if not site_url: + logger.warning(f"网站 '{site_name}' 缺少URL,跳过") + continue + + logger.info(f"开始处理网站: {site_name} ({site_url})") + + # 创建新标签页 + try: + new_tab = self.browser.new_tab(site_url) + logger.info(f"为网站 '{site_name}' 创建了新标签页") + tabs.append(new_tab) + # 添加网站元数据 + self._add_page_metadata(new_tab, site_name, site_url, site_cookies) + except Exception as e: + logger.error(f"为网站 '{site_name}' 创建标签页失败: {e}") + continue + + # 设置cookies + if site_cookies: + self._set_cookies_to_page(new_tab, site_cookies, site_name) + + + # 等待页面加载完成 + logger.info("页面加载中...") + time.sleep(2) # 仅保留短暂延时确保页面加载完成 + + logger.info(f"网站 '{site_name}' 处理完成") + + except Exception as e: + logger.error(f"处理网站时发生错误: {e}") + continue - logger.info(f"成功获取 {len(cookies_list)} 条cookies记录") - return cookies_list + logger.info(f"所有网站处理完成,共创建了 {len(tabs)} 个标签页") + + # 所有标签页已通过_add_page_metadata添加到sites列表中 + if tabs: + logger.info("已创建并记录所有标签页") + except Exception as e: - logger.error(f"获取cookies时发生错误: {e}") - return [] - - def close(self) -> None: - """ - 关闭浏览器页面 - """ - if self.page: - try: - self.page.browser.quit() - # self.page.close() - # self.page = None - logger.info("浏览器页面已关闭") - except Exception as e: - logger.error(f"关闭浏览器页面时发生错误: {e}") - else: - logger.warning("没有活动的浏览器页面需要关闭") \ No newline at end of file + logger.error(f"运行自动登录流程时发生错误: {e}") \ No newline at end of file diff --git a/main.py b/main.py index 156b296..7175b4c 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ import time -import json from typing import Dict, List, Any from config import ConfigManager from login_manager import LoginManager @@ -17,7 +16,6 @@ class AutoLoginApp: """ self.config_manager = ConfigManager() self.login_manager = LoginManager() - def run(self): """ @@ -25,83 +23,36 @@ class AutoLoginApp: """ logger.info("===== 自动登录应用启动 =====") - # 加载配置 - config = self.config_manager.load_config() - sites = config.get('sites', []) + # 直接调用LoginManager的run函数处理所有网站 + self.login_manager.run() - if not sites: - logger.warning("配置中没有设置任何网站,使用默认示例网站") - # 添加默认示例网站 - sites = [ - {'name': '百度', 'url': 'https://www.baidu.com'}, - {'name': '新浪', 'url': 'https://www.sina.com.cn'} - ] + # 从配置管理器直接获取duration参数 + duration = self.login_manager.get_duration() - # 处理每个网站 - for site in sites: - site_name = site.get('name', '未知网站') - site_url = site.get('url', '') - - if not site_url: - logger.warning(f"网站 '{site_name}' 的URL为空,跳过") - continue - - logger.info(f"处理网站: {site_name} ({site_url})") - - # 获取cookies(从配置中) - cookies = [] - if 'cookies' in site: - config_cookies = site['cookies'] - # 确保cookies是列表格式 - if isinstance(config_cookies, dict): - # 直接将字典转换为列表格式 - cookies = [] - for key, value in config_cookies.items(): - cookies.append({ - 'name': key, - 'value': value, - 'domain': '', - 'path': '/', - 'expires': 0, - 'secure': False, - 'httpOnly': False - }) - else: - cookies = config_cookies - logger.info(f"使用配置文件中的 {len(cookies)} 条cookies记录") - - # 登录网站 - login_success = self.login_manager.login(site_url, cookies) - - if login_success: - logger.info(f"登录网站 '{site_name}' 成功") - - # 保持登录状态一段时间,从配置中获取duration值,默认30秒 - duration = site.get('duration', 30) - self.login_manager.maintain_login(duration=duration) # 保持{duration}秒 - - # 保持登录状态后获取并更新到配置中的cookies - new_cookies = self.login_manager.get_cookies() - if new_cookies: - # 更新当前site对象的cookies - site['cookies'] = new_cookies - logger.info(f"已更新网站 '{site_name}' 的cookies到配置中,共 {len(new_cookies)} 条记录") - - else: - logger.error(f"登录网站 '{site_name}' 失败") - - # 关闭当前浏览器页面,准备下一个网站 - self.login_manager.close() - time.sleep(2) # 间隔2秒再处理下一个网站 + logger.info(f"延时 {duration} 秒后获取所有页面cookies") + time.sleep(duration) - # 保存更新后的配置到config.json + # 调用save_cookies函数获取并保存所有网站的cookies到config.json try: - self.config_manager.save_config(config) - logger.info("已将更新后的cookies保存到config.json文件中") + all_cookies = self.login_manager.save_cookies() + logger.info(f"成功获取并保存 {len(all_cookies)} 个网站的cookies到config.json文件") except Exception as e: - logger.error(f"保存配置文件时发生错误: {e}") + logger.error(f"获取或保存cookies时发生错误: {e}") logger.info("===== 所有网站处理完成 =====") + + # 从配置管理器直接获取AutoQuit配置并关闭浏览器 + auto_quit = self.config_manager.load_config().get('AutoQuit', False) + if auto_quit: + try: + self.login_manager.close(True) # 直接传入True表示关闭浏览器 + logger.info("已关闭浏览器") + except Exception as e: + logger.error(f"关闭浏览器时发生错误: {e}") + else: + logger.info("浏览器保持打开状态") + + # 不再需要返回auto_quit值,因为它已经在方法内部使用 if __name__ == "__main__": app = AutoLoginApp() @@ -110,7 +61,4 @@ if __name__ == "__main__": except KeyboardInterrupt: logger.info("程序被用户中断") except Exception as e: - logger.error(f"程序运行时发生错误: {e}") - finally: - # 确保关闭浏览器 - app.login_manager.close() \ No newline at end of file + logger.error(f"程序运行时发生错误: {e}") \ No newline at end of file diff --git a/开发文档.md b/开发文档.md index fba26a8..55ac0cc 100644 --- a/开发文档.md +++ b/开发文档.md @@ -8,7 +8,7 @@ - 从配置文件读取网站地址和对应的cookies信息 - 按顺序登录每个网站 -- 登录成功后保持登录状态指定时间(可配置) +- 登录成功后依次打开多个网页(支持全局duration配置) - 保存更新后的cookies信息到配置文件 - 关闭网站浏览器实例 - 完整的日志记录功能 @@ -60,7 +60,7 @@ autoLogon/ - `__init__()`: 初始化登录管理器 - `login(site_url, cookies=None)`: 登录网站,支持设置cookies - `maintain_login(duration=120)`: 保持登录状态指定时间 - - `get_cookies()`: 获取当前页面的cookies + - `save_cookies()`: 获取所有页面的cookies - `close()`: 关闭浏览器页面 ### 5.3 日志配置器 (logger_config.py) @@ -96,13 +96,13 @@ autoLogon/ ```json { + "duration": 30, "browser_path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", "sites": [ { - "name": "网站名称", - "url": "网站URL", - "duration": 30, - "cookies": [ + "name": "网站名称", + "url": "网站URL", + "cookies": [ { "name": "cookie名称", "value": "cookie值", @@ -119,11 +119,11 @@ autoLogon/ ``` **配置项说明**: +- `duration`: 处理每个网站时的延时时间(秒),默认30秒 - `browser_path`: 浏览器可执行文件路径(可选) - `sites`: 网站配置列表 - `name`: 网站名称 - `url`: 网站URL - - `duration`: 保持登录状态的时间(秒),默认30秒 - `cookies`: cookies列表,每个cookie包含完整信息(name, value, domain等) ```