跳到主要内容

ruyipage 进阶功能

进阶篇覆盖 Cookie、标签页、网络拦截、iframe、Shadow DOM、窗口、提示框、PDF、高级输入,以及环境模拟、用户上下文、事件系统、数据采集、扩展与下载、代理、接管浏览器、拟人化、智能指纹、调试诊断和 JS 调试器等高级能力。

参考示例 08_cookies.py。

# 读取所有 Cookie
all_cookies = page.get_cookies(all_info=True)
for cookie in all_cookies:
print(cookie.name, cookie.value)

# 设置 Cookie
page.set_cookies({
"name": "user_id", "value": "12345",
"domain": "127.0.0.1", "path": "/",
"expiry": int(time.time()) + 3600, # 过期时间(可选)
"httpOnly": True, # HttpOnly(可选)
})

# setter 风格
page.set.cookies({"name": "setter_cookie", "value": "v", "domain": "127.0.0.1", "path": "/"})

# 过滤读取
filtered = page.get_cookies_filtered(name="user_id", all_info=True)

# 简单属性 / 浏览器级
page.cookies # 当前页面 cookies 简写
page.browser.cookies(all_info=False) # 浏览器级 cookies

# 删除
page.delete_cookies(name="api_cookie")
page.delete_cookies() # 不带参数 = 清空全部

10. 标签页管理​

参考示例 09_tabs.py、16_window_management.py。

page.tabs_count # 标签页数量
page.tab_id # 当前标签页 ID
page.tab_ids # 所有标签页 ID 列表

# 新建标签页
tab2 = page.new_tab(tab2_url)
bg_tab = page.new_tab(tab2_url, background=True) # 后台标签页

# 按序号 / 标题 / URL 获取
first_tab = page.get_tab(1)
page.get_tab(title="Example")
all_tabs = page.get_tabs() # 全部

# 切换 / 激活
page.browser.activate_tab(bg_tab)

# 关闭
second_tab.close()
page.close_other_tabs(first_tab) # 关闭其他,保留指定标签页

11. 滚动操作​

参考示例 10_scrolling.py。

# 滚动到顶部 / 底部
page.scroll.to_bottom()
page.scroll.to_top()

# 滚动到元素使其可见
page.scroll.to_see(table_section)

# 元素内滚动
container.scroll.to_bottom()
container.scroll.to_top()
container.scroll.to_see(scroll_target)

# 用 actions 滚动指定像素
page.actions.scroll(0, 500).perform()

# 读取滚动位置
page.run_js("return {x: window.scrollX, y: window.scrollY}")

# 判断元素是否在视口内
table_section.states.is_in_viewport

12. 网络拦截与抓包​

参考示例 11_network_intercept.py、18_advanced_network.py。page.intercept 是核心网络拦截入口。

# 1) 拦截并 Mock 响应
def mock_handler(req):
if "/api/data" in req.url:
req.mock(
'{"status":"ok","data":{"message":"mocked-by-interceptor"}}',
status_code=200,
headers={"content-type": "application/json",
"access-control-allow-origin": "*"}, # 跨域需带 CORS 头
)
else:
req.continue_request() # 不处理的请求必须显式继续

page.intercept.start(mock_handler, phases=["beforeRequestSent"])

# ... 触发请求 ...

page.intercept.stop()

# 2) 阻止请求
def fail_handler(req):
if "/api/data" in req.url:
req.fail()
else:
req.continue_request()

# 3) 修改请求头并继续(headers 支持 dict)
req.continue_request(headers={"X-Ruyi-Demo": "yes", "User-Agent": "RuyiPage/1.0"})

# 4) 队列模式:handler=None,用 wait() 手动取出
page.intercept.start(handler=None, phases=["beforeRequestSent"])
req = page.intercept.wait(timeout=5)
req.continue_request()

# 5) 响应阶段读取并修改
def response_handler(req):
if "/api/data" in req.url:
print(req.response_status)
print(req.response_headers)
req.continue_response(status_code=299, reason_phrase="RuyiModified")
else:
req.continue_response()

page.intercept.start_responses(response_handler)

# 6) 直接读取请求体
body = req.body # 请求体
req.request_id # 请求 ID

# 7) collect_response=True 时一步读取响应体
resp_body = req.response_body

