🎯 一款可定制、具备反检测功能的云浏览器,由自主研发的 Chromium驱动,专为网页爬虫AI 代理设计。👉立即试用
返回博客

剧作家 + 无痕抓取浏览器:捕获和重放完整的 HAR 会话

James Thompson
James Thompson

Scraping and Proxy Management Expert

30-Jul-2026

TL;DR:

  • HAR文件是结构化的HTTP存档,而不是截图或视频。 它的log.entries数组存储每个捕获的HTTP请求的对象,包括请求和响应元数据,以及可用时的记录内容。
  • Playwright在浏览器上下文级别记录HAR文件。 创建上下文时设置record_har_path,并在调用context.close()时将存档刷新到磁盘。
  • Scrapeless Scraping Browser提供远程浏览器会话。 Playwright通过CDP与其连接,加载页面,触发动态请求,并将结果流量保存到本地。
  • HAR分析不需要浏览器。 一旦文件存在,Python的标准json模块可以检查其URL、方法、状态、MIME类型、头部和嵌入的响应体。
  • 捕获的请求可以在没有Playwright的情况下重新发出。 该示例在删除HTTP/2伪头并重新协商内容编码后,使用urllib重建一个公共JSON请求。
  • HAR重放有界限。 过期的cookie、CSRF令牌、Bearer凭证、WebSocket帧和变化的服务器数据可能会阻止后续请求再现原始响应。
  • 免费开始。 新的Scrapeless账户包括免费的Scraping Browser运行时 — 在app.scrapeless.com注册

介绍:先捕获,后决定什么重要

HAR文件是浏览器会话期间观察到的HTTP事务的JSON存档。它不是渲染页面的截图、DOM快照或会话视频。

每个捕获的请求作为一个对象出现在:

text Copy
log.entries

一个条目可以包含请求方法、URL、头部、查询参数、邮寄数据、响应状态、响应头、时序信息和录制的响应内容。二进制内容可能以Base64等编码表示,而文本体可以直接出现在条目中。

本指南将Playwright连接到Scrapeless Scraping Browser,通过Chrome DevTools协议记录完整的页面加载和滚动序列,并关闭浏览器。

工作流程的后半部分不需要浏览器:

  1. 使用json检查HAR结构。
  2. 查找捕获的API请求。
  3. 重新构建可重用的头部。
  4. 使用urllib重新发出请求。
  5. 将新的JSON响应与存档中保存的主体进行比较。

每个显示的结果都来自捕获的目标会话。

使用HAR存档可以做什么

HAR捕获在重要的端点在页面加载之前未知时非常有用。

  • 审核完整的页面加载。 审查一个会话中的HTML、样式表、脚本、字体、图像和API请求。
  • 发现内部JSON端点。 捕获后在存档中搜索,而不是预测哪些请求会重要。
  • 调试失败的页面行为。 在浏览器关闭后检查请求URL、响应状态、头部、MIME类型和正文。
  • 保存网络证据。 存储一个可移植的JSON文物,以便稍后分析或转移到另一台机器上。
  • 比较页面加载行为。 捕获不同的会话并比较它们的请求集、状态或响应内容。
  • 提取捕获的响应数据。 阅读嵌入的JSON或文本体,而无需再次加载页面。
  • 重建合格的请求。 通过标准的HTTP客户端重新发出公共或仍然经过身份验证的HTTP请求。
  • 构建确定性浏览器测试。 使用Playwright的route_from_har()功能将记录的响应返回到实时浏览器上下文中。

当广泛捕获比附加监听器到一个已知端点更有用时,HAR文件最有价值。

HAR捕获、视频录制和HAR路由是不同的

该领域的三个功能使用相似的术语,但解决不同的问题。

功能 记录或执行的内容 之后需要浏览器吗?
Playwright record_har_path 归档HTTP请求和响应数据 不需要,供离线检查
Scrapeless会话录制 创建渲染会话的视觉重放 不需要,供仪表板播放
Playwright route_from_har() 将记录的响应提供给浏览器上下文发出的请求 需要
本指南中的urllib示例 针对实时服务器重新发出一个捕获的请求 不需要

实时拦截

实时拦截观察请求的发生。当目标端点或响应模式已知时,它工作得很好。

一旦会话结束,任何未被监听器捕获的内容就消失了。

HAR捕获

HAR捕获广泛记录上下文的HTTP流量。重要的端点可以在浏览器关闭后选择。

