Undetected-Chromedriver 的继任者。提供一个极速框架,用于网络自动化、网络爬虫、机器人以及任何其他通常被 Captcha / CloudFlare / Imperva / hCaptcha 等烦人反机器人系统阻碍的创意想法。
直接通信提供了对 Web 应用防火墙(WAF)更强的抵抗力,同时性能获得了巨大提升。 与 undetected-chromedriver 不同,这个模块是完全异步的。
使这个包与其他已知包不同的地方在于,它针对大多数反机器人解决方案进行了优化以保持不被检测。
另一个重点是易用性和快速原型设计,因此很多功能开箱即用,大多数方法参数都设有最佳实践默认值。
只需 1 到 2 行代码即可启动并运行,默认提供最佳实践配置。它会在运行后清理创建的(配置文件)文件。
已知兼容以下浏览器:
虽然易用性和便利性很重要,但也可以通过使用全套 CDP 域、方法和事件轻松实现完全自定义。
无需 chromedriver 二进制文件或 Selenium 依赖
一行代码即可启动并运行*
每次运行使用新的配置文件,退出时自动清理
将 cookies 保存/加载到文件,以避免重复繁琐的登录步骤
tab.find("sometext")
tab.find_all("sometext")
tab.select("a[class*=something]")
tab.select_all("a[href] > div > img")
智能且高性能的元素查找,支持按选择器或文本查找,包括 iframe 内容。
这也可以用作元素出现的等待条件,因为它会在超时时间内重试直到找到。因此,await tab.select('body') 可以用于判断页面是否加载完成。
find 方法按文本搜索,但不会朴素地返回第一个匹配元素,而是根据文本长度(最短者获胜)匹配候选元素,这使得像 tab.find('accept all') 这样的查找能够返回实际的 cookie 按钮,而不是 header 中的脚本。
可以连接到正在运行的 Chrome 调试会话
元素具有描述性的 __repr__,以 HTML 形式呈现元素
提供实用函数,可将正在运行的 undetected_chromedriver.Chrome 实例转换为 nodriver.Browser 实例并继续使用
包含大量针对最常用和重要操作的辅助方法和工具
部分代码已重写,使用协议中的扁平连接。
为什么?
- iframe 包含在大多数操作中。
- tab 有了新方法:await tab.get_frames()
它将返回可检查的 Iframe。
- find() 将包含 iframe,因此你甚至可以搜索 "verify you are human" 并点击 JS 挑战中的验证复选框。
由于这需要相当多的重写,请全面测试,尤其是在运行大型项目时。
tab.xpath(selector, timeout=2.5)使用 XPath 选择器查找节点! 参见 API 文档中的 tab xpath
tab.cf_verify()找到复选框并成功点击 这仅在非专家模式下有效。 目前仅内置英文 需要安装 opencv-python 包
tab.bypass_insecure_connection_warning()便捷方法,用于不安全的页面警告。 例如,当证书无效时。
tab.open_external_debugger()让你检查 tab 而不中断连接
tab.get_local_storage()获取 localstorage 内容
tab.set_local_storage(dict)设置 localstorage 内容
tab.add_handler(someEvent, callback)回调可以接受一个参数(事件),或两个参数(事件,tab)。
start(expert=True)为更有经验的用户提供一些“黑客”手段。它会禁用 Web 安全性和 origin-trials,并确保 shadow-root 始终开放。但这会使你更容易被检测到!
你需要在本机上安装 chrome(或某些基于 chromium 的浏览器) 最好是在默认位置 才能使用这个包。
当在无显示器的机器上运行时,例如 AWS 或任何其他没有显示器的环境,最好使用 Xvfb 工具来模拟 屏幕。 另外,这个包也可以在无头模式下使用。
pip install nodriver
pip install -U nodriver
这个项目(就像很久以前的 undetected-chromedriver)的目标是保持简短和简单,这样你可以快速打开编辑器或交互式会话,输入或粘贴几行代码然后开始。
import nodriver as uc
async def main():
browser = await uc.start()
page = await browser.get('https://www.nowsecure.nl')
... 进一步代码 ...
if __name__ == '__main__':
# 因为 asyncio.run 对我来说从未生效
uc.loop().run_until_complete(main())
这里省略异步样板代码
from nodriver import *
browser = await start(
headless=False,
user_data_dir="/path/to/existing/profile", # 指定后,不会在结束时自动清理
browser_executable_path="/path/to/some/other/browser",
browser_args=['--some-browser-arg=true', '--some-other-option'],
lang="en-US" # 可设置 navigator 中的 ISO 语言代码,不建议更改
)
tab = await browser.get('https://somewebsite.com')
这里省略异步样板代码
from nodriver import *
config = Config()
config.headless = False
config.user_data_dir="/path/to/existing/profile", # 指定后,不会在结束时自动清理
config.browser_executable_path="/path/to/some/other/browser",
config.browser_args=['--some-browser-arg=true', '--some-other-option'],
config.lang="en-US" # 可设置 navigator 中的 ISO 语言代码,不建议更改
)
import nodriver
async def main():
browser = await nodriver.start()
page = await browser.get('https://www.nowsecure.nl')
await page.save_screenshot()
await page.get_content()
await page.scroll_down(150)
elems = await page.select_all('*[src]')
for elem in elems:
await elem.flash()
page2 = await browser.get('https://twitter.com', new_tab=True)
page3 = await browser.get('https://github.com/ultrafunkamsterdam/nodriver', new_window=True)
for p in (page, page2, page3):
await p.bring_to_front()
await p.scroll_down(200)
await p # 等待事件处理
await p.reload()
if p != page3:
await p.close()
if __name__ == '__main__':
# 因为 asyncio.run 对我来说从未生效
uc.loop().run_until_complete(main())
自动化 Twitter/X 账号创建
一个更具体的示例,可以在 ./example/ 文件夹中找到,
展示了一个创建 Twitter 账号的脚本
```python
import random
import string
import logging
logging.basicConfig(level=30)
import nodriver as uc
months = [
"january",
"february",
"march",
"april",
"may",
"june",
"july",
"august",
"september",
"october",
"november",
"december",
]
async def main():
driver = await uc.start()
tab = await driver.get("https://twitter.com")
# 等待文本出现,而不是固定的等待秒数
# 由于速度原因,这不一定总能按预期工作
print('正在查找“创建账号”按钮')
create_account = await tab.find("create account", best_match=True)
print('"创建账号" => 点击')
await create_account.click()
print("正在查找邮箱输入字段")
email = await tab.select("input[type=email]")
# 有时邮箱字段不显示,而是要求输入手机号
# 当出现这种情况时,查找“改用邮箱”的小字
if not email:
use_mail_instead = await tab.find("use email instead")
# 并点击它
await use_mail_instead.click()
# 现在重新查找邮箱字段
email = await tab.select("input[type=email]")
randstr = lambda k: "".join(random.choices(string.ascii_letters, k=k))
# 向邮箱字段发送键
print('正在填写“邮箱”输入字段')
await email.send_keys("".join([randstr(8), "@", randstr(8), ".com"]))
# 查找名字输入字段
print("正在查找名字输入字段")
name = await tab.select("input[type=text]")
# 再次发送随机文本
print('正在填写“名字”输入字段')
await name.send_keys(randstr(8))
# 由于页面上有 3 个选择字段,我们可以使用解包
# 分别赋值给每个字段
print('一次性查找“月”、“日”和“年”字段')
sel_month, sel_day, sel_year = await tab.select_all("select")
# await sel_month.focus()
print('正在填写“月”输入字段')
await sel_month.send_keys(months[random.randint(0, 11)].title())
# await sel_day.focus()
# 不想处理每个月的长度和闰年问题
print('正在填写“日”输入字段')
await sel_day.send_keys(str(random.randint(0, 28)))
# await sel_year.focus()
# 不想处理年龄限制
print('正在填写“年”输入字段')
await sel_year.send_keys(str(random.randint(1980, 2005)))
await tab
# 同样处理 cookie 提示
cookie_bar_accept = await tab.find("accept all", best_match=True)
if cookie_bar_accept:
await cookie_bar_accept.click()
await tab.sleep(1)
next_btn = await tab.find(text="next", best_match=True)
# for btn in reversed(next_btns):
await next_btn.mouse_click()
print("休眠 2 秒")
await tab.sleep(2) # 直观地查看当前处于哪一步
print('正在查找“下一步”按钮')
next_btn = await tab.find(text="next", best_match=True)
print('点击“下一步”按钮')
await next_btn.mouse_click()
# 先等待某个按钮出现,然后再继续
await tab.select("[role=button]")
print('正在查找“注册”按钮')
sign_up_btn = await tab.find("Sign up", best_match=True)
# 需要第二个
print('点击“注册”按钮')
await sign_up_btn.click()
print('其余“实现”超出范围')
# 进一步实现超出范围
await tab.sleep(10)
driver.stop()
# 通过邮件接收验证码
if __name__ == "__main__":
# 因为 asyncio.run 对我来说从未生效
# 我使用
uc.loop().run_until_complete(main())