diff --git a/README.md b/README.md new file mode 100644 index 0000000..9dea2c1 --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# autoLogon - 自动登录应用 + +## 项目简介 + +autoLogon是一个自动化网站登录工具,可以帮助用户自动登录多个网站并维护登录状态。该应用通过配置文件管理网站信息,支持使用cookies维持登录会话,并能自动保存和更新cookies。 + +## 主要功能 + +- 自动登录配置的多个网站 +- 支持cookies管理,自动保存和更新会话信息 +- 可自定义每个网站的保持登录时间 +- 支持自定义浏览器路径 +- 完整的日志记录系统 +- 配置文件热更新 + +## 项目结构 + +``` +autoLogon/ +├── main.py # 应用主入口 +├── login_manager.py # 登录管理器 +├── config.py # 配置管理器 +├── logger_config.py # 日志配置 +├── config.json # 配置文件 +├── requirements.txt # 依赖列表 +└── log/ # 日志文件夹 +``` + +## 核心文件说明 + +### main.py +应用的主入口文件,包含`AutoLoginApp`类,负责初始化各个管理器并协调它们的工作流程,包括加载配置、处理网站列表、执行登录操作等。 + +### login_manager.py +登录管理器,封装了与浏览器相关的操作,负责网站登录、cookies设置与获取、保持登录状态等功能。使用DrissionPage库实现浏览器自动化。 + +### config.py +配置管理器,负责加载、保存和管理应用配置,包括网站列表、浏览器路径等信息。 + +### logger_config.py +日志配置模块,设置全局日志记录器,支持文件和控制台双重输出。 + +### config.json +应用配置文件,使用JSON格式存储,包含以下主要配置项: +- `sites`:网站配置列表 +- `browser_path`:浏览器可执行文件路径 + +## 配置文件格式 + +```json +{ + "browser_path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "sites": [ + { + "name": "网站名称", + "url": "网站URL", + "duration": 30, + "cookies": [ + { + "name": "cookie名称", + "value": "cookie值", + "domain": "域名", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + } + ] + } + ] +} +``` + +## 安装依赖 + +使用以下命令安装项目依赖: + +```bash +pip install -r requirements.txt +``` + +## 使用方法 + +1. 确保已安装所有依赖 +2. 配置`config.json`文件,添加需要自动登录的网站信息 +3. 运行主程序: + +```bash +python main.py +``` + +## 工作流程 + +1. 加载配置文件 +2. 遍历处理每个网站: + - 从配置中获取cookies(如果有) + - 启动浏览器并访问网站 + - 设置cookies(如果提供) + - 保持登录状态指定时间 + - 获取并更新最新cookies到配置 + - 关闭当前浏览器页面 +3. 保存更新后的配置 +4. 关闭浏览器 + +## 注意事项 + +- 确保浏览器路径正确配置,默认使用Edge浏览器 +- 配置文件中的`duration`参数表示网站打开后保持的时间(秒) +- 首次运行时,应用会为没有cookies的网站创建空的cookies配置 +- 日志文件保存在`log`目录下,按日期命名 + +## 故障排除 + +- 如遇浏览器启动失败,请检查`browser_path`配置是否正确 +- 如遇登录失败,检查网站URL是否正确,以及cookies格式是否符合要求 +- 查看日志文件了解详细的错误信息 + +## 扩展建议 + +- 增加对更多浏览器的支持 +- 添加自动识别登录状态的功能 +- 实现定时任务功能,定期执行自动登录 +- 添加图形用户界面进行配置管理 \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..569f00b --- /dev/null +++ b/config.json @@ -0,0 +1,267 @@ +{ + "browser_path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "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", + "domain": ".baidu.com", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + } + ] + }, + { + "name": "新浪", + "url": "https://www.sina.com.cn", + "duration": 30, + "cookies": [ + { + "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", + "domain": ".www.sina.com.cn", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + }, + { + "name": "Hm_lvt_90c40f528e0b2106bc03da5aadec190f", + "value": "1760759893,1760768784,1760769195", + "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": "示例网站1", + "url": "https://lmkbi.95155.com/bi-system/#/login", + "duration": 30, + "cookies": [ + { + "name": "userType", + "value": "2", + "domain": "lmkbi.95155.com", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + }, + { + "name": "user", + "value": "13548569666", + "domain": "lmkbi.95155.com", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + }, + { + "name": "edentoken", + "value": "13548569666", + "domain": "lmkbi.95155.com", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + }, + { + "name": "LMKBIUSS", + "value": "eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOiIxMzU0ODU2OTY2NiIsInRpbWVzdGFtcCI6MTc2MDY4Mzg5MzIwOCwidG9rZW4iOiJlMmJhMTE1Yi05YmZiLTQ5MDYtYTA0OS1iMzlmMjkwYTFmODUifQ==", + "domain": "lmkbi.95155.com", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + } + ] + }, + { + "name": "示例网站2", + "url": "http://192.168.100.1/", + "duration": 30, + "cookies": [ + { + "name": "sysauth_http", + "value": "b05d9c8feea4a5a931e8ef687ac119de", + "domain": "192.168.100.1", + "path": "/", + "expires": 0, + "secure": false, + "httpOnly": false + } + ] + } + ] +} \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..eff81ad --- /dev/null +++ b/config.py @@ -0,0 +1,158 @@ +import json +import os +from typing import Dict, List, Optional +from logger_config import global_logger as logger + +class ConfigManager: + """ + 配置管理器,负责加载、保存和管理应用配置 + """ + + def __init__(self, config_file: str = 'config.json'): + """ + 初始化配置管理器 + + Args: + config_file (str): 配置文件路径 + """ + self.config_file = config_file + self.default_config = { + 'sites': [] + } + + def load_config(self) -> Dict: + """ + 加载配置文件 + + Returns: + dict: 配置数据 + """ + try: + if os.path.exists(self.config_file): + with open(self.config_file, 'r', encoding='utf-8') as f: + config = json.load(f) + logger.info(f"成功加载配置文件: {self.config_file}") + return config + else: + logger.warning(f"配置文件不存在: {self.config_file}") + logger.info("使用默认配置") + return self.default_config.copy() + except json.JSONDecodeError as e: + logger.error(f"配置文件格式错误: {e}") + logger.info("使用默认配置") + return self.default_config.copy() + except Exception as e: + logger.error(f"加载配置文件时发生错误: {e}") + logger.info("使用默认配置") + return self.default_config.copy() + + def save_config(self, config: Dict) -> bool: + """ + 保存配置到文件 + + Args: + config (dict): 要保存的配置数据 + + Returns: + bool: 保存是否成功 + """ + try: + # 确保配置中包含必要的键 + if 'sites' not in config: + config['sites'] = [] + + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(config, f, ensure_ascii=False, indent=2) + + logger.info(f"成功保存配置到文件: {self.config_file}") + return True + except Exception as e: + logger.error(f"保存配置文件时发生错误: {e}") + return False + + def get_sites(self) -> List[Dict]: + """ + 获取所有网站配置 + + Returns: + list: 网站配置列表 + """ + config = self.load_config() + sites = config.get('sites', []) + logger.info(f"获取到 {len(sites)} 个网站配置") + return sites + + def add_site(self, site_config: Dict) -> bool: + """ + 添加网站配置 + + Args: + site_config (dict): 网站配置 + + Returns: + bool: 添加是否成功 + """ + if not isinstance(site_config, dict) or 'name' not in site_config or 'url' not in site_config: + logger.error("网站配置格式错误,必须包含name和url字段") + return False + + config = self.load_config() + sites = config.get('sites', []) + + # 检查是否已存在同名网站 + for site in sites: + if site.get('name') == site_config.get('name'): + logger.warning(f"网站 '{site_config.get('name')}' 已存在") + return False + + sites.append(site_config) + config['sites'] = sites + return self.save_config(config) + + def update_site(self, site_name: str, updated_config: Dict) -> bool: + """ + 更新网站配置 + + Args: + site_name (str): 网站名称 + updated_config (dict): 更新后的配置 + + Returns: + bool: 更新是否成功 + """ + config = self.load_config() + sites = config.get('sites', []) + + for i, site in enumerate(sites): + if site.get('name') == site_name: + # 保留原有的name和url,只更新其他字段 + updated_site = site.copy() + updated_site.update(updated_config) + sites[i] = updated_site + config['sites'] = sites + return self.save_config(config) + + logger.warning(f"找不到名称为 '{site_name}' 的网站配置") + return False + + def delete_site(self, site_name: str) -> bool: + """ + 删除网站配置 + + Args: + site_name (str): 网站名称 + + Returns: + bool: 删除是否成功 + """ + config = self.load_config() + sites = config.get('sites', []) + + new_sites = [site for site in sites if site.get('name') != site_name] + + if len(new_sites) == len(sites): + logger.warning(f"找不到名称为 '{site_name}' 的网站配置") + return False + + config['sites'] = new_sites + return self.save_config(config) \ No newline at end of file diff --git a/logger_config.py b/logger_config.py new file mode 100644 index 0000000..e599d5d --- /dev/null +++ b/logger_config.py @@ -0,0 +1,61 @@ +import logging +import os +from datetime import datetime + +class LoggerConfig: + """ + 日志配置类,用于初始化和配置logging + """ + + @staticmethod + def setup_logger(log_level=logging.INFO): + """ + 设置日志记录器 + + Args: + log_level: 日志级别,默认为INFO + + Returns: + logger: 配置好的logger对象 + """ + # 创建log目录 + log_dir = 'log' + if not os.path.exists(log_dir): + os.makedirs(log_dir) + + # 生成日志文件名,使用当前日期 + current_date = datetime.now().strftime('%Y-%m-%d') + log_file = os.path.join(log_dir, f'autologon_{current_date}.log') + + # 创建logger + logger = logging.getLogger('autoLogon') + logger.setLevel(log_level) + + # 清除已有的handler + if logger.handlers: + for handler in logger.handlers: + logger.removeHandler(handler) + + # 创建文件handler + file_handler = logging.FileHandler(log_file, encoding='utf-8') + file_handler.setLevel(log_level) + + # 创建控制台handler + console_handler = logging.StreamHandler() + console_handler.setLevel(log_level) + + # 定义日志格式 + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s' + ) + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + # 添加handler到logger + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + return logger + +# 创建全局logger实例 +global_logger = LoggerConfig.setup_logger() \ No newline at end of file diff --git a/login_manager.py b/login_manager.py new file mode 100644 index 0000000..8096c35 --- /dev/null +++ b/login_manager.py @@ -0,0 +1,299 @@ +import os +import time +from typing import Dict, List, Any +from DrissionPage import ChromiumPage, ChromiumOptions +from logger_config import global_logger as logger +from config import ConfigManager + + +class LoginManager: + """ + 登录管理器,负责处理网站登录和会话维护 + """ + + def __init__(self): + """ + 初始化登录管理器 + """ + self.page = 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}") + + 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}") + else: + logger.warning(f"Edge浏览器路径不存在: {edge_path}") + logger.info("将使用默认配置,让DrissionPage自动查找浏览器") + + # 使用配置创建ChromiumPage实例 + # 根据文档,在DrissionPage 4.x中,应该使用正确的方式初始化 + self.page = ChromiumPage(co) + + # 访问网站 + 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 = 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") + else: + logger.error("无法找到设置cookies的有效方法") + except Exception as e: + logger.error(f"设置cookies失败: {e}") + + # 刷新页面以应用cookies + try: + self.page.refresh() + except Exception as e: + logger.warning(f"刷新页面时出错: {e}") + + # 等待页面加载完成 + time.sleep(2) + + # 验证是否登录成功 + current_url = self.page.url + logger.info(f"当前URL: {current_url}") + + # 如果能成功加载页面,暂时认为登录成功 + 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 + + return False + + def maintain_login(self, duration: int = 120) -> None: + """ + 保持登录状态 + + Args: + duration (int): 保持时间(秒) + """ + if not self.page: + logger.warning("没有活动的浏览器页面,无法保持登录状态") + return + + logger.info(f"保持登录状态 {duration} 秒...") + try: + # 定期刷新页面以保持会话活跃 + interval = min(30, duration) # 最多每30秒刷新一次 + elapsed = 0 + + while elapsed < duration: + time.sleep(interval) + elapsed += interval + + # 刷新页面 + 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}") + + def get_cookies(self) -> List[Dict[str, Any]]: + """ + 获取当前页面的cookies + + Returns: + List[Dict[str, Any]]: cookies记录列表 + """ + if not self.page: + logger.warning("没有活动的浏览器页面,无法获取cookies") + return [] + + try: + cookies_list = [] + + # 根据DrissionPage文档,尝试多种方式获取cookies + cookies = None + + # 首先尝试使用标准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},尝试其他方法") + + # 处理获取到的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 + }) + + # 如果还是没有获取到cookies,尝试使用execute_script方法 + if not cookies_list: + 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 + + 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}") + + logger.info(f"成功获取 {len(cookies_list)} 条cookies记录") + return cookies_list + 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 diff --git a/main.py b/main.py new file mode 100644 index 0000000..07760e7 --- /dev/null +++ b/main.py @@ -0,0 +1,116 @@ +import time +import json +from typing import Dict, List, Any +from config import ConfigManager +from login_manager import LoginManager + +from logger_config import global_logger as logger + +class AutoLoginApp: + """ + 自动登录应用主类 + """ + + def __init__(self): + """ + 初始化自动登录应用 + """ + self.config_manager = ConfigManager() + self.login_manager = LoginManager() + + + def run(self): + """ + 运行自动登录应用 + """ + logger.info("===== 自动登录应用启动 =====") + + # 加载配置 + config = self.config_manager.load_config() + sites = config.get('sites', []) + + if not sites: + logger.warning("配置中没有设置任何网站,使用默认示例网站") + # 添加默认示例网站 + sites = [ + {'name': '百度', 'url': 'https://www.baidu.com'}, + {'name': '新浪', 'url': 'https://www.sina.com.cn'} + ] + + # 处理每个网站 + 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秒再处理下一个网站 + + # 保存更新后的配置到config.json + try: + self.config_manager.save_config(config) + logger.info("已将更新后的cookies保存到config.json文件中") + except Exception as e: + logger.error(f"保存配置文件时发生错误: {e}") + + logger.info("===== 所有网站处理完成 =====") + +if __name__ == "__main__": + app = AutoLoginApp() + try: + app.run() + except KeyboardInterrupt: + logger.info("程序被用户中断") + except Exception as e: + logger.error(f"程序运行时发生错误: {e}") + finally: + # 确保关闭浏览器 + app.login_manager.close() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..37d06ee --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +drissionpage \ No newline at end of file diff --git a/开发文档.md b/开发文档.md new file mode 100644 index 0000000..7eb6606 --- /dev/null +++ b/开发文档.md @@ -0,0 +1,132 @@ +# 自动登录网站并管理Cookies项目开发文档 + +## 1. 项目概述 + +本项目使用Python语言和DrissionPage库开发一个自动化脚本,用于按顺序登录多个网站,保持登录状态一段时间后保存更新的cookies,并关闭网站。 + +## 2. 功能需求 + +- 从配置文件读取网站地址和对应的cookies信息 +- 按顺序登录每个网站 +- 登录成功后保持登录状态2分钟 +- 保存更新后的cookies信息 +- 关闭网站 + +## 3. 技术栈 + +- Python 3.8+ +- DrissionPage库 +- JSON/YAML用于配置文件格式 + +## 4. 项目结构 + +``` +autoLogon/ +├── config.py # 配置文件处理模块 +├── login_manager.py # 登录管理模块 +├── cookie_handler.py # Cookies处理模块 +├── main.py # 主程序入口 +├── config.json # 配置文件(存储网站和cookies信息) +├── requirements.txt # 依赖包列表 +└── README.md # 项目说明文档 +``` + +## 5. 模块设计 + +### 5.1 配置文件处理模块 (config.py) + +**功能**:负责读取和写入配置文件中的网站地址和cookies信息 + +**主要函数**: +- `load_config(file_path)`: 加载配置文件 +- `save_config(config_data, file_path)`: 保存配置文件 + +### 5.2 Cookies处理模块 (cookie_handler.py) + +**功能**:负责cookies的管理,包括解析、转换和保存 + +**主要函数**: +- `parse_cookies(cookie_str)`: 解析cookies字符串为字典格式 +- `cookies_to_str(cookies_dict)`: 将cookies字典转换为字符串 +- `save_cookies(site_url, cookies, config_manager)`: 保存网站的cookies到配置文件 + +### 5.3 登录管理模块 (login_manager.py) + +**功能**:负责网站的登录和维护登录状态 + +**主要函数**: +- `login_to_site(browser, site_url, cookies)`: 使用提供的cookies登录网站 +- `verify_login(browser)`: 验证是否登录成功 +- `maintain_session(browser, duration=120)`: 保持登录状态指定时间 + +### 5.4 主程序模块 (main.py) + +**功能**:程序入口,协调各模块工作 + +**主要流程**: +1. 初始化配置管理器 +2. 加载配置文件 +3. 初始化浏览器 +4. 遍历每个网站配置 +5. 调用登录管理模块登录网站 +6. 保持登录状态 +7. 更新并保存cookies +8. 关闭浏览器 + +## 6. 配置文件格式 (JSON) + +```json +{ + "sites": [ + { + "name": "网站A", + "url": "https://example.com", + "cookies": { + "cookie1": "value1", + "cookie2": "value2" + } + }, + { + "name": "网站B", + "url": "https://example2.com", + "cookies": { + "cookie3": "value3", + "cookie4": "value4" + } + } + ] +} +``` + +## 7. 依赖安装 + +```bash +pip install drissionpage +``` + +## 8. 实现步骤 + +1. 创建项目目录结构 +2. 编写配置文件处理模块 +3. 编写Cookies处理模块 +4. 编写登录管理模块 +5. 编写主程序模块 +6. 创建配置文件示例 +7. 测试各个模块 +8. 整合测试 + +## 9. 注意事项 + +- 确保使用正确的cookies格式 +- 处理可能的登录失败情况 +- 考虑网站的反爬机制 +- 妥善保存配置文件,避免敏感信息泄露 +- 处理网络异常和超时情况 + +## 10. 扩展功能建议 + +- 添加日志记录功能 +- 实现登录失败重试机制 +- 添加多线程或异步支持以提高效率 +- 实现定时任务功能 +- 添加GUI界面便于操作 \ No newline at end of file