常用拦截方法:

  • page.intercept.start(handler, phases=[...]) — 通用入口
  • page.intercept.start_requests(handler) — 请求阶段快捷方式
  • page.intercept.start_responses(handler) — 响应阶段快捷方式
  • page.intercept.wait(timeout) — 队列模式等待
  • page.intercept.stop() — 停止

13. 控制台监听​

参考示例 12_console_listener.py。

page.console.start() # 启动监听

# ... 触发页面 console.log / warn / error ...

logs = page.console.get() # 读取日志
for log in logs:
print(f"[{log.level}] {log.text}")

error_logs = page.console.get(level="error") # 按级别过滤
waited = page.console.wait(level="error", text="wait-target-message", timeout=5) # 等待指定日志

page.console.clear() # 清空
page.console.stop() # 停止监听

14. iframe 操作​

参考示例 13_iframe.py。

# 三种方式获取 iframe
iframe = page.get_frame("#test-iframe") # 按选择器
iframe = page.get_frame(index=0) # 按序号
iframe = page.get_frame(context_id=iframe.tab_id) # 按 context_id

# 在 iframe 内查找 / 操作 / 执行 JS
iframe.ele("tag:h1")
iframe.ele("#iframe-btn").click_self()
iframe.run_js("return document.body.innerHTML")
iframe.is_cross_origin # 是否跨域

# 切换回主页面:直接用 page 对象即可
page.ele("#main-title")

# 推荐:with_frame 上下文管理器(退出后自动回到主页面)
with page.with_frame("#test-iframe") as frame_ctx:
text = frame_ctx.ele("tag:h1").text

# 退出后仍是主页面上下文

15. Shadow DOM​

参考示例 14_shadow_dom.py。

# open shadow
host_open = page.ele("#host-open-shadow")
with host_open.with_shadow("open") as open_root:
text = open_root.ele("#host-open-text").text

# closed shadow(Firefox 通过 privileged 通道)
host_closed = page.ele("#host-closed-shadow")
with host_closed.with_shadow("closed") as closed_root:
text = closed_root.ele("#host-closed-text").text

# 嵌套:iframe 内 + shadow
with page.with_frame("#outer-iframe") as frame:
inner_open = frame.ele("#inner-open-host")
with inner_open.with_shadow("open") as inner_root:
text = inner_root.ele("#inner-open-text").text

16. 窗口管理​

参考示例 16_window_management.py。

# 读取尺寸/位置
page.rect.window_size # (宽, 高)
page.rect.viewport_size # 视口
page.rect.window_location # 位置
page.rect.page_size # 页面完整大小
page.rect.scroll_position # 滚动位置

# 窗口状态流
page.window.maximize()
page.window.minimize()
page.window.fullscreen()
page.window.normal()

# 设置尺寸 / 位置 / 居中
page.window.set_size(1200, 820)
page.window.set_position(40, 40)
page.window.center()

17. 用户提示框处理​

参考示例 17_user_prompts.py。处理 alert / confirm / prompt。

# 方式一:启动时配置自动策略
opts = FirefoxOptions()
opts.set_user_prompt_handler({
"alert": "accept",
"confirm": "accept",
"prompt": "ignore",
"default": "accept",
})

# 方式二:运行时设置
page.set_prompt_handler(alert="accept", confirm="accept",
prompt="ignore", default="accept", prompt_text="张三")

# 步骤式登录 API:自动填 prompt 的用户名/密码
page.prompt_login("#login-prompt-btn", "alice", "s3cr3t",
trigger="mouse", timeout=2)

# 清空策略
page.clear_prompt_handler()

18. PDF 打印​

参考示例 19_pdf_printing.py。

# 基本打印
page.save_pdf(str(path))

# A4 + 背景
page.save_pdf(str(path), page={"width": 21.0, "height": 29.7}, background=True)

# 横向 + 缩放 + 页边距
page.save_pdf(str(path), orientation="landscape", scale=0.9,
margin={"top": 1.2, "bottom": 1.2, "left": 1.0, "right": 1.0})

# 指定页范围 + shrinkToFit
page.save_pdf(str(path), page_ranges=["1-2"], shrink_to_fit=True)

# 获取 bytes
pdf_bytes = page.pdf(page_ranges=["1"])

19. 高级输入操作​

参考示例 20_advanced_input.py。综合了键盘、鼠标、拟人、触摸、上传等全部输入能力。

