ruyipage 进阶功能
进阶篇覆盖 Cookie、标签页、网络拦截、iframe、Shadow DOM、窗口、提示框、PDF、高级输入,以及环境模拟、用户上下文、事件系统、数据采集、扩展与下载、代理、接管浏览器、拟人化、智能指纹、调试诊断和 JS 调试器等高级能力。
9. Cookie 管理
参考示例 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/save | 01 |
| 元素查找 | ele() / eles() | 02 |
| 元素交互 | click_self/input/select/hover... | 03 |
| 等待 | page.wait.* | 04 |
| 动作链 | page.actions.* | 05, 20 |
| 截图 | page.screenshot() | 06 |
| JS | run_js / eval_handle / 预加载 | 07 |
| Cookie | get/set/delete_cookies | 08 |
| 标签页 | new_tab/get_tab/close_other_tabs | 09 |
| 滚动 | page.scroll.* | 10 |
| 网络拦截 | page.intercept.* | 11, 18, 34 |
| 控制台 | page.console.* | 12 |
| iframe | get_frame / with_frame | 13 |
| Shadow DOM | with_shadow | 14 |
| 窗口 | page.window.* / rect | 16 |
| 用户提示 | set_prompt_handler / prompt_login | 17 |
save_pdf / pdf | 19 | |
| 高级输入 | combo/press/human/touch/setFiles | 20 |
| 环境模拟 | page.emulation.* | 21, 27 |
| 上下文 | browser_tools / contexts | 25, 26, 37 |
| 事件 | page.events / navigation / realms | 24, 30–33 |
| 数据采集 | network / capture / listen | 28, 40, 54, 53 |
| 扩展 | page.extensions.* | 22 |
| 下载 | page.downloads.* | 23 |
| 代理 | set_proxy / set_fpfile / set_per_tab_proxies | 38, 51, 52 |
| 接管浏览器 | auto_attach_exist_browser_by_process | 39 |
| XPath Picker | xpath_picker=True | 42–44 |
| 动作可视化 | action_visual=True | 42_2, 49, 50 |
| 拟人行为 | human_move/click/type | 46 |
| 智能指纹 | opts.smart_fingerprint() | 48 |
| 调试诊断 | page.trace / FailureSnapshot | 47 |
| JS 调试 | page.debugger.* | 55, 56 |
常见要点提醒
- 用完关闭:每个脚本
finally里调用page.quit(),避免残留 Firefox 进程。 - 网络拦截必须放行:
beforeRequestSent阶段不处理的请求要显式req.continue_request(),否则会挂起。 - Mock 跨域要带 CORS 头:mock 响应时加上
access-control-allow-origin,否则浏览器拦截。 - 原生优先、兼容保底:下拉选择
native_only失败后回退compat。 - JS 断点用后台线程:暂停期间 BiDi 调用会阻塞。
- 抓包先 start 再开页:
page.capture/listen无法补抓历史请求。 - 拟人动作 = 更真实:需要
isTrusted=true的物理事件,用page.actions而非 JS 触发。