这使得HAR捕获更适合:

  • 会话后调试
  • 网络清单
  • API发现
  • 审计页面加载的所有资源
  • 保存响应体以供离线检查

无刮取会话录制

无刮取抓取浏览器支持单独的原生会话录制功能。这会产生可重播的可视记录,显示呈现的浏览器会话。

它并不替代 HAR 文件。视频显示了屏幕上出现的内容;而 HAR 则暴露了结构化的 HTTP 事务。

Playwright route_from_har()

Playwright 的 route_from_har() 将保存的 HAR 响应发送回实时浏览器上下文中的请求。它通常用于在浏览器测试中模拟后端行为。

本指南中的重播示例做了其他事情:它从存档中读取一个请求,并使用 urllib 提交一个新的实时 HTTP 请求。

为什么通过无刮取抓取浏览器捕获 HAR?

无刮取抓取浏览器提供了由 Playwright 通过 CDP 驱动的远程浏览器环境。

浏览器会话在云基础设施上运行,并通过连接参数支持地理代理选择。Playwright 仍然使用其正常的浏览器、上下文、页面和 HAR API。

对于这个工作流程,责任划分很简单:

组件 责任
无刮取抓取浏览器 运行远程 Chromium 会话并提供 CDP 端点
Playwright 创建上下文、驱动页面并记录 HAR
本地文件系统 存储 session.har
Python 标准库 检查存档并重新发出选择的请求

HAR 录制本身是一个 Playwright 特性。将 Playwright 连接到无刮取使实时渲染阶段移动到受管理的远程浏览器中,同时将结果存档保留在运行 Python 脚本的机器上。

连接使用相同的 Chrome DevTools Protocol 网络域,浏览器工具使用它来观察网络活动。

抓取浏览器快速入门 涵盖了更广泛的 Playwright 和 Puppeteer 连接模型。

先决条件

您需要:

  • Python 3.9 或更新版本
  • Playwright 1.59.0,以便进行重现的运行
  • 一个无刮取账户和 API 密钥
  • session.har 将要创建的目录的写入权限
  • 对公共目标页面的网络访问

Playwright 1.59.0 声明需要 Python 3.9 或更新版本。固定该版本使安装与本指南中捕获的结果匹配。

此工作流程不需要本地的 Chrome 安装。connect_over_cdp() 附加到已经在无刮取基础设施上运行的浏览器。

检查和请求重新发出的脚本仅使用 Python 的标准库。它们不导入 Playwright 或打开浏览器连接。

第 1 步 — 安装 Playwright

安装用于录制运行的版本:

bash Copy
pip install "playwright==1.59.0"

由于脚本通过 CDP 连接到现有的远程浏览器,因此不会启动本地安装的浏览器可执行文件。

在 shell 中设置无刮取 API 密钥:

bash Copy
export SCRAPELESS_API_KEY="your_scrapeless_api_key"

代码从环境读取密钥,而不是将其存储在源代码控制中。

第 2 步 — 构建无刮取 CDP URL

抓取浏览器连接 URL 作为查询参数携带 API 密钥、会话生命周期和代理位置:

python Copy
import os
from urllib.parse import urlencode

API_KEY = os.environ["SCRAPELESS_API_KEY"]


def scraping_browser_url(proxy_country="US", session_ttl=180):
    params = urlencode({
        "token": API_KEY,
        "sessionTTL": session_ttl,
        "proxyCountry": proxy_country,
    })
    return f"wss://browser.scrapeless.com/api/v2/browser?{params}"

该函数生成的 URL 形式如下:

text Copy
wss://browser.scrapeless.com/api/v2/browser?token=...&sessionTTL=180&proxyCountry=US

三个配置值为:

  • token:无刮取 API 密钥
  • sessionTTL:最大会话生命周期
  • proxyCountry:请求的代理国家

将 URL 构造保留在一个函数中,使得在多个捕获脚本中应用相同的连接设置更加容易。

第 3 步 — 捕获浏览器会话

Playwright 的 record_har_path 设置属于 browser.new_context(),而不是 connect_over_cdp()

浏览器连接提供对 Chromium 的访问。上下文定义了将被记录的内容。

python Copy
import os
from urllib.parse import urlencode

from playwright.sync_api import sync_playwright


API_KEY = os.environ["SCRAPELESS_API_KEY"]
HAR_PATH = "session.har"