# 组合键便捷方法
page.actions.combo(Keys.CTRL, "a").perform() # Ctrl+A
page.actions.combo(Keys.CTRL, "c").perform() # Ctrl+C
page.actions.combo(Keys.CTRL, "v").perform() # Ctrl+V

# 单键按下
page.actions.press(Keys.END).perform()
page.actions.press(Keys.TAB).perform()

# 统一命名的鼠标方法
page.actions.double_click(dbl_btn).perform() # 双击
page.actions.right_click(right_btn).perform() # 右键
page.actions.middle_click(click_btn).perform() # 中键

# 拖拽(高层原生 BiDi,isTrusted=true)
page.actions.drag(start, end, duration=720, steps=20).perform()
page.actions.drag_to(start, end, duration=800, steps=25).perform()
element.drag_to(drop_zone, duration=0.8)

# 滚轮 / 悬停
page.actions.scroll(0, -500).perform()
page.actions.move_to(hover_target).perform()

# Shift+点击
page.actions.key_down(Keys.SHIFT).click(click_btn).key_up(Keys.SHIFT).perform()

# 释放所有动作
page.actions.release_all()

# 相对移动
page.actions.move(50, 30).perform()
page.actions.curr_x / page.actions.curr_y # 当前坐标

# 拟人化操作(isTrusted=true)
page.actions.human_move(click_btn).human_click().perform()
page.actions.human_type("拟人输入").perform()
page.actions.type("带间隔输入", interval=50).perform()

# 元素级方法
element.double_click()
element.right_click()
element.hover()
element.drag_to(drop_zone, duration=0.8)
element.click.left() # 细分点击

# 触摸操作
page.touch.tap(click_btn).perform()
page.touch.long_press(hover_target, duration=600).perform()

# 文件上传
page.ele("#file-input").input(r"绝对路径\file.txt")
page.ele("#multiple-files").input([str(f1), str(f2)]) # 多文件

# isTrusted 验证
page.is_trusted('click')
page.is_trusted('keydown')
page.is_trusted('mouseenter')

向后兼容别名:db_click() 等价于 double_click(),r_click() 等价于 right_click()。

20. 环境模拟 Emulation​

参考示例 21_emulation.py、21_mobile_google_emulation.py、27_emulation_advanced.py。

# UA 覆盖
page.emulation.set_user_agent(custom_ua)

# 地理位置
page.emulation.set_geolocation(39.9042, 116.4074, accuracy=100)

# 时区 / 语言
page.emulation.set_timezone("America/New_York")
page.emulation.set_locale("ja-JP")

# 屏幕方向 / 屏幕尺寸 / DPR
page.emulation.set_screen_orientation("landscape-primary", angle=90)
page.emulation.set_screen_size(1920, 1080, device_pixel_ratio=2.0)

# 网络 / 触摸 / JS 开关 / 滚动条 / 强制颜色 / CSP
page.emulation.set_network_offline(True)
page.emulation.set_touch_enabled(True, max_touch_points=5, scope="context")
page.emulation.set_javascript_enabled(True)
page.emulation.set_scrollbar_type("overlay")
page.emulation.set_forced_colors_mode("dark")
page.emulation.set_bypass_csp(True)

# 移动端一键预设
page.emulation.apply_mobile_preset(
iphone_ua, width=390, height=844, device_pixel_ratio=3.0,
orientation_type="portrait-primary", angle=0,
locale="en-US", timezone_id="America/Los_Angeles", touch=False,
)

注意:部分命令(如触摸、离线)依赖具体 Firefox 版本实现,示例中返回 False 表示当前版本不支持。

21. 浏览器上下文与用户上下文​

参考示例 25_browser_user_context.py、26_browsing_context_advanced.py、37_three_isolated_user_context_tabs.py。

用户上下文(user context) 用于在同一个浏览器内隔离 Cookie / 会话。

# 创建 / 查询 / 删除用户上下文
before = page.browser_tools.get_user_contexts()
user_context_id = page.browser_tools.create_user_context()
new_tab = page.browser_tools.create_tab(user_context=user_context_id)
page.browser_tools.remove_user_context(user_context_id)

# 典型应用:多个隔离页面,各自登录态不互串
for _ in range(3):
ctx = page.browser_tools.create_user_context()
tab_id = page.browser_tools.create_tab(user_context=ctx)

# 每个 tab 独立设置 Cookie,互不影响(见示例37)

BrowsingContext 底层命令:

page.contexts.get_tree() # 上下文树
page.contexts.create_tab() # 新建标签页
page.contexts.create_window() # 新建窗口
page.contexts.close(tab_id) # 关闭
page.contexts.reload() # 刷新
page.contexts.reload(ignore_cache=True) # 强制刷新
page.contexts.set_viewport(800, 600) # 设置视口
page.contexts.set_bypass_csp(True) # 绕过 CSP

客户端窗口状态:

windows = page.browser_tools.get_client_windows()
win_id = windows[0].get("clientWindow")
page.browser_tools.set_window_state(win_id, state="maximized")

22. 事件系统​

ruyipage 基于 BiDi 订阅了大量事件,覆盖导航、网络、脚本、日志、上下文、下载、用户提示等。参考示例 24_navigation_events.py、30_browsing_context_events.py、31_network_events.py、32_script_events.py、33_log_input_events.py。

通用事件订阅(page.events):

# 订阅多个事件
page.events.start([
"network.beforeRequestSent",
"network.responseStarted",
"network.responseCompleted",
"network.fetchError",
"network.authRequired",
], contexts=[page.tab_id])

page.events.clear() # 清空缓存
evt = page.events.wait("network.beforeRequestSent", timeout=5) # 等待某事件
page.events.stop() # 停止

专用订阅器(更便捷):

# 导航事件
page.navigation.start([...])
page.navigation.wait("browsingContext.domContentLoaded", timeout=3)
page.navigation.wait_for_load(timeout=3)
page.navigation.wait_for_fragment("a", timeout=3)

# realm 事件(脚本执行环境)
page.realms.start()
page.realms.list()
page.realms.stop()

# 日志 / 控制台事件
page.console.start()
page.console.wait(timeout=3)
page.console.entries

常见事件名:

  • 导航:navigationStarted、fragmentNavigated、historyUpdated、domContentLoaded、load、navigationCommitted、navigationFailed、navigationAborted
  • 网络:beforeRequestSent、responseStarted、responseCompleted、fetchError、authRequired
  • 脚本:realmCreated、realmDestroyed、message
  • 上下文:contextCreated、contextDestroyed、userPromptOpened、userPromptClosed
  • 下载:downloadWillBegin、downloadEnd
  • 输入:fileDialogOpened
  • 日志:log.entryAdded

23. 网络数据采集​

参考示例 28_network_data_collector.py、40_scraper_packet_capture.py、54_bing_passive_capture.py、53_duckai_eventstream_capture.py。

方式一:DataCollector + listen(手动编排)

from ruyipage import DataCollector, FirefoxPage, InterceptedRequest

collector = page.network.add_data_collector(data_types=["response"])
page.network.set_extra_headers({"X-Test-Collector": "yes"})
page.network.set_cache_behavior("bypass")

page.intercept.start_requests()
page.listen.start("/api/collector")

# ... 触发请求 ...

req = page.intercept.wait(timeout=8) # 捕获请求
req.continue_request()

packet = page.listen.wait(timeout=8) # 等待响应完成事件

data = collector.get(req.request_id, data_type="response") # 按 request_id 取响应体
data.has_data
data.bytes
collector.disown(req.request_id, data_type="response") # 释放
collector.remove() # 移除收集器

方式二:page.capture(被动抓包,最简单)

# 必须先 start,再打开页面(历史请求无法补抓)
page.capture.start("bing.com", method="GET", collect_bodies=True)
page.get(search_url)

# 等待抓包(count=1 返回单个,count>1 返回列表)
result = page.capture.wait(timeout=15, count=5)

packet.request_headers # 请求头(dict,key 小写)
packet.request_body # 请求体
packet.response_status # 响应状态
packet.response_headers # 响应头
packet.response_body # 响应体(自动等待解码)
packet.is_failed

# 历史包
page.capture.steps
page.capture.stop()

方式三:拦截 + collect_response 一步到位(参考 40_scraper_packet_capture.py)

# collect_response=True 自动管理 DataCollector
page.intercept.start(handler=None, phases=["beforeRequestSent"], collect_response=True)
req = page.intercept.wait(timeout=8)
req.continue_request()
resp_body = req.response_body # 无需手动编排,直接读响应体

24. 浏览器扩展与下载​

参考示例 22_web_extension.py、23_download.py。

WebExtension 安装 / 卸载:

# 安装目录扩展
ext_id = page.extensions.install(ext_dir)
page.extensions.uninstall(ext_id)

