From bbf84b7f8c4ac27ed3db2e80b95ee6769c8a5d5d Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Fri, 23 Jan 2026 15:00:55 +0800 Subject: [PATCH] docs: update project structure and add database documentation - Add tests/ directory to .gitignore - Document database connectivity with pyodbc and config - Add project structure and file organization guidelines - Update installation steps to use requirements.txt - Document new dependencies: pandas, openpyxl, pyodbc --- .gitignore | 6 +- CLAUDE.md | 71 ++++++++- config/__init__.py | 1 + config/database_config.py | 9 ++ db/__init__.py | 1 + db/connection.py | 184 ++++++++++++++++++++++ docs/DEPENDENCIES.md | 169 ++++++++++++++++++++ requirements.txt | 19 +++ test_playwright.py | 318 -------------------------------------- 9 files changed, 456 insertions(+), 322 deletions(-) create mode 100644 config/__init__.py create mode 100644 config/database_config.py create mode 100644 db/__init__.py create mode 100644 db/connection.py create mode 100644 docs/DEPENDENCIES.md create mode 100644 requirements.txt delete mode 100644 test_playwright.py diff --git a/.gitignore b/.gitignore index 898de88..e8f320b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,8 @@ tmpclaude-* *workspace* *.png data/ -orderID.txt \ No newline at end of file +orderID.txt +ProductionID.txt + +# 测试脚本 +tests/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index ed6afdf..fe50fab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,8 +12,8 @@ This is a Python automation project using Playwright to interact with a Chinese # Activate the virtual environment (Windows) .venv\Scripts\activate -# Install dependencies -pip install playwright +# Install all dependencies +pip install -r requirements.txt # Install Playwright browsers playwright install chromium @@ -23,17 +23,53 @@ playwright install chromium ```bash # Run the main test script -python test_playwright.py +python tests/test_playwright.py # Run the record script python record.py + +# Run database query test +python tests/test_db_query.py ``` ## Key Dependencies - **playwright==1.57.0**: Browser automation framework +- **pyodbc**: SQL Server database connectivity +- **pandas**: Data processing and Excel file handling +- **openpyxl**: Excel file operations - Uses synchronous API (`playwright.sync_api`) +## Project Structure + +``` +playwrite/ +├── config/ # Configuration files (database, etc.) +├── db/ # Database connection components +├── data/ # Data files (excluded from git) +├── docs/ # Documentation (e.g., DEPENDENCIES.md) +├── tests/ # Test scripts (excluded from git) +├── requirements.txt # Python dependencies +├── .gitignore # Git ignore rules +└── CLAUDE.md # This file +``` + +## File Organization Rules + +**IMPORTANT**: When creating test scripts, always place them in the `tests/` folder: + +- Test scripts should be prefixed with `test_` +- All files in `tests/` are excluded from git tracking +- Examples: `tests/test_playwright.py`, `tests/test_db_query.py` + +**Configuration Management**: +- Place configuration files in `config/` folder +- Database credentials and settings go in `config/database_config.py` + +**Data Storage**: +- Use `data/` folder for temporary data files +- This folder is excluded from git tracking + ## Architecture ### Nested Iframe Structure @@ -80,6 +116,35 @@ with page.expect_popup() as popup_info: new_page = popup_info.value ``` +### Database Connection + +The project uses `pyodbc` to connect to SQL Server for data queries: + +```python +from db.connection import get_connection + +# Using context manager +with get_connection() as db: + results = db.execute_query("SELECT * FROM table") + # Connection automatically closed + +# Using the query helper +from db.connection import query_production_orders +results = query_production_orders(['ID1', 'ID2', 'ID3']) +``` + +Database configuration is stored in `config/database_config.py`: +```python +SQL_SERVER_CONFIG = { + 'driver': 'ODBC Driver 18 for SQL Server', + 'server': '192.168.110.114', + 'database': 'CompanyDB', + 'username': 'peng', + 'password': 'Cqbld123456.', + 'TrustServerCertificate': 'yes' +} +``` + ## Common Utilities ### `get_input_by_label(frame, label_text)` diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..2726292 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1 @@ +# Config package diff --git a/config/database_config.py b/config/database_config.py new file mode 100644 index 0000000..39e370f --- /dev/null +++ b/config/database_config.py @@ -0,0 +1,9 @@ +# ================= SQL Server 配置 ================= +SQL_SERVER_CONFIG = { + 'driver': 'ODBC Driver 18 for SQL Server', + 'server': '192.168.110.114', + 'database': 'CompanyDB', + 'username': 'peng', + 'password': 'Cqbld123456.', + 'TrustServerCertificate': 'yes' +} diff --git a/db/__init__.py b/db/__init__.py new file mode 100644 index 0000000..99ce574 --- /dev/null +++ b/db/__init__.py @@ -0,0 +1 @@ +# Database package diff --git a/db/connection.py b/db/connection.py new file mode 100644 index 0000000..0cd5ea0 --- /dev/null +++ b/db/connection.py @@ -0,0 +1,184 @@ +""" +SQL Server 数据库连接组件 + +提供数据库连接和查询接口 +""" +import pyodbc +from typing import List, Dict, Any, Optional +import sys +import os + +# 添加项目根目录到 sys.path +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if project_root not in sys.path: + sys.path.insert(0, project_root) + +from config.database_config import SQL_SERVER_CONFIG + + +class DatabaseConnection: + """SQL Server 数据库连接类""" + + def __init__(self, config: Optional[Dict[str, Any]] = None): + """ + 初始化数据库连接 + + Args: + config: 数据库配置字典,默认使用 SQL_SERVER_CONFIG + """ + self.config = config or SQL_SERVER_CONFIG + self.connection = None + + def connect(self) -> pyodbc.Connection: + """ + 建立数据库连接 + + Returns: + pyodbc.Connection: 数据库连接对象 + """ + if self.connection is not None: + return self.connection + + # 构建连接字符串 + conn_str = ( + f"DRIVER={{{self.config['driver']}}};" + f"SERVER={self.config['server']};" + f"DATABASE={self.config['database']};" + f"UID={self.config['username']};" + f"PWD={self.config['password']};" + f"TrustServerCertificate={self.config['TrustServerCertificate']};" + ) + + try: + self.connection = pyodbc.connect(conn_str) + print(f"成功连接到数据库: {self.config['server']}/{self.config['database']}") + return self.connection + except pyodbc.Error as e: + print(f"数据库连接失败: {e}") + raise + + def disconnect(self): + """关闭数据库连接""" + if self.connection: + self.connection.close() + self.connection = None + print("数据库连接已关闭") + + def execute_query(self, sql: str, params: Optional[tuple] = None) -> List[Dict[str, Any]]: + """ + 执行查询语句并返回结果 + + Args: + sql: SQL 查询语句 + params: 查询参数(可选) + + Returns: + List[Dict[str, Any]]: 查询结果列表,每个元素为一行数据的字典 + """ + if not self.connection: + self.connect() + + cursor = self.connection.cursor() + + try: + if params: + cursor.execute(sql, params) + else: + cursor.execute(sql) + + # 获取列名 + columns = [column[0] for column in cursor.description] + + # 将结果转换为字典列表 + results = [] + for row in cursor.fetchall(): + results.append(dict(zip(columns, row))) + + return results + + except pyodbc.Error as e: + print(f"查询执行失败: {e}") + raise + finally: + cursor.close() + + def execute_update(self, sql: str, params: Optional[tuple] = None) -> int: + """ + 执行更新/插入/删除语句 + + Args: + sql: SQL 语句 + params: 参数(可选) + + Returns: + int: 受影响的行数 + """ + if not self.connection: + self.connect() + + cursor = self.connection.cursor() + + try: + if params: + cursor.execute(sql, params) + else: + cursor.execute(sql) + + self.connection.commit() + return cursor.rowcount + + except pyodbc.Error as e: + self.connection.rollback() + print(f"执行失败,已回滚: {e}") + raise + finally: + cursor.close() + + def __enter__(self): + """支持 with 语句的上下文管理器入口""" + self.connect() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """支持 with 语句的上下文管理器出口""" + self.disconnect() + + +# 便捷函数 +def query_production_orders(总排号_list: List[str]) -> List[Dict[str, Any]]: + """ + 根据总排号列表查询生产订单号 + + Args: + 总排号_list: 总排号列表 + + Returns: + List[Dict[str, Any]]: 查询结果 + """ + db = DatabaseConnection() + + # 构建占位符字符串 + placeholders = ','.join(['?' for _ in 总排号_list]) + + sql = f""" + SELECT [总排号], [生产订单号], [序号], [订单号], [客户名称], [产品型号] + FROM [productionContractData].[26年压力表合同数据] + WHERE [总排号] IN ({placeholders}) + ORDER BY [序号] + """ + + try: + results = db.execute_query(sql, tuple(总排号_list)) + return results + finally: + db.disconnect() + + +def get_connection() -> DatabaseConnection: + """ + 获取数据库连接实例 + + Returns: + DatabaseConnection: 数据库连接对象 + """ + return DatabaseConnection() diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md new file mode 100644 index 0000000..2d1420d --- /dev/null +++ b/docs/DEPENDENCIES.md @@ -0,0 +1,169 @@ +# 项目依赖说明 + +## Python 版本要求 + +- Python >= 3.8 + +## 依赖安装 + +```bash +# 激活虚拟环境 (Windows) +.venv\Scripts\activate + +# 安装所有依赖 +pip install -r requirements.txt + +# 安装 Playwright 浏览器 +playwright install chromium +``` + +## 核心依赖 + +### 1. playwright==1.57.0 + +**用途**: 浏览器自动化框架 + +**功能模块**: +- `playwright.sync_api` - 同步 API +- 浏览器启动与页面操作 +- 元素定位器 (Locator) +- 弹窗处理 (expect_popup) +- 嵌套 iframe 处理 + +**使用文件**: +- `test_playwright.py` - 主要自动化脚本 +- `record.py` - 录制脚本 + +**重要**: 安装后需运行 `playwright install chromium` 下载浏览器 + +--- + +### 2. pyodbc>=5.0.0 + +**用途**: SQL Server 数据库连接 + +**功能模块**: +- 数据库连接管理 +- SQL 查询执行 +- 事务处理 + +**使用文件**: +- `db/connection.py` - 数据库连接组件 +- `test_db_query.py` - 数据库查询测试脚本 + +**注意**: Windows 系统通常已预装 ODBC 驱动,如遇连接问题请安装 [ODBC Driver 18 for SQL Server](https://learn.microsoft.com/zh-cn/sql/connect/odbc/download-odbc-driver-for-sql-server) + +--- + +### 3. pandas>=2.0.0 + +**用途**: 数据处理与分析 + +**功能模块**: +- DataFrame 数据结构 +- Excel 文件读写 +- 数据清洗与转换 + +**使用文件**: +- `excel_to_markdown.py` - Excel 转 Markdown +- `analyze_excel.py` - Excel 数据分析 + +--- + +### 4. openpyxl>=3.1.0 + +**用途**: Excel 文件操作 + +**功能模块**: +- 读取 `.xlsx` 文件 +- 工作表操作 +- 单元格读取 + +**使用文件**: +- `excel_to_markdown.py` +- `analyze_excel.py` + +--- + +### 5. numpy>=1.24.0 + +**用途**: 数值计算 + +**说明**: pandas 的依赖包,自动安装 + +--- + +### 6. python-dateutil>=2.8.0 + +**用途**: 日期时间处理 + +**说明**: pandas 的依赖包,自动安装 + +--- + +### 7. pytz>=2023.0 + +**用途**: 时区处理 + +**说明**: pandas 的依赖包,自动安装 + +--- + +## 传递依赖 + +以下包由核心依赖自动安装,无需手动指定: + +| 包名 | 版本 | 被依赖包 | +|------|------|----------| +| greenlet | 3.3.0 | playwright | +| pyee | 13.0.0 | playwright | +| typing_extensions | 4.15.0 | 多个包 | +| et_xmlfile | 2.0.0 | openpyxl | +| six | 1.17.0 | python-dateutil | +| tzdata | 2025.3 | pandas | + +--- + +## 按功能分类 + +### 浏览器自动化 +``` +playwright==1.57.0 +``` + +### 数据库 +``` +pyodbc>=5.0.0 +``` + +### Excel/数据处理 +``` +pandas>=2.0.0 +openpyxl>=3.1.0 +numpy>=1.24.0 +``` + +--- + +## 当前环境已安装包 + +``` +Package Version +----------------- ----------- +et_xmlfile 2.0.0 +greenlet 3.3.0 +numpy 2.2.6 +openpyxl 3.1.5 +pandas 2.3.3 +pip 25.3 +playwright 1.57.0 +pyee 13.0.0 +python-dateutil 2.9.0.post0 +pytz 2025.2 +setuptools 65.5.0 +six 1.17.0 +typing_extensions 4.15.0 +tzdata 2025.3 +``` + +**注意**: `pyodbc` 尚未安装,运行数据库相关脚本前请先安装。 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..91618d8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,19 @@ +# =========================== +# Playwright Python Automation +# =========================== +# Python 3.8+ required + +# --- Browser Automation --- +playwright==1.57.0 + +# --- Database --- +pyodbc>=5.0.0 + +# --- Excel/Data Processing --- +pandas>=2.0.0 +openpyxl>=3.1.0 +numpy>=1.24.0 + +# --- System Utilities (installed via pip) --- +python-dateutil>=2.8.0 +pytz>=2023.0 diff --git a/test_playwright.py b/test_playwright.py deleted file mode 100644 index 48ca9e7..0000000 --- a/test_playwright.py +++ /dev/null @@ -1,318 +0,0 @@ -import re -import time -from playwright.sync_api import Playwright, sync_playwright, expect, TimeoutError as PWTimeoutError -import pdb - -def click_button_until_disappear(frame, button_name="保存提交", max_attempts=None, max_duration=None): - """ - 持续点击按钮直到按钮消失。 - 包含业务逻辑:点击前检查【生产部门】是否为空,为空则自动回填数据。 - """ - click_count = 0 - start_time = time.time() - - # 定义加载控件的定位器 (基于传入的 frame) - loading_locator = frame.locator("div").filter(has_text="加载中").nth(1) - - print(f"开始监控'{button_name}'按钮 (含自动补全逻辑)...") - - while True: - try: - # --- 1. 检查退出条件 --- - if max_attempts and click_count >= max_attempts: - print(f"已达到最大点击次数{max_attempts}次,停止操作") - break - - if max_duration and (time.time() - start_time) >= max_duration: - elapsed = time.time() - start_time - print(f"已达到最大持续时间{max_duration}秒,停止操作") - break - - # --- 2. 检查按钮状态 --- - button = frame.get_by_role("button", name=button_name) - - # 如果按钮已经消失,任务完成 - if button.count() == 0: - elapsed = time.time() - start_time - print(f"SUCCESS: '{button_name}'按钮已消失") - print(f"共点击{click_count}次,总耗时{elapsed:.1f}秒") - return { - "success": True, - "click_count": click_count, - "elapsed_time": elapsed - } - - # ========================================== - # 插入业务逻辑:检查并回填数据 - # ========================================== - try: - # 1. 获取生产部门输入框 - dep_input = get_input_by_label(frame, "生产部门") - - if dep_input: - current_val = dep_input.input_value() - # 检查是否为空 (strip去除空白字符) - if current_val != "压力表车间" and current_val != "CQ030101": - print(f" [补全] 检测到生产部门为空,开始自动填充...") - - # A. 填入生产部门 - dep_input.fill("压力表车间") - print(" [补全] -> 生产部门已填入: CQ030101") - - # B. 填入订单类型 (仅在需要补全生产部门时才操作这个,根据你的需求逻辑) - type_input = get_input_by_label(frame, "订单类型") - if type_input: - type_input.fill("55C2-Cxx-02") - print(" [补全] -> 订单类型已填入: 55C2-Cxx-02") - else: - print(" [警告] 无法找到'订单类型'输入框") - - # 稍微等待一下填入值的生效(可选) - time.sleep(0.5) - else: - print(f" [信息] 生产部门已有值: {current_val},跳过补全") - else: - print(" [警告] 无法找到'生产部门'输入框,跳过检查") - - except Exception as logic_e: - print(f" [异常] 数据补全逻辑出错 (不影响继续点击): {logic_e}") - # ========================================== - - - # --- 3. 执行点击 --- - button.click() - click_count += 1 - print(f"[{time.strftime('%H:%M:%S')}] 第{click_count}次点击'{button_name}'") - - # --- 4. 智能等待加载逻辑 --- - - # 4.1 缓冲等待:点击后,UI可能需要几秒钟才会弹出遮罩 - # print(" - 等待遮罩层响应...") - time.sleep(2) - - # 4.2 检测并等待遮罩消失 - try: - # 尝试等待遮罩出现 (给它3秒的时间被检测到) - if loading_locator.is_visible(timeout=3000): - print(" - 检测到'加载中',正在等待数据载入...") - # 只要出现了,就无限等待它消失 (timeout=0) - loading_locator.wait_for(state="hidden", timeout=0) - print(" - 数据载入完毕") - else: - # 如果3秒内没检测到 visible,可能是数据极少瞬间加载完了 - pass - except Exception as e: - # 忽略定位超时等非致命错误 - pass - - # 稍微休息一下 - time.sleep(0.5) - - except Exception as e: - elapsed = time.time() - start_time - print(f"操作异常: {e}") - return { - "success": False, - "click_count": click_count, - "error": str(e) - } - - -def get_input_by_label(frame, label_text: str, label_locator=None): - """ - 通过标签文本获取对应的输入框对象 - - 参数: - frame: iframe对象 - label_text: 标签文本(用于查找和日志输出) - label_locator: 可选,已经定位好的标签locator。如果为None,则函数内部查找 - - 返回: - 成功:返回输入框的locator对象 - 失败:返回None - """ - try: - # 如果没有传入label_locator,则根据label_text查找 - if label_locator is None: - label_locator = frame.locator("div").filter(has_text=re.compile(f"^{label_text}$")).first - print(f"找到{label_text}标签") - - # 向上找到包含标签和输入框的共同父容器 - parent_container = label_locator.locator("..") # 父元素 - - # 在父容器中查找输入框 - input_box = parent_container.locator("input").first - - # 如果父元素中没有,再向上一层 - if input_box.count() == 0: - print(f"{label_text}: 在父元素中未找到,向上一层查找...") - grandparent = parent_container.locator("..") # 祖父元素 - input_box = grandparent.locator("input").first - - # 验证是否找到输入框 - if input_box.count() > 0: - input_box.wait_for(state="visible", timeout=5000) - current_value = input_box.input_value() - print(f"✓ {label_text}: {current_value}") - print(input_box.count()) - return input_box - else: - print(f"✗ {label_text}: 在父容器中找不到输入框") - return None - - except Exception as e: - print(f"✗ {label_text}失败: {e}") - return None - -def run(playwright: Playwright) -> None: - browser = playwright.chromium.launch(headless=False) - context = browser.new_context(ignore_https_errors=True) - page = context.new_page() - - # 1. 登录主页面 - try: - page.goto("https://68.11.34.30:8082/yonbip/resources/uap/rbac/login/main/index.html") - - # 提取主 iframe - main_frame = page.locator("#forwardFrame").content_frame - - main_frame.get_by_role("textbox", name="用户名").fill("BLDpengqiangqiang") - main_frame.get_by_role("textbox", name="密码").fill("Cqbld123456.") - main_frame.get_by_role("button", name="登录").click() - - # 2. 处理强制登录弹窗(判断是否存在) - confirm_btn = main_frame.get_by_role("button", name="确定") - try: - # 短暂等待检测弹窗 - if confirm_btn.is_visible(timeout=3000): - confirm_btn.click() - print("强制登录") - else: - print("正常登录") - except: - print("正常登录 (未检测到弹窗)") - - # 3. 点击打开“补货安排” - main_frame.locator("i").first.click() # 假设是某个新建按钮 - - with page.expect_popup() as page1_info: - main_frame.get_by_title("补货安排", exact=True).click() - page1 = page1_info.value - - # 4. 新页面:等待页面加载完成 + 提取嵌套 iframe - print("Page1 已打开,正在等待内层 iframe 加载...") - page1.wait_for_load_state("domcontentloaded") - - # 提取外层 forwardFrame - outer_frame = page1.locator("#forwardFrame").content_frame - - # 关键:等待内层 #mainiframe 出现并加载 - inner_frame_locator = outer_frame.locator("#mainiframe") - inner_frame_locator.wait_for(state="visible", timeout=15000) - inner_frame = inner_frame_locator.content_frame - - print("Page1 内层 iframe 已加载完成") - - # 填写查询条件 - textbox = inner_frame.get_by_role("textbox", name="期初标识≠") - textbox.fill("") - inner_frame.locator("#rc_select_0").fill("关闭") - - textbox = inner_frame.get_by_role("textbox", name="排产号") - textbox.fill("R") - - inner_frame.get_by_role("textbox", name="单据日期开始日期").fill("2025-12-28") - inner_frame.get_by_role("textbox", name="单据日期结束日期").fill("2025-12-31") - # inner_frame.get_by_text("今日").click() - # inner_frame.get_by_text("今日").click() - - # 点击查询按钮 - inner_frame.locator(".iconfont.icon-chaxun").click() - - # 勾选所有合同 - inner_frame.get_by_role("row", name="序号").get_by_label("").check() - - # 获取订单合计数量 - summary_locator = inner_frame.get_by_text(re.compile(r"合计:\s*\d+\s*行")) - try: - summary_text = summary_locator.inner_text(timeout=5000) - match = re.search(r"\d+", summary_text) - if match: - row_count = int(match.group()) - print(f"当前总行数:{row_count}") - else: - print("未匹配到行数") - except: - print("未找到合计行") - - # 进入 Page2 - inner_frame.get_by_role("button").filter(has_text="补货安排").hover() - inner_frame.get_by_text("生产订单").click() - inner_frame.get_by_role("textbox", name="工厂").fill("10010705") - - with page1.expect_popup(timeout=60000) as page2_info: - inner_frame.get_by_role("button", name="确定(Y)").click() - page2 = page2_info.value - - # --- Page2 加载逻辑优化 --- - print("Page2 已打开,正在初始化...") - page2.wait_for_load_state("domcontentloaded") - - outer_frame_p2 = page2.locator("#forwardFrame").content_frame - inner_frame_locator_p2 = outer_frame_p2.locator("#mainiframe") - inner_frame_locator_p2.wait_for(state="visible", timeout=15000) - inner_frame_p2 = inner_frame_locator_p2.content_frame - print("Page2 基础框架已就绪") - - # 1. 按照建议,等待3秒,让加载遮罩有机会出现 - print("等待 7 秒,检测加载遮罩是否出现...") - time.sleep(7) - - # 2. 定义加载控件定位器 - # 注意:这里直接基于 inner_frame_p2 构建,对应你的 path: ...locator("#mainiframe").content_frame.locator("div")... - loading_locator = inner_frame_p2.locator("div").filter(has_text="加载中").nth(1) - - try: - # 尝试等待加载控件变为 visible。 - # 给它 3秒 的检测时间,如果这 3秒 内出现了,说明页面正在加载数据。 - # 如果没出现,触发 TimeoutError,进入 except 块,说明数据量很少,可能瞬间加载完了。 - loading_locator.wait_for(state="visible", timeout=3000) - - print("检测到'加载中'控件,开始等待数据加载(时间不定)...") - - # 3. 关键:等待控件消失 (state="hidden" 或 "detached") - # timeout=0 表示无限等待,防止数据量大加载时间过长导致报错 - loading_locator.wait_for(state="hidden", timeout=0) - - print(">>> 页面加载完毕!(加载控件已消失)") - - except PWTimeoutError: - print(">>> 页面加载完毕!(未检测到'加载中'控件,可能已快速完成)") - except Exception as e: - print(f">>> 页面加载状态判断异常: {e},默认视为加载完毕") - - # --- 这里可以继续 Page2 的后续操作 --- - - input_box = get_input_by_label(inner_frame_p2, "生产部门") - input_box = get_input_by_label(inner_frame_p2, "订单类型") - input_box = get_input_by_label(inner_frame_p2, "数量") - - print("\n>>> 开始执行连续点击操作 <<<") - - # 这里的 inner_frame_p2 是你在 Page2 提取出来的内层 iframe - result = click_button_until_disappear( - frame=inner_frame_p2, - button_name="保存提交", # 修改为你实际要点击的按钮名字,比如 "下一页" 或 "加载更多" - max_attempts=500 # 防止死循环,设置一个最大上限 - ) - print("进入调试模式,你现在可以在终端输入 Python 代码来测试定位器...") - pdb.set_trace() # <--- 程序会在这里卡住,控制权交给终端 - print("最终结果:", result) - input("操作完成,按回车关闭...") - - finally: - context.close() - browser.close() - -with sync_playwright() as playwright: - run(playwright) \ No newline at end of file