def scraping_browser_url(proxy_country="US", session_ttl=180):
    params = urlencode({
        "token": API_KEY,
        "sessionTTL": session_ttl,
        "proxyCountry": proxy_country,
    })
    return f"wss://browser.scrapeless.com/api/v2/browser?{params}"
zh Copy
with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(scraping_browser_url())

    context = browser.new_context(
        record_har_path=HAR_PATH,
        record_har_content="embed",
    )

    page = context.new_page()
    page.goto(
        "https://quotes.toscrape.com/scroll",
        wait_until="networkidle",
    )

    for _ in range(3):
        page.evaluate(
            "window.scrollTo(0, document.body.scrollHeight)"
        )
        page.wait_for_timeout(800)

    print(
        "滚动后可见的引用元素数量:",
        page.locator(".quote").count(),
    )

    context.close()
    browser.close()

print(
    "HAR已写入:",
    HAR_PATH,
    "-",
    os.path.getsize(HAR_PATH),
    "字节",
)

运行时打印了:

```text
滚动后可见的引用元素数量: 40
HAR已写入: session.har - 333269 字节

目标最初渲染了十条引用。每次滚动到接近底部时,都会触发另一个/api/quotes?page=N请求,使可见的总数增加到40条引用,跨越四个API页面。

该脚本无需预测哪个请求之后会变得重要。HAR还捕获了文档、样式表、JavaScript、字体和JSON调用。

获取您的免费计划API密钥:app.scrapeless.com

为什么需要context.close()

当浏览器上下文关闭时,HAR将被最终定稿。

Playwright将record_har_path文档化为浏览器上下文设置,并要求browser_context.close()才能保存HAR。仅关闭浏览器连接可能会在没有优雅刷新上下文伪影的情况下留下。

正确的关闭顺序是:

python Copy
context.close()
browser.close()

因此,context.close()调用是捕获过程的一部分,而不是可选的清理。

record_har_content="embed"将记录的响应内容存储在HAR中,而不是在单独的伴侣文件中。这使得session.har在后续的JSON检查和主体比较中是自包含的。

历史HAR格式草案定义了顶级log对象及其必需的entries数组。每个条目表示一个导出的HTTP请求。

第4步 - 在没有浏览器的情况下检查HAR

上下文关闭后,session.har是一个本地JSON文档。

以下脚本只使用jsoncollectionspathlib

python Copy
import json
from collections import Counter
from pathlib import Path


har = json.loads(Path("session.har").read_text())
entries = har["log"]["entries"]

print("总条目:", len(entries))

by_type = Counter(
    entry["response"]["content"]["mimeType"].split(";")[0]
    for entry in entries
)

for mime_type, count in by_type.most_common():
    print(f"  {mime_type}: {count}")

print()

for entry in entries:
    request = entry["request"]
    response = entry["response"]

    print(
        f"{request['method']:4s} "
        f"{response['status']:3d}  "
        f"{request['url']}"
    )

捕获的文件包含:

text Copy
总条目: 10
  application/json: 4
  text/css: 3
  text/html: 1
  application/javascript: 1
  font/woff2: 1

GET  200  https://quotes.toscrape.com/scroll
GET  200  https://quotes.toscrape.com/static/bootstrap.min.css
GET  200  https://quotes.toscrape.com/static/main.css
GET  200  https://quotes.toscrape.com/static/jquery.js
GET  200  https://fonts.googleapis.com/css?family=Raleway:400,700
GET  200  https://fonts.gstatic.com/s/raleway/v37/1Ptug8zYS_SKggPNyC0ITw.woff2
GET  200  https://quotes.toscrape.com/api/quotes?page=1
GET  200  https://quotes.toscrape.com/api/quotes?page=2
GET  200  https://quotes.toscrape.com/api/quotes?page=3
GET  200  https://quotes.toscrape.com/api/quotes?page=4

这十个条目覆盖了五种MIME类型:

  • 一个HTML文档
  • 三个CSS响应
  • 一个JavaScript响应
  • 一个网页字体
  • 四个JSON响应

一个实时监听器过滤到/api/quotes将观察到四个JSON请求,但忽略其他六个资源。HAR保留了所有十个条目供后续检查。

理解HAR条目结构

har["log"]["entries"]中的每个项都包含嵌套的请求和响应对象。

简化的条目具有以下结构:

json Copy
{
  "request": {
    "method": "GET",
    "url": "https://example.com/api/data",
    "headers": []
  },
  "response": {
    "status": 200,
    "headers": [],
    "content": {
      "mimeType": "application/json",
      "text": "{}"
    }
  }
}

有用的请求字段包括:

  • method
  • url
  • headers
  • queryString
  • postData

有用的响应字段包括:

  • status
  • statusText
  • headers
  • content
  • redirectURL
Copy
HAR内容并不保证在每个条目中都以直接可读的文本形式存储。根据资源和记录器的不同,`content.text` 可能缺失、解码文本,或者是一个编码表示,其 `encoding` 字段标识格式。

## 第5步 — 处理HTTP/2伪头部

捕获的 `page=1` API调用的请求头包含如下名称:

```text
:authority
:method
:path
:scheme