# 安装打包的 XPI
ext_id = page.extensions.install_archive(xpi_path)
page.extensions.uninstall(ext_id)
page.extensions.uninstall_all() # 卸载全部

下载管理:

# 允许下载并指定目录
page.downloads.set_behavior("allow", path=download_path)

# 订阅下载事件
page.downloads.start()

# 触发下载后等待事件链
begin, end = page.downloads.wait_chain(filename="test.txt", timeout=5)
begin.suggested_filename
end.status

# 等待文件落盘
page.downloads.wait_file(text_path, timeout=3)

# deny 模式(阻止下载)
page.downloads.set_behavior("deny")
page.downloads.clear()
page.downloads.stop()

25. 代理配置​

参考示例 38_proxy_auth_ipinfo.py、51_socks5_auth_proxy.py、52_per_tab_socks5_proxy_browserscan.py 及 http_socks5_examples/。

HTTP 代理(fpfile 认证):

opts = FirefoxOptions()
opts.set_browser_path(BROWSER_PATH)
opts.set_proxy("http://host:port")
opts.set_fpfile(FPFILE_PATH) # 内核自动读取 httpauth.username/password
page = FirefoxPage(opts)

fpfile 内容:

httpauth.host:gate.example.com
httpauth.port:1288
httpauth.username:<user>
httpauth.password:<pass>

SOCKS5 密码代理:

page = launch(browser_path=..., user_dir=profile_dir, fpfile=fpfile_path)

fpfile 内容(= 格式):

socksauth.host=host
socksauth.port=1000
socksauth.username=<user>
socksauth.password=<pass>

按标签页分配不同代理(container tabs):

opts = FirefoxOptions()
opts.set_browser_path(browser_path)
opts.set_per_tab_proxies(proxies, exhausted="wrap") # 代理池
page = FirefoxPage(opts)

tabs = page.new_container_tabs(count=len(proxies), url=TARGET_URL)
# 每个 container tab 自动使用不同的代理

说明:按标签页代理需要支持 proxy.rotate.* 的 ruyi 定制 Firefox 指纹内核。

26. 接管已有浏览器​

参考示例 39_attach_exist_browser.py。

from ruyipage import auto_attach_exist_browser_by_process

# 前提:先手工启动 Firefox / 指纹浏览器,并加入参数:
# --remote-debugging-port=9222
# 如果浏览器把固定端口改成随机端口,本函数会自动降级为暴力扫描
page = auto_attach_exist_browser_by_process(
timeout=0.2, max_workers=16, latest_tab=True,
)

print(page.browser.address)
print(page.title)

27. 隐私模式与 user_dir​

参考示例 41_private_mode_userdir.py。

# 方式一:FirefoxOptions
opts = FirefoxOptions()
opts.set_user_dir(USER_DIR) # 指定 profile 目录(与 set_profile 等价)
opts.private_mode(True) # 隐私模式
page = FirefoxPage(opts)

# 方式二:launch 参数
page = launch(headless=False, user_dir=USER_DIR, private=True)

user_dir 可复用既有登录态(保持 Cookie / 会话),示例 44_shopee_userdir_xpath.py 中用于打开已登录的 Shopee 页面。

28. XPath Picker​

参考示例 42_xpath_picker_complex_showcase.py、43_zhipin_xpath_picker.py、44_shopee_userdir_xpath.py。

XPath Picker 是辅助定位复杂节点的工具,启动时打开,可在浏览器中手动点选并校验 XPath。

page = launch(
headless=False,
xpath_picker=True, # 开启 XPath picker
window_size=(1600, 1100),
)

典型用途(示例 44):用 xpath 定位商品列表、翻页按钮并多页采集。

ITEMS_XPATH = "xpath://section[1]/ul[1]/li"
NEXT_PAGE_XPATH = "xpath:/html[1]/body[1]/div[1]/..."

items = page.eles(ITEMS_XPATH, timeout=2)
for item in items:
print(item.text)

next_btn = page.ele(NEXT_PAGE_XPATH, timeout=5)
next_btn.click_self()

29. 鼠标行为可视化​

参考示例 42_2_action_visual_showcase.py、49_human_click_visual_grid.py、50_human_click_visual_points.py。

开启 action_visual=True 后,浏览器内会实时显示鼠标轨迹、点击位置扩散动画、目标元素高亮框、当前坐标等,非常适合调试拟人操作。

