项目介绍
引言
在现代Web开发和测试领域,无头浏览器已经成为不可或缺的工具。它们能够在没有图形用户界面的情况下加载并渲染网页内容,广泛应用于自动化脚本、网页测试、网页截屏及爬虫等场景。然而,传统的无头浏览器部署和管理方式往往复杂且耗时。为了解决这一问题,Browserless项目应运而生。
本文展示了Browserless的技术细节,同时给出了实际操作的指南,希望对这个项目感兴趣的人带来帮助。
项目特点
- 支持多种无头浏览器:如Chrome和Firefox,确保了广泛的兼容性和适用性。
- 简单的API调用:用户可以通过API远程控制浏览器执行页面访问、操作DOM、执行JavaScript代码等操作,非常适合构建自动化测试套件、网页数据抓取服务或是任何需要与Web交互的后端服务。
访问GitHub仓库(https://github.com/browserless/browserless ),可以获取更多技术细节、文档和支持信息。
部署
我这里直接采用 zeabur 平台来部署 browserless,地址:https://zeabur.com/zh-CN。
当然,直接用 docker 部署也是可行的,点击查看
$ docker run \
--rm \
-p 3000:3000 \
-e "CONCURRENT=10" \
-e "TOKEN=6R0W53R135510" \
ghcr.io/browserless/chromium
操作方法:
「新建项目」,选择核实的地区,搜索「Browserless」,启动服务,生成一个免费域名
- 我这里选的 Frankfurt(德国比美国更快一点;如果选上海的实例,需要域名备案才能对外暴露访问;)
- Browserless 的域名:happy-holiday-bro.zeabur.app
- 在环境变量中,记录 API 调用凭据(
PASSWORD变量)




使用
我们试试访问这个网站:happy-holiday-bro.zeabur.app,你会发现打不开,显示的是404错误。这是咋回事呢?
happy-holiday-bro.zeabur.app

其实啊,Browserless这个服务,它不像普通网站那样可以直接在浏览器里使用。要想用它,你得通过特定的方法“调用”它。Browserless有两种方式可以和它沟通:
- 第一种方法,是使用已经设置好的**REST API **[ 点击查看 ],这些API能帮你做很多事情,比如生成PDF文件啊,网页截图之类的。
所以,要是你想访问那个网址成功,就得用这些特别的方法去“敲门”,直接输网址是进不去的。 - 第二种方法,是通过SDK进行集成 [ 点击查看 ],例如 Python 下面的Playwright、Pyppeteer、Scrapy、Beautiful Soup

因此,若要成功使用Browserless,关键在于调用,且为了安全,默认需要带上 Token (=环境变量PASSWORD)。
使用示例
本节将介绍两种不同的方法来驱动 Browserless【SDK、Http API】,满足以下几个具体的应用需求:
- 获取浏览器渲染后的动态 HTML源码:这个场景旨在捕获网页在浏览器完全渲染后的最终HTML结构,包括动态加载的内容和JavaScript执行结果,为后续的数据解析与爬取提供准确的基础。
- 提取页面纯文本内容:此场景中,我们希望从网页中提取所有可见文本信息,剔除HTML标签及样式信息,仅保留用户可直接阅读的内容,适用于文本分析、信息汇总等场景。
- 获取网页截图:捕获网页的截图,适用于页面内容变化监控等用途
Http API
浏览器API 可通过 Http REST API,执行特定任务。
- 接口记得添加
<font style="color:rgb(28, 30, 33);">/chrome</font>前缀 —— 如<font style="color:rgb(28, 30, 33);">/content</font>接口的完整路径是<font style="color:rgb(28, 30, 33);">/chrome/</font>content - 完整接口文档请参考:https://docs.browserless.io/open-api#tag/Browser-REST-APIs/paths/
1screenshot%201chromium~1screenshot/post
| 接口路径 | 说明 | Description |
|---|---|---|
/content | 返回动态内容的HTML | Returns HTML of dynamic content |
/unblock | 返回受保护网站的HTML、屏幕截图或cookies | Returns HTML, screenshots, or cookies for protected sites |
/download | 返回Chrome下载的文件 | Returns files Chrome has downloaded |
/function | 在不安装库的情况下运行HTTP请求 | Runs HTTP requests without installing a library |
/pdf | 将页面导出为PDF | Exports a page as a PDF |
/screenshot | 返回.png/.jpg 格式的截图 | Captures a .png or .jpg screenshot |
/scrape | 返回结构化JSON数据 | Returns structured JSON data |
/performance | 运行并行的Google Lighthouse测试以进行性能分析 | Runs parallel Google Lighthouse tests for performance analysis |
:::info a 获取动态 HTML 源码
:::
host = "happy-holiday-bro.zeabur.app"
token = "2****************3"
browserless_uri = f'https://{host}/chrome/content?token={token}'
def get_html(url):
post_data = {
"url": url,
"gotoOptions": {"waitUntil": "networkidle2" },
}
print(post_data)
response = requests.post(browserless_uri, json=post_data, headers=header)
return response.text
url = "https://www.baidu.com"
html = get_html(url)
print(html)
- “
networkidle2” 表示在 500ms 内没有超过 2 个网络连接。

:::info b 提取页面纯文本内容
:::
完整的文档,请看:
和
def get_text(url):
# 构建请求数据
post_data = {
"url": url,
"gotoOptions": {"waitUntil": "networkidle2"}, # 等待网络空闲
"elements": [
{"selector": "body", "timeout": 60000}, # 选择整个body元素,超时时间为60秒
],
"viewport": {"width": 1920, "height": 1080}, # 设置视口大小
"waitForSelector": {
"hidden": True,
"selector": "body",
"timeout": 60000,
"visible": True
} # 等待body元素可见
}
print(post_data)
# 发送POST请求到Browserless API
response = requests.post(f'https://{host}/chrome/scrape?token={token}', json=post_data, headers=header)
_rdata = response.json()
# 提取响应中的数据
data = _rdata['data']
return data
if __name__ == "__main__":
# 示例:抓取Hacker News首页内容
data = get_text("https://news.ycombinator.com/")
# 处理返回的数据
results = data[0]['results'] # 获取第一个结果(通常只有一个,因为我们只选择了body元素)
for r in results:
html = r.get('html') # 获取HTML内容
text = r.get('text') # 获取纯文本内容
print(text) # 打印纯文本内容
print("Html=", len(html), "Text=", len(text))
执行效果

:::info c 获取网页截图
:::
def get_screen_shot(url):
post_data = {
"url": url,
"gotoOptions": {"waitUntil": "networkidle2" },
"options": {
"fullPage": True,
"omitBackground": True,
}
}
print(post_data)
response = requests.post(f'https://{host}/chrome/screenshot?token={token}', json=post_data, headers=header)
return response.content
if __name__ == "__main__":
url = "https://cn.bing.com/search?pglt=163&q=browserless"
r = get_screen_shot(url)
# save file to local
print(r)
with open("img/sdk_screen.png", "wb") as fs:
fs.write(r)
执行效果

SDK
大多数库都允许您指定要与之交互的 Chrome 远程实例。他们要么在寻找 websocket 接口地址、主机和端口,要么在寻找某个 URL 地址。Browserless 默认支持这些。
这里以 Python 为例,使用方法可参考这篇文档
- python-playwright 文档: https://docs.browserless.io/libraries/playwright#python-playwright
:::info a 获取动态 HTML 源码
:::
host = "happy-holiday-bro.zeabur.app"
token = "2****************3"
args = {
"--window-size=1920,1080",
"--lang=en-US",
}
browserless_uri = f'wss://{host}/chrome/playwright?token={token}&--lang=zh-CN&--window-size=1920,1080'
def get_html_source(url) -> str:
# 打印将要获取的URL
print(f"Will GET {url}")
# 使用Playwright创建一个同步上下文
with sync_playwright() as p:
# 打印browserless的URI
print(browserless_uri)
# 连接到远程浏览器
browser = p.chromium.connect(browserless_uri)
# 创建一个新的浏览器上下文
context = browser.new_context()
# 在上下文中创建一个新页面
page = context.new_page()
# 导航到指定的URL,等待直到DOM内容加载完成
page.goto(url, wait_until='domcontentloaded')
# 获取页面的HTML源代码
html_source = page.content()
# 关闭浏览器上下文
context.close()
return html_source
# demo
html = get_html_source('https://shop35928747.taobao.com/?spm=pc_detail.29232929/evo365560b447259.shop_block.dshopinfo.744f7dd6PK7NQa')
print(html)
self.assertIn("GOGOSOON禾子先生", html)
:::info b 提取页面纯文本内容
:::
关键代码:text_content = page.locator('body').inner_text()
原理:这行代码是用来获取页面上所有可见的文本,包括标题、段落、列表等。让我们逐步分解它:
page.locator('body'):page是 Playwright 中表示一个网页的对象。locator()方法用于在页面中定位元素。'body'是一个 CSS 选择器,指向 HTML 文档的<body>标签。- 所以
page.locator('body')返回一个定位器对象,指向页面的 body 元素。
.inner_text():- 这是 Playwright 中的一个方法,用于获取元素的内部文本内容。
- 它类似于 JavaScript 中的
innerText属性。 inner_text()返回元素及其所有子元素的可见文本内容,不包括隐藏元素的文本。- 它会保留文本的格式,如换行和空格。
- 原理:
- 这行代码首先定位到页面的
<body>元素。 - 然后获取
<body>元素及其所有可见子元素的文本内容。 - 这样做可以获取页面上所有可见的文本,包括标题、段落、列表等。
- 这行代码首先定位到页面的
:::info c 获取网页截图
:::
下面,我们在一份代码中,同时实现「获取页面纯文本」和「网页截图」
def get_text(url):
# 打印将要获取的URL
print(f"Will GET {url}")
with sync_playwright() as p:
# 打印browserless的URI
print(browserless_uri)
# 连接到远程浏览器
browser = p.chromium.connect(browserless_uri)
# 创建一个新的浏览器上下文
context = browser.new_context()
# 在上下文中创建一个新页面
page = context.new_page()
# 导航到指定的URL,等待直到网络空闲
page.goto(url, wait_until='networkidle')
# 获取页面body元素的文本内容
text_content = page.locator('body').inner_text()
# 截图并保存
page.screenshot(path=f'img/1.png')
# 全屏截图
# page.screenshot(path=f'img/1-full.png', full_page=True)
# 关闭浏览器上下文
context.close()
return text_content
text = get_text('https://cn.bing.com/search?q=ajax+demo++vulpweb&qs=n&form=QBRE&sp=-1&lq=0&pq=ajax+demo++vulpweb&sc=0-18&sk=&cvid=801DE93231A94BDEAAA48CC50A688E31&ghsh=0&ghacc=0&ghpl=')
print(text)
获取页面文字

截图(只截 1 屏)-默认参数

全屏截图-指定full_page=True

小结
page.goto(url, wait_until='domcontentloaded')
wait_until有几种可选的状态,使用场景如下"commit",含义:在收到页面的第一个字节时完成。使用场景:当您只需要确认服务器已经开始响应,而不需要等待页面完全加载时使用。这是最快的选项,但页面内容可能还没有完全加载。"domcontentloaded",在 DOMContentLoaded 事件触发时被认为完成。使用场景:当您需要 DOM 结构已经构建完成,但不需要等待所有资源(如图片、样式表)加载完成时使用。这对于需要快速访问 DOM 结构但不关心外部资源的情况很有用。"load",在 load 事件触发时被认为完成,当您需要页面的所有资源(包括图片、样式表等)都加载完成时使用。这是最全面的选项,"networkidle",在网络连接至少 500 毫秒没有活动时被认为完成。当页面可能有延迟加载的内容或 AJAX 请求时使用。这对于确保所有动态内容都已加载完成很有用,但可能是最慢的。
爬虫最常用的是:load、networkidle
- SDK 的完整接口文档在这里:https://docs.browserless.io/open-api#tag/Browser-REST-APIs/paths/
1screenshot%201chromium~1screenshot/post

- REST Api 的 query 参数,还可以指定「是否拦截广告」(blockAds 参数)——是否为本次会话加载广告拦截扩展。目前使用的是uBlock Origin,可能会导致某些网站无法正常加载。
例如
response = requests.post(f'https://{host}/chrome/scrape?token={token}&blockAds=true'
这个Browserless项目,它确实是个很有创意的玩意儿。Browserless能够结合Docker和无头浏览器的优势,为开发者和测试工程师提供这么一个灵活的工具,部署简单,使用方便,确实挺吸引人的。
支持
如果你感觉有一些收获,想给予我一些鼓励和支持,可以试试通过下面的推荐链接来注册哦。
将来,如果你决定尝试它们的付费服务,我的账户将会得到五美金(或三十五人民币)的现金抵扣。