这些是 HTTP/2伪头部字段

它们携带控制信息,该信息通常会出现在HTTP/1.1请求行或目标中:

  • :method 指定请求方法。
  • :scheme 指定URI方案。
  • :authority 指定目标权限。
  • :path 指定路径和查询。

伪头部不是普通的HTTP头字段。像 urllib 这样的面向HTTP/1.1的客户端无法接受带冒号前缀的头名称。

重放脚本必须将它们的含义转换为URL和方法,然后从普通头映射中省略它们。

第6步 — 使用 urllib 重新发出一个捕获的请求

选定的 /api/quotes?page=1 请求现在在本地JSON文件中是一个字典。

下面的脚本:

  1. 找到捕获的条目。
  2. 读取其方法、URL和头部。
  3. 去除HTTP/2伪头部。
  4. 去除 accept-encoding
  5. 创建一个新的 urllib.request.Request
  6. 解析实时和捕获的响应体。
  7. 比较两个Python对象。
python Copy
import json
import urllib.error
import urllib.request
from pathlib import Path


har = json.loads(Path("session.har").read_text())
entries = har["log"]["entries"]

target = next(
    entry
    for entry in entries
    if entry["request"]["url"].endswith("page=1")
)

captured_request = target["request"]

# HTTP/2伪头部描述协议框架,不能作为普通HTTP/1.1风格的头传递。
#
# 还省略了accept-encoding,以便urllib可以协商一个
# 脚本可以直接解码的编码。
skip_headers = {"accept-encoding"}

headers = {
    header["name"]: header["value"]
    for header in captured_request["headers"]
    if not header["name"].startswith(":")
    and header["name"].lower() not in skip_headers
}

print("重放的头部数量:", len(headers))

request = urllib.request.Request(
    captured_request["url"],
    headers=headers,
    method=captured_request["method"],
)

with urllib.request.urlopen(request, timeout=10) as response:
    if response.status != 200:
        raise urllib.error.HTTPError(
            captured_request["url"],
            response.status,
            "意外的状态",
            response.headers,
            None,
        )

    live_data = json.loads(response.read())

captured_content = target["response"]["content"]
captured_data = json.loads(captured_content["text"])

print("状态:", response.status, "-- 没有运行浏览器进程")
print(
    "第一条引用作者:",
    live_data["quotes"][0]["author"]["name"],
)
print(
    "与HAR已经捕获的响应体匹配:",
    live_data == captured_data,
)

无浏览器重放的结果打印为:

text Copy
重放的头部数量: 12
状态: 200 -- 没有运行浏览器进程
第一条引用作者: 阿尔伯特·爱因斯坦
与HAR已经捕获的响应体匹配: True

过滤后的映射包含12个普通头部。HTTP/2伪头部和 accept-encoding 被排除在外。

accept-encoding一个HTTP内容协商字段。重放客户端可以宣传其支持的内容编码,而不是盲目复制浏览器的Brotli协商。

目标响应与此运行中存储在HAR中的JSON主体匹配。该比较在解析过的Python对象上进行,而不是在它们的序列化空格或键格式上进行。

这是一种语义请求重建,而不是对原始网络交换的逐字再现。新的请求可以使用不同的HTTP版本、头部顺序、压缩协商、连接和TLS会话。

你可以获得的结果

该工作流程生成三个可重用的工件或结果:

阶段 输出 需要浏览器吗?
捕获 session.har
检查 请求清单和MIME类型摘要
重新发出 解析的实时响应和捕获主体比较

捕获的会话生成了:

text Copy
HAR大小: 333269字节
HTTP条目: 10
JSON条目: 4
滚动后可见的引用: 40
重新发出的请求状态: 200
捕获/实时JSON匹配: True

这些数字描述了这个特定目标会话。不同的页面、浏览器版本、滚动时机、代理位置或页面响应可能会产生不同的请求集和文件大小。

确认一个脚本中的完整序列

单独的捕获、检查和重放程序更容易理解,但相同的阶段可以合并:

python Copy
import json
import os
import urllib.error
import urllib.request
from collections import Counter
from pathlib import Path
from urllib.parse import urlencode

from playwright.sync_api import sync_playwright


API_KEY = os.environ["SCRAPELESS_API_KEY"]
HAR_PATH = "session.har"


def scraping_browser_url(proxy_country="US", session_ttl=180):
    params = urlencode({
        "token": API_KEY,
        "sessionTTL": session_ttl,
        "proxyCountry": proxy_country,
    })
    return f"wss://browser.scrapeless.com/api/v2/browser?{params}"


# 阶段 1:捕获。需要一个浏览器。
with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(
        scraping_browser_url()
    )

    context = browser.new_context(
        record_har_path=HAR_PATH,
        record_har_content="embed",
    )

    page = context.new_page()
    page.goto(
        "https://quotes.toscrape.com/scroll",
        wait_until="networkidle",
    )

    for _ in range(3):
        page.evaluate(
            "window.scrollTo(0, document.body.scrollHeight)"
        )
        page.wait_for_timeout(800)

    context.close()
    browser.close()

print("=== 捕获 ===")
print(
    "HAR 写入:",
    HAR_PATH,
    "-",
    os.path.getsize(HAR_PATH),
    "字节",
)


# 阶段 2:检查。浏览器已关闭。
har = json.loads(Path(HAR_PATH).read_text())
entries = har["log"]["entries"]

print("\n=== 检查(无浏览器) ===")
print("总条目:", len(entries))

by_type = Counter(
    entry["response"]["content"]["mimeType"].split(";")[0]
    for entry in entries
)

for mime_type, count in by_type.most_common():
    print(f"  {mime_type}: {count}")


# 阶段 3:重新发出一个请求。浏览器保持关闭。
target = next(
    entry
    for entry in entries
    if entry["request"]["url"].endswith("page=1")
)

captured_request = target["request"]
skip_headers = {"accept-encoding"}

headers = {
    header["name"]: header["value"]
    for header in captured_request["headers"]
    if not header["name"].startswith(":")
    and header["name"].lower() not in skip_headers
}

request = urllib.request.Request(
    captured_request["url"],
    headers=headers,
    method=captured_request["method"],
)

with urllib.request.urlopen(request, timeout=10) as response:
    if response.status != 200:
        raise urllib.error.HTTPError(
            captured_request["url"],
            response.status,
            "意外状态",
            response.headers,
            None,
        )

    live_data = json.loads(response.read())

captured_data = json.loads(
    target["response"]["content"]["text"]
)

print("\n=== 重新发出(无浏览器) ===")
print("状态:", response.status)
print(
    "第一条报价作者:",
    live_data["quotes"][0]["author"]["name"],
)
print(
    "匹配捕获的主体:",
    live_data == captured_data,
)

合并运行输出为:

text Copy
=== 捕获 ===
HAR 写入: session.har - 333259 字节

=== 检查(无浏览器) ===
总条目: 10
  application/json: 4
  text/css: 3
  text/html: 1
  application/javascript: 1
  font/woff2: 1

=== 重新发出(无浏览器) ===
状态: 200
第一条报价作者: 阿尔伯特·爱因斯坦
匹配捕获的主体: True

只有第一阶段导入并使用了 Playwright。后面的阶段操作于文件和选定的实时 HTTP 端点。

HAR 捕获了什么

HAR 文件表示浏览器上下文记录的 HTTP 操作。

根据记录器和配置,条目可以包含:

  • 请求方法和 URL
  • 查询字符串参数
  • 请求头
  • 请求 cookie
  • 已发布的数据
  • 响应状态
  • 响应头
  • 响应 cookie
  • MIME 类型
  • 响应内容
  • 传输大小
  • 定时信息
  • 重定向细节
  • 缓存信息

通过 record_har_content="embed",Playwright 将可用的响应内容存储在 HAR 内部。没有嵌入的内容,请求清单仍然可以有用,但归档可能不包含离线解析或比较所需的响应主体。