page = launch(
action_visual=True, # 开启可视化
headless=False,
port=9334,
window_size=(1400, 900),
)

# 各种移动轨迹都能可视化
page.actions.move_to((200, 200)).perform()
page.actions.human_move((800, 200)).perform()
page.actions.move(100, 0).perform()

30. 拟人化行为​

参考示例 46_human_behavior_showcase.py。ruyipage 内置两种拟人鼠标轨迹算法:Bezier 和 WindMouse,配合 action_visual 可直观观察。

opts = FirefoxOptions()
opts.enable_action_visual(True)
opts.set_human_algorithm("bezier") # 默认算法
opts.set_window_size(1440, 980)
page = FirefoxPage(opts)

# 按算法与风格移动 + 点击 + 输入
page.actions.human_move(page.ele("#target-a"),
algorithm="bezier", style="line_then_arc").human_click().perform()
page.actions.human_move(page.ele("#target-b"),
algorithm="windmouse").human_click().perform()
page.actions.human_move(page.ele("#search-input"),
algorithm="bezier", style="arc").human_click().human_type("demo").perform()

拟人动作方法:

  • human_move(target, algorithm=..., style=...) — 拟人移动
  • human_click() — 拟人点击
  • human_type(text) — 拟人输入

31. 智能指纹​

参考示例 48_smart_fingerprint.py。opts.smart_fingerprint() 一键生成指纹与 fpfile,覆盖代理出口 IP、国家 / 时区 / 经纬度、语言、22 套 Windows 真机硬件特征、UA、Canvas/Audio 种子等。

from ruyipage import FirefoxOptions, FirefoxPage

opts = FirefoxOptions()
opts.set_browser_path(BROWSER_PATH)
opts.set_port(9222)

ctx = opts.smart_fingerprint(
proxy_host="127.0.0.1", proxy_port=7890,
proxy_user=None, proxy_pwd=None,
require_country=None, # 出口 IP 国家校验(None=不校验)
logger=print,
)

print(ctx.summary())
page = FirefoxPage(opts)
ctx.apply_emulation(page, logger=print) # 叠加 screen/geolocation/locale/timezone/headers
page.get("https://www.google.com")

异常处理:CountryMismatchError(国家不匹配)、GeoError(geo 探测全部失败)。

32. 调试与诊断​

参考示例 47_trace_and_diagnostics.py。提供 Trace 时间线 和 失败快照(Failure Snapshot) 两套诊断能力。

三种启用方式:

# 方式 A:launch 参数(最简单)
page = launch(trace=True, failure_snapshot=True, snapshot_dir="./diag_output_47")

# 方式 B:FirefoxOptions 链式
opts = FirefoxOptions()
opts.enable_trace(True)
opts.enable_failure_snapshot()
opts.set_snapshot_dir("./diag")
page = FirefoxPage(opts)

# 方式 C:Settings 全局开关
Settings.trace_enabled = True
Settings.trace_max_entries = 2000
Settings.failure_snapshot_enabled = True
Settings.raise_when_ele_not_found = True # 元素找不到时抛异常

Trace 时间线 API:

page.trace.enabled # 是否启用
page.trace.latest(5) # 最近 N 条 TraceEntry
page.trace.recent_requests(10) # 最近 N 条网络请求
page.trace.summary(10) # 人类可读摘要
page.trace.dump_json() # 完整 JSON 导出
page.trace.clear() # 清空缓冲区

# TraceEntry 属性
e.category / e.event / e.data / e.context_id / e.elapsed_ms / e.status
e.to_dict()

失败快照 API(异常自动抓现场):

Settings.raise_when_ele_not_found = True
try:
page.ele("#this-element-does-not-exist", timeout=2)
except ElementNotFoundError as e:
snap = e.diagnostics # FailureSnapshot
snap.error_type / snap.error_message / snap.url / snap.context_id
snap.screenshot_path / snap.dom_path / snap.recent_requests / snap.trace_entries
snap.summary() / snap.to_json() / snap.to_dict()

敏感字段自动脱敏:password、cookie、authorization 等字段在诊断数据中自动替换为 ***,超长字符串自动截断。

33. JS 调试器​

参考示例 55_js_debugger.py、56_ai_autonomous_debug.py。ruyipage 通过 Firefox 自己的 DevTools 通道提供完整 JS 断点调试能力(断点、单步、调用栈、作用域、监视点、事件 / XHR 断点等)。

