playwright爬虫记录

6 阅读4分钟

核心总结

  1. 如果页面有多个相似文本,要正确告诉cc, 点击哪个位置, 比如问题1, 多个"导出"文本,导致cc点击错误的地方
  2. 如果某个元素是悬停鼠标后出现新内容, 因为出现新内容要重新渲染DOM, 要告诉CC,避免多次尝试,浪费时间

问题1: 弹窗有多个相似文本

● 问题分析与解决过程总结

  失败原因分析

  ┌──────────┬────────────────────────────────────┬──────┬───────────────────────────────────────────────────┐
  │ 尝试次数 │              点击方式              │ 结果 │                     原因分析                      │
  ├──────────┼────────────────────────────────────┼──────┼───────────────────────────────────────────────────┤
  │ 1        │ locator.click()                    │ ❌   │ 元素在弹窗中,Playwright 的默认可见性检查可能误判 │
  ├──────────┼────────────────────────────────────┼──────┼───────────────────────────────────────────────────┤
  │ 2        │ locator.evaluate(el => el.click()) │ ❌   │ el.click() 不是有效的选择器语法                   │
  ├──────────┼────────────────────────────────────┼──────┼───────────────────────────────────────────────────┤
  │ 3        │ page.mouse.click() 直接点击        │ ❌   │ 点击位置正确,但可能点击速度太快,页面没反应过来  │
  ├──────────┼────────────────────────────────────┼──────┼───────────────────────────────────────────────────┤
  │ 4        │ 监听 page.on('download') 事件      │ ❌   │ 点击没有真正触发,所以监听不到下载事件            │
  ├──────────┼────────────────────────────────────┼──────┼───────────────────────────────────────────────────┤
  │ 5        │ 使用 CDP 监听网络请求              │ ❌   │ 点击没有生效,没有下载相关的网络请求              │
  └──────────┴────────────────────────────────────┴──────┴───────────────────────────────────────────────────┘

  关键发现

  弹窗结构分析后才发现:
  ┌─────────────────────────────────────┐
  │     导出 Word 设置                   │  ← 标题
  ├─────────────────────────────────────┤
  │  内容范围                            │
  │  ○ 导出正文及评论  ● 仅正文          │  ← "仅正文"是默认选项
  ├─────────────────────────────────────┤
  │                        [取消] [导出] │  ← 导出按钮在右下角
  └─────────────────────────────────────┘

  最终成功的三个关键因素

  1. 正确的元素定位
    - 之前用 text=导出 会匹配到多个元素(标题、按钮等)
    - 后来用 button:has-text("导出") 精确定位到按钮元素
  2. 完整的操作流程
    - 需要按顺序点击:菜单 → 悬停"下载为" → Word → 导出
    - 每一步之间需要适当的等待时间(1-5 秒)
  3. 使用鼠标模拟真实点击
  const exportBox = await exportButton.boundingBox();
  await page.mouse.click(
    exportBox.x + exportBox.width / 2,
    exportBox.y + exportBox.height / 2
  );
    - 比 locator.click() 更接近真实用户行为
    - 绕过了 Playwright 的可见性检查问题

  为什么之前截图显示弹窗没消失

  从日志中看到,脚本点击了多个包含"导出"文本的元素:
  点击按钮:导出 Word 设置 at (280, 252.5)  ← 标题,不是按钮
  点击按钮:导出正文及评论 at (400, 349.5)  ← 单选框,不是按钮
  点击按钮:导出 at (896, 411.5)           ← 这才是真正的导出按钮!

  之前点击的是标题或其他元素,真正的导出按钮坐标是 (896, 411.5),只有点击这个位置才触发下载。

  核心教训

  ┌──────────────────────┬──────────────────────────────────────────────┐
  │         问题         │                     教训                     │
  ├──────────────────────┼──────────────────────────────────────────────┤
  │ 多个元素包含相同文本 │ 需要用更精确的选择器(如 button:has-text()) │
  ├──────────────────────┼──────────────────────────────────────────────┤
  │ 弹窗中的元素         │ 需要获取 boundingBox() 后用鼠标点击          │
  ├──────────────────────┼──────────────────────────────────────────────┤
  │ 下载不触发           │ 可能是点击了错误的元素,需要分析完整 DOM     │
  ├──────────────────────┼──────────────────────────────────────────────┤
  │ 等待时间不足         │ 每步操作后需要适当 waitForTimeout()          │
  └──────────────────────┴──────────────────────────────────────────────┘

  一句话总结: 之前失败是因为点击了弹窗标题而不是真正的导出按钮,后来通过分析 DOM 结构找到正确的按钮元素并用鼠标模拟点击才成功。