HAR 不保证的内容

HAR 文件是捕获的 HTTP 活动的持久记录,但它并不是每个浏览器行为的完整记录。

WebSocket 帧不会被归档

HAR 模型请求和响应事务。它不会保留在建立的 WebSocket 连接中携带的消息序列。

一个结合普通 HTTP 端点和实时 WebSocket 源的页面需要单独的捕获机制:

  • HTTP 请求和响应流量的 HAR
  • WebSocket 帧监听器用于 socket 消息

初始连接可能涉及 HTTP 升级,但正在进行的帧流超出了正常的 HAR 请求-响应模型。

HAR 不是 DOM 快照

该文件不会以 DOM 快照的方式保留最终的文档树。
响应体可以包含原始 HTML 或 JSON,但后续的 JavaScript 变更、元素状态、渲染布局和用户可见外观是单独的问题。

HAR 并不是视频

该存档不包含页面上出现内容的视觉时间线。

当目标是回顾可见的浏览器行为时,请使用 Scrapeless 进行会话录制。当目标是检查结构化的 HTTP 流量时,请使用 HAR 捕获。

某些内容可能缺失或编码

HAR 支持可选内容字段。录制器可以省略主体,二进制资源可能会被编码。

在解析 response.content.text 之前,请检查:

  • text 字段是否存在。
  • 内容类型是否符合预期。
  • 如果存在,则处理 encoding 字段。
  • 录制配置是否省略了主体。

当无浏览器重发有效时

当实时服务器仍然接受重建的请求时,捕获的请求可以成功重发。

公共的 /api/quotes 端点有效,因为它不依赖于即将过期的身份验证会话。

当原始请求使用以下内容时,重发会变得更加复杂:

  • 会话 Cookie
  • CSRF 令牌
  • 短期持有凭证
  • 签名 URL
  • 每会话请求签名
  • 仅存储在浏览器中的状态
  • 由前一个导航步骤生成的数据
  • 内容在每个会话中变化的请求体

HAR 可能保留原始凭证值,但它不能延长这些值的有效性。一旦这些值过期,可能需要新的浏览器会话来创建新的状态。

安全重播 HAR 数据

HAR 文件可能包含敏感的会话数据。

请求和响应头可能暴露:

  • Cookie
  • 授权头
  • API 密钥
  • 会话标识符
  • 内部 URL
  • 端点返回的个人数据

将 HAR 文件视为敏感文档处理:

  • 不要将其提交到公共代码库。
  • 在分享之前移除凭证。
  • 限制对存档生产会话的访问。
  • 避免不必要地记录完整头部。
  • 仅存储调试或审计任务所需的捕获内容。
  • 根据项目的保留要求删除存档。

本教程中的存档来自公共的、未经身份验证的演示页面。这些假设不应自动应用于经过身份验证的应用程序。

常见问题

HAR 文件未出现

最常见的原因是关闭浏览器时没有明确关闭上下文。

使用:

python Copy
context.close()
browser.close()

还要确认进程是否可以向包含 HAR_PATH 的目录写入。

HAR 不包含响应体

确认上下文使用:

python Copy
record_har_content="embed"

然后检查 entry["response"]["content"]["text"] 是否存在。某些资源可能被省略或以编码形式表示。

urllib 拒绝一个头部名称

删除任何以 : 开头的头部。这些是 HTTP/2 伪头部,而不是普通的头部字段。

URL 和请求方法已经携带了其相关的含义。

重发的响应意外被压缩

除非重放客户端支持每个宣传的内容编码,否则不要盲目复制浏览器的 accept-encoding 值。

让 HTTP 客户端协商一个它可以解码的编码。

新的响应与 HAR 不匹配

不匹配并不一定意味着重建代码是错误的。

服务器可能返回变化的内容、时间戳、随机字段、特定地理位置的结果,或与不再有效的凭证相关的数据。

比较预期保持稳定的字段,而不是假设每个端点始终返回相同的字节。

结论

Playwright 的 record_har_path 将浏览器上下文转换为持久的 HTTP 存档。

该工作流程有三个明确的阶段:

  1. 将 Playwright 连接到 Scrapeless 爬虫浏览器并捕获页面会话。
  2. 关闭浏览器并将 log.entries 视为普通 JSON。
  3. 使用 urllib 重建一个合格的请求,并将其实时响应与捕获的主体进行比较。

