ruyipage 基础入门
ruyipage 是一个基于 Firefox 的 Python 浏览器自动化库,底层走 WebDriver BiDi(双向协议),封装了大量友好、易用的高层 API。从最简单的「打开网页、点按钮、填表单」到「网络拦截、环境模拟、拟人化操作、JS 断点调试」都覆盖到了。
核心特点:
- 两种启动方式:极简的
launch()与可精细配置的FirefoxOptions + FirefoxPage。 - 元素查找极简:CSS、XPath、文本、属性四种方式,
.ele()/.eles()两种方法。 - 事件驱动:导航、网络、脚本、日志、下载、用户提示等事件都可订阅、等待。
- 原生 BiDi + 拟人增强:物理级鼠标 / 键盘动作(
isTrusted=true),也可用贝塞尔 / WindMouse 拟人轨迹。 - 指纹与代理:内置智能指纹、HTTP/SOCKS5 代理(含按标签页分配)、指纹浏览器接管。
- 调试友好:Trace 时间线、失败快照、XPath Picker、动作可视化、JS 调试器。
安装
pip install ruyipage
# 可选:安装/更新自带的 runtime Firefox
python -m ruyipage install
启动浏览器的两种方式
# 方式一:极简 launch()(推荐新手)
from ruyipage import launch
page = launch(headless=False) # headless=True 则后台运行
# 方式二:FirefoxOptions 精细配置
from ruyipage import FirefoxOptions, FirefoxPage
opts = FirefoxOptions()
opts.headless(False) # 显示浏览器窗口
page = FirefoxPage(opts)
无论哪种方式,用完都要记得 page.quit() 关闭浏览器。
1. 快速上手
参考示例 00_quickstart.py。最少代码完成「访问页面 → 读标题 → 找元素 → 读文本」。
from ruyipage import launch
page = launch(headless=False)
try:
page.get("https://example.com")
page.wait(1) # 暂停 1 秒
print(f"标题: {page.title}")
h1 = page.ele("tag:h1") # 通过标签名找元素
print(f"H1文本: {h1.text}")
finally:
page.wait(1)
page.quit()
2. 页面导航
参考示例 01_basic_navigation.py。
from ruyipage import FirefoxPage, FirefoxOptions
opts = FirefoxOptions()
opts.headless(False)
page = FirefoxPage(opts)
page.get("https://www.example.com") # 导航到 URL
print(page.title) # 标题
print(page.url) # 当前 URL
print(len(page.html)) # 页面 HTML 长度
page.get("https://www.example.com") # 再导航到另一页
page.back() # 后退
page.forward() # 前进
page.refresh() # 刷新
# 保存页面
# 保存页面
save_dir = './save'
page.save(path=str(save_dir), name="example_page", as_pdf=False) # 存 HTML
page.save(path=str(save_dir), name="example_page", as_pdf=True) # 存 PDF
3. 元素查找
参考示例 02_element_finding.py。page.ele() 返回单个元素(找不到时返回空元素而非报错),page.eles() 返回元素列表。
# 1. 通过 ID
title = page.ele("#main-title")
title.text # 文本
title.tag # 标签名
# 2. 通过 class(返回第一个)
page.ele(".test-class")
# 3. 查找所有匹配元素
all_test = page.eles(".test-class")
len(all_test) # 元素数量
# 4. 通过 XPath
link = page.ele('xpath://a[@id="test-link"]')
link.text
link.link # 链接地址
# 5. 通过文本
btn = page.ele("text:点击我")
btn.attr('id') # 取属性
# 6. 通过属性
test_div = page.ele('[data-test="value"]')
test_div.attrs # 所有属性
# 7. 组合选择器(在已有元素内继续查找)
section = page.ele("#form-section")
input_elem = section.ele('input[type="text"]')
# 8. 元素常用属性
img.src
title.size # 尺寸
title.location # 位置
disabled_btn.is_enabled # 是否可用
disabled_btn.is_displayed # 是否显示
4. 元素交互
参考示例 03_element_interaction.py。
# 点击
btn = page.ele("#click-btn")
btn.click_self()
# 输入 / 清空
text_input = page.ele("#text-input")
text_input.input("Hello RuyiPage!")
text_input.clear()
text_input.input("新的内容")
text_input.value # 读取当前值
# 不同类型输入框
page.ele("#email-input").input("test@example.com")
page.ele("#textarea").input("第一行\n第二行\n第三行")
# 复选框 / 单选框
checkbox.is_checked # 是否选中
checkbox.click_self()
# 下拉选择(原生优先,失败回退兼容模式)
select_ok = select.select.by_value("opt2", mode="native_only")
if not select_ok:
select_ok = select.select.by_value("opt2", mode="compat")
select.value # 选中值
selected = select.select.selected_option # 选中项文本
# 双击 / 右键 / 悬停
dbl_btn.double_click()
right_btn.right_click()
hover_target.hover()
# 提交表单
submit_btn.click_self()
5. 等待条件
参考示例 04_wait_conditions.py。页面是动态的,往往需要「等到某个条件成立」再操作。
# 等待元素出现
elem = page.wait.ele("#main-title", timeout=5)
# 等待元素可见
btn = page.wait.ele_displayed("#click-btn", timeout=5)
# 等待元素隐藏 / 删除
is_hidden = page.wait.ele_hidden("#dynamic-content", timeout=3)
is_deleted = page.wait.ele_deleted("#dynamic-content", timeout=3)
# 等待标题包含指定文本
page.wait.title_contains("RuyiPage", timeout=2)
# 等待页面加载完成
page.wait.doc_loaded(timeout=5)
# 简单暂停
page.wait(2)
6. 动作链 Actions
参考示例 05_actions_chain.py。动作链用 page.actions 串联鼠标 / 键盘动作,最后 .perform() 执行。这些动作是物理级的,能产生 isTrusted=true 的事件。
from ruyipage import FirefoxPage, FirefoxOptions, Keys
# 移动并点击
page.actions.move_to(click_btn).click().perform()
# 双击 / 右键
page.actions.move_to(dbl_btn).db_click().perform()
page.actions.move_to(right_btn).r_click().perform()
# 键盘输入
text_input.click_self()
page.actions.type("通过动作链输入的文本").perform()
# 组合键(全选)
page.actions.key_down(Keys.CONTROL).type("a").key_up(Keys.CONTROL).perform()
# 复制粘贴
page.actions.key_down(Keys.CONTROL).type("c").key_up(Keys.CONTROL).perform()
page.actions.key_down(Keys.CONTROL).type("v").key_up(Keys.CONTROL).perform()
# 悬停
page.actions.move_to(hover_target).perform()
# 滚动
page.actions.scroll(0, 500).perform() # 向下 500px
page.actions.scroll(0, -300).perform() # 向上 300px
# 拖拽(物理鼠标序列,Firefox 下先关闭原生 draggable 更稳)
page.run_js("document.getElementById('draggable').setAttribute('draggable', 'false')")
steps = 20
dx, dy = 200, 100
actions = page.actions.move_to(draggable).hold().wait(0.15)
for _ in range(steps):
actions.move(dx / steps, dy / steps, duration=40)
actions.wait(0.1).release().perform()
# 连续动作
page.actions.move_to(click_btn).click().wait(0.3).click().wait(0.3).click().perform()
7. 截图
参考示例 06_screenshot.py。
# 整页截图(保存文件)
page.screenshot(str(full_path))
# 元素截图
title = page.ele("#main-title")
title.screenshot(str(title_path))
# 获取 base64 / bytes
base64_data = page.screenshot(as_base64=True)
bytes_data = page.screenshot(as_bytes=True)
# 先滚动到元素再截图
page.scroll.to_see(btn)
page.scroll.to_bottom()
8. JavaScript 执行
参考示例 07_javascript.py。run_js 在页面主世界执行 JS,可用 arguments[i] 传参,可用 return 返回值。
# 基础表达式
result = page.run_js("return 1 + 2") # 3
title = page.run_js("return document.title")
# 修改页面内容
page.run_js('document.getElementById("main-title").textContent = "标题已被JS修改"')
# 传参
result = page.run_js("return arguments[0] + arguments[1]", 10, 20)
# 在元素上执行 JS(this 指向元素)
btn = page.ele("#click-btn")
btn_text = btn.run_js("function() { return this.textContent; }")
btn.run_js('function() { this.style.background = "red"; }')
# 复杂脚本 + 返回值
result = page.run_js("""
(() => {
const elements = document.querySelectorAll('.test-class');
return Array.from(elements).map(el => el.textContent);
})()
""")
# 事件触发
page.run_js('document.getElementById("click-btn").click()')
# sandbox 隔离执行(as_expr=False 表示按脚本执行而非表达式)
sandbox_value = page.run_js("...", as_expr=False, sandbox="example07")
# 预加载脚本(每次导航前自动注入)
preload_id = page.add_preload_script("""() => {
window.__example07Preload = 'preload-ready';
}""")
page.remove_preload_script(preload_id)