问题2: 部分元素靠鼠标悬停,而不是点击

  问题根源:悬停菜单(Hover Menu)的特殊性

  这个网站的"下载为"菜单是一个悬停触发的子菜单,不是点击后弹出的对话框。

  失败过程回顾

  尝试 1: 直接查找 text=下载为
  结果:❌ 元素存在但不可见(子菜单未展开)

  尝试 2: 使用选择器 [class*="dropdown"]
  结果:❌ 子菜单还没渲染出来

  尝试 3: 查找 text=Word
  结果:❌ Word 选项在子菜单中,子菜单没展开

  关键发现

  菜单结构:
  右上角菜单按钮 [⋮]
      ↓ 点击后展开
  ┌─────────────────────┐
  │ 下载为  ───────────►│  ← 悬停后才显示子菜单
  │ 打印                │
  │ 另存为版本          │
  └─────────────────────┘
          ↓ 悬停后展开
          ┌──────────────┐
          │ Word         │  ← 这个元素最初不在 DOM 中!
          │ PDF          │
          └──────────────┘

  为什么找不到

  ┌────────────┬─────────────────────────────────────────────────────────────────────────┐
  │    原因    │                                  说明                                   │
  ├────────────┼─────────────────────────────────────────────────────────────────────────┤
  │ 动态渲染   │ 子菜单在悬停前可能根本不在 DOM 中                                       │
  ├────────────┼─────────────────────────────────────────────────────────────────────────┤
  │ 可见性问题 │ 即使元素在 DOM 中,display: none 或 visibility: hidden 也会导致查找失败 │
  ├────────────┼─────────────────────────────────────────────────────────────────────────┤
  │ 时机问题   │ 悬停后需要等待 1-2 秒子菜单才完全渲染                                   │
  └────────────┴─────────────────────────────────────────────────────────────────────────┘

  最终成功的解决方案

  // 步骤 1: 点击菜单按钮
  await page.locator('[data-icon="MoreOutlined"]').first().click();
  await page.waitForTimeout(1000);

  // 步骤 2: 悬停在"下载为"上(关键!)
  const dlBox = await page.locator('text=下载为').first().boundingBox();
  await page.mouse.move(dlBox.x + dlBox.width / 2, dlBox.y + dlBox.height / 2);
  await page.waitForTimeout(2000);  // ← 等待子菜单渲染

  // 步骤 3: 现在才能找到 Word
  const wordOption = page.locator('text=Word').first();
  await wordOption.click();

  核心教训

  ┌─────────────────────────────────────────────────────────┐
  │  悬停菜单的操作顺序:                                    │
  │                                                         │
  │  1. 先触发父菜单(点击)                                 │
  │  2. 悬停在目标项上(mouse.move)                         │
  │  3. 等待子菜单渲染(waitForTimeout)                     │
  │  4. 最后点击子菜单项                                     │
  │                                                         │
  │  缺少任何一步都会导致失败!                              │
  └─────────────────────────────────────────────────────────┘

  对比两种菜单

  ┌────────────────┬────────────────────────┬──────────┐
  │    菜单类型    │          特点          │ 查找难度 │
  ├────────────────┼────────────────────────┼──────────┤
  │ 点击弹出对话框 │ 点击后整个对话框出现   │ 简单     │
  ├────────────────┼────────────────────────┼──────────┤
  │ 悬停子菜单     │ 需要悬停触发,动态渲染 │ 困难     │
  └────────────────┴────────────────────────┴──────────┘

  一句话总结: "下载为"是悬停菜单,需要先悬停等待子菜单渲染完成后才能找到"Word"选项,之前失败是因为没有正确的悬停操作和等待时间。