浏览器仅在产生存档时是必需的。一旦 context.close() 刷新了 HAR,文件可以在没有 Playwright 安装、浏览器进程或 CDP 连接的机器上进行解析。

这种分离使得 HAR 捕获在会话后调试、内部 API 发现、网络审计和请求重建中非常有用。它也使限制保持可见:HAR 不保留 WebSocket 帧、渲染的视觉状态或捕获凭证的未来有效性。
请查看 Scraping Browser 产品页面 以获取与捕获工作流相关的浏览器会话功能,并在从演示会话转向更大捕获工作负载时查看 Scrapeless 定价计划

Chrome DevTools 协议解释 涵盖了浏览器连接所依赖的协议表面。

准备好归档真实的浏览器会话吗?

加入 Scrapeless 社区,与其他开发者比较 Playwright 捕获模式和浏览器调试工作流: Discord · Telegram

app.scrapeless.com 注册以获取免费的 Scraping Browser 运行时,然后将捕获、检查和请求重构步骤适应于与您的项目相关的公共页面。


常见问题

问:什么是 HAR 文件?

HAR 文件是浏览器会话中捕获的 HTTP 交易的 JSON 存档。它的 log.entries 数组包含每个记录请求的一个对象,嵌套请求、响应、时间和内容信息。

HAR 文件不是截图、页面视频或完整的 DOM 快照。

问:HAR 捕获与实时请求拦截有什么不同?

HAR 捕获宽泛地记录浏览器上下文的 HTTP 流量,而实时拦截观察发生的匹配请求。

实时拦截在已知端点时很有用。HAR 捕获在重要请求可能只有在会话结束后才能发现时很有用。

问:读取 HAR 文件需要浏览器吗?

不需要。完成的 HAR 文件是一个普通的 JSON 文档,可以使用 Python 的 json 模块读取。

捕获时需要浏览器,但离线检查时不需要。

问:HAR 捕获是否包括 WebSocket 流量?

HAR 捕获不归档在已建立的 WebSocket 连接内交换的消息。

当页面依赖于实时套接字数据流时,请使用 WebSocket 帧监听器。HAR 仍然可以覆盖同一页面使用的普通 HTTP 流量。

问:Playwright HAR 录制和 Scrapeless 会话录制是一样的吗?

不是。Playwright HAR 录制创建结构化的网络档案,而 Scrapeless 会话录制创建渲染的浏览器会话的可视重放。

使用 HAR 进行请求和响应分析。当调查的对象是可见的页面行为时,使用会话录制。

问:urllib 示例和 Playwright 的 route_from_har() 是一样的吗?

不是。route_from_har() 为在实时 Playwright 浏览器上下文中发出的请求提供录制的响应。

urllib 示例从 HAR 中读取请求,并向实时服务器提交新请求,而不启动浏览器。

问:为什么 HAR 包含像 :authority:method 这样的头部?

这些名称是 HTTP/2 伪头字段,用于在 HTTP/2 协议中承载请求控制数据。

它们不是普通的头部字段,因此 HTTP/1.1 样式的客户端必须通过请求方法和 URL 表示其含义,而不是直接复制带冒号的名称。

问:每个捕获的请求都能成功重新发出吗?

不可以。请求只能在实时服务器接受重构的方法、URL、主体、头和凭据时才可以重新发出。

依赖于过期的 cookie、CSRF 令牌、签名 URL 或短期有效的令牌的请求可能需要一个新的浏览器会话。

问:这个工作流需要单独的代理吗?

当 Scrapeless Scraping Browser 连接已经指定所需的代理国家时,不需要单独的代理配置。

这个示例在 CDP 连接 URL 中设置了 proxyCountry=US,因此浏览器会话使用该配置的出口。

问:共享 HAR 文件是否安全?

HAR 文件应被视为敏感文件,直到其内容被审核和清理。

它可能包含 cookies、授权头、API 密钥、内部端点、提交的表单数据或私人响应内容。在共享或提交文件之前,请删除敏感值。

问:HAR 录制是否仅与 Scrapeless Scraping Browser 一起工作?

不。record_har_path 是一个 Playwright 浏览器上下文选项,可以与兼容的本地或远程浏览器一起使用。

Scrapeless Scraping Browser 提供在捕获阶段使用的受管理的远程 Chromium 环境和代理配置。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录