opts = FirefoxOptions()
opts.set_browser_path(BROWSER_PATH)
opts.enable_debugger()
page = FirefoxPage(opts)
page.get(PAGE)

dbg = page.debugger.start(auto_resume_after=90)

# 源码
dbg.sources(url_contains=SOURCE)
dbg.source_text(SOURCE)
dbg.breakable_lines(SOURCE)

# 断点 / 条件断点
bp = dbg.set_breakpoint(SOURCE, line, condition="i === 0")
dbg.breakpoints

# 等待暂停
state = dbg.wait_paused(timeout=30)
state.why / state.line
dbg.frames() # 调用栈
dbg.scope() # 作用域
dbg.step_into() / step_over() / step_out() / restart_frame()

# 日志断点(不暂停)
dbg.set_breakpoint(SOURCE, line, log_value="i, sum")
dbg.wait_logs(count=3, timeout=10)

# 暂停时检查对象
dbg.expand(probe, depth=1, max_items=20)
dbg.get_property(probe, "double")
dbg.entries(map_obj)
dbg.call(fn, args=[21])
dbg.invoke_getter(probe, "computed")
dbg.promise_state(promise)
dbg.global_object()

# 属性监视点
dbg.watch_property(config, "token", on="set")
dbg.unwatch_property(config)

# 异常暂停 / debugger 语句
dbg.pause_on_exceptions(True, ignore_caught=True)
dbg.pause_on_debugger_statement(True)

# 事件 / XHR 断点
dbg.available_event_breakpoints()
dbg.set_event_breakpoints(["event.mouse.click"])
dbg.set_xhr_breakpoint("/", "ANY")

# 控制
dbg.skip_breakpoints(True) # 跳过所有断点
dbg.blackbox(SOURCE) # 黑盒(不进入)
dbg.include_async_frames(True)
dbg.clear_breakpoints()
dbg.stop()

重要:JS 暂停期间,依赖它的 BiDi 调用(run_js、点击、取文本)会阻塞到超时。触发断点的调用必须放到后台线程里执行(参考示例 55 的 bg() 辅助函数)。

附录​

功能速查表​

能力入口参考示例
启动launch() / FirefoxPage(opts)00, 01, 41
导航get/back/forward/refresh/save01
元素查找ele() / eles()02
元素交互click_self/input/select/hover...03
等待page.wait.*04
动作链page.actions.*05, 20
截图page.screenshot()06
JSrun_js / eval_handle / 预加载07
Cookieget/set/delete_cookies08
标签页new_tab/get_tab/close_other_tabs09
滚动page.scroll.*10
网络拦截page.intercept.*11, 18, 34
控制台page.console.*12
iframeget_frame / with_frame13
Shadow DOMwith_shadow14
窗口page.window.* / rect16
用户提示set_prompt_handler / prompt_login17
PDFsave_pdf / pdf19
高级输入combo/press/human/touch/setFiles20
环境模拟page.emulation.*21, 27
上下文browser_tools / contexts25, 26, 37
事件page.events / navigation / realms24, 30–33
数据采集network / capture / listen28, 40, 54, 53
扩展page.extensions.*22
下载page.downloads.*23
代理set_proxy / set_fpfile / set_per_tab_proxies38, 51, 52
接管浏览器auto_attach_exist_browser_by_process39
XPath Pickerxpath_picker=True42–44
动作可视化action_visual=True42_2, 49, 50
拟人行为human_move/click/type46
智能指纹opts.smart_fingerprint()48
调试诊断page.trace / FailureSnapshot47
JS 调试page.debugger.*55, 56

常见要点提醒​

  1. 用完关闭:每个脚本 finally 里调用 page.quit(),避免残留 Firefox 进程。
  2. 网络拦截必须放行:beforeRequestSent 阶段不处理的请求要显式 req.continue_request(),否则会挂起。
  3. Mock 跨域要带 CORS 头:mock 响应时加上 access-control-allow-origin,否则浏览器拦截。
  4. 原生优先、兼容保底:下拉选择 native_only 失败后回退 compat。
  5. JS 断点用后台线程:暂停期间 BiDi 调用会阻塞。
  6. 抓包先 start 再开页:page.capture / listen 无法补抓历史请求。
  7. 拟人动作 = 更真实:需要 isTrusted=true 的物理事件,用 page.actions 而非 JS 触发。