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

如何使用 Playwright 和 Scrapeless 抓取 localStorage

Ava Wilson
Ava Wilson

Expert in Web Scraping Technologies

30-Jul-2026

浏览器可以在 DOM 之外保存有用的页面状态。本指南使用 Playwright 在一个公共的 MDN 演示中检查 localStorage,通过用户界面更改三个真实偏好设置,在第二个标签页中捕获结果存储事件,并在移动相同逻辑到 Scrapeless Scraping Browser 之前证明这些值在重新加载后仍然存在。

Web 存储为抓取工作流带来的优势

Web 存储 API 为一个来源提供了两个以字符串键为索引的存储区:

  • localStorage 由同源文档共享,并在普通页面重新加载和浏览器重启时保持,当配置文件是持久的情况下。
  • sessionStorage 限制在一个顶层浏览上下文中,并在该页面会话期间有效。

这两种存储都不是 cookie 瓶。Web 存储值不会自动附加到 HTTP 请求中,并且每个键/值都是字符串。MDN 的 Web 存储 API 指南定义了这些范围和持久性边界。

对于抓取而言,存储可以包含用户界面偏好、缓存标识符、特性状态或应用程序已加载的公共数据。将其视为浏览器状态进行验证,而不是收集身份验证令牌或私人用户数据的理由。

为什么要将 Playwright 和 Scrapeless 一起使用

Playwright 可以在页面来源中评估 JavaScript,在同一个 BrowserContext 中打开两个页面,并观察浏览器自己的存储事件。 Scrapeless Scraping Browser 提供了一个托管的 Chromium 会话,同时保留这些 Playwright API。

浏览器连接使用 connect_over_cdp。一旦存在 page,无论 Chromium 是本地的还是托管的,存储检查都是相同的。

前提条件

  • Python 3.10 或更高版本。
  • playwright 1.59.0 或当前兼容版本。
  • 本地 Chrome/Chromium 以完整的可运行示例。
  • 一个有资金的 Scrapeless 账户和用于云连接的 SCRAPELESS_API_KEY。在验证账户上新的浏览器会话返回代码 14500,因此连接模块是一个明确的前提缺口。

安装

bash Copy
python -m pip install "playwright==1.59.0"

连接到 Scrapeless Scraping Browser

注意:此模块需要有资金的抓取浏览器余额。最终验证账户返回 余额不足,请先充值。使用您自己的资金密钥运行;以下完整的存储工作流在本地 Chrome 下针对相同的公共页面运行。

python Copy
import os
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright

params = urlencode({
    "token": os.environ["SCRAPELESS_API_KEY"],
    "sessionTTL": 120,
    "proxyCountry": "US",
})
cdp_url = f"wss://browser.scrapeless.com/api/v2/browser?{params}"

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(cdp_url)
    context = browser.contexts[0]
    page = context.pages[0]
    # 使用上下文和页面运行下面的存储步骤。
    browser.close()

请使用 Scrapeless 开发者文档 获取当前连接参数。

检查每个 localStorage 键

MDN 的公共演示存储了背景颜色、字体和图像选择:

python Copy
TARGET_URL = "https://mdn.github.io/dom-examples/web-storage/"

page.goto(TARGET_URL, wait_until="domcontentloaded")
stored = page.evaluate(
    "Object.fromEntries(Object.entries(localStorage))"
)
print("初始存储:", stored)
print("会话存储:", page.evaluate("sessionStorage.length"))
text Copy
初始存储: {'font': 'sans-serif', 'image': 'images/firefoxos.png', 'bgcolor': 'FF0000'}
会话存储: 0

读取所有条目可以避免将提取器绑定到一个键。将来源保留在输出中,因为相同的键名在另一个站点上可能意味着不同的内容。

在第二个标签页捕捉更改

storage 事件 在共享更改存储的其他同源文档中触发。在进行更改的标签页中不会触发该事件。

python Copy
observer_page = context.new_page()
observer_page.goto(TARGET_URL, wait_until="domcontentloaded")
observer_page.evaluate("""
    window.capturedStorageEvents = [];
    addEventListener('storage', event => {
        capturedStorageEvents.push({
            key: event.key,
            oldValue: event.oldValue,
            newValue: event.newValue,
        });
    });
""")

这两个页面必须共享一个浏览器上下文和来源。第二个上下文有独立的存储,将不会接收这些事件。

更改真实 UI 控件

更改演示的控件,而不是直接写入值:

python Copy
page.evaluate("""
    bgcolor.value = '00FF00';
    bgcolor.dispatchEvent(new Event('change', {bubbles: true}));
""")
page.select_option("#font", "serif")
page.select_option("#image", "images/crocodile.png")
page.wait_for_timeout(500)

print("更新后的存储:", page.evaluate(
    "Object.fromEntries(Object.entries(localStorage))"
))
print("事件:", observer_page.evaluate("capturedStorageEvents"))
text Copy
更新后的存储: {'font': 'serif', 'image': 'images/crocodile.png', 'bgcolor': '00FF00'}
事件: [{'key': 'bgcolor', 'oldValue': 'FF0000', 'newValue': '00FF00'}, {'key': 'font', 'oldValue': 'sans-serif', 'newValue': 'serif'}, {'key': 'image', 'oldValue': 'images/firefoxos.png', 'newValue': 'images/crocodile.png'}]

该输出同时证明了三件事:UI 写入了存储,第二个标签页准确接收了三次更改,并且每个事件都保留了其先前的值。

准备在托管会话中检查状态吗?创建一个免费的 Scrapeless 账户并仅替换浏览器创建。

证明值在重新加载后依然存在

python Copy
page.reload(wait_until="domcontentloaded")
reloaded = page.evaluate(
    "Object.fromEntries(Object.entries(localStorage))"
)
controls = {
    "bgcolor": page.locator("#bgcolor").input_value(),
    "font": page.locator("#font").input_value(),
    "image": page.locator("#image").input_value(),
}

print("重新加载后的存储:", reloaded)
print("控件值:", controls)
text Copy
重新加载后的存储: {'font': 'serif', 'image': 'images/crocodile.png', 'bgcolor': '00FF00'}
控件值: {'bgcolor': '00FF00', 'font': 'serif', 'image': 'images/crocodile.png'}

将可见控件与存储状态匹配比仅检查存储更为强大。这证明应用程序在导航后使用了保存的值。

完整可运行脚本

python Copy
from playwright.sync_api import sync_playwright

TARGET_URL = "https://mdn.github.io/dom-examples/web-storage/"

with sync_playwright() as p:
    browser = p.chromium.launch(
        executable_path="/usr/bin/google-chrome",
        headless=True,
    )
    context = browser.new_context()
    page = context.new_page()
    observer_page = context.new_page()

    observer_page.goto(TARGET_URL, wait_until="domcontentloaded")
    observer_page.evaluate("""
        window.capturedStorageEvents = [];
        addEventListener('storage', event => {
            capturedStorageEvents.push({
                key: event.key,
                oldValue: event.oldValue,
                newValue: event.newValue,
            });
        });
    """)
    page.goto(TARGET_URL, wait_until="domcontentloaded")

    initial = page.evaluate(
        "Object.fromEntries(Object.entries(localStorage))"
    )
    page.evaluate("""
        bgcolor.value = '00FF00';
        bgcolor.dispatchEvent(new Event('change', {bubbles: true}));
    """)
    page.select_option("#font", "serif")
    page.select_option("#image", "images/crocodile.png")
    page.wait_for_timeout(500)

    updated = page.evaluate(
        "Object.fromEntries(Object.entries(localStorage))"
    )
    events = observer_page.evaluate("capturedStorageEvents")
    page.reload(wait_until="domcontentloaded")
    reloaded = page.evaluate(
        "Object.fromEntries(Object.entries(localStorage))"
    )
    controls = {
        "bgcolor": page.locator("#bgcolor").input_value(),
        "font": page.locator("#font").input_value(),
        "image": page.locator("#image").input_value(),
    }

    assert len(events) == 3
    assert updated == reloaded == controls
    assert page.evaluate("sessionStorage.length") == 0

    print("标题:", page.title())
    print("初始存储:", initial)
    print("更新后的存储:", updated)
    print("事件键:", [event["key"] for event in events])
    print("事件计数:", len(events))
    print("重新加载后的存储:", reloaded)
    print("控件值:", controls)
    print("会话存储条目:", page.evaluate("sessionStorage.length"))
    browser.close()

运行结果返回三个初始键、三个更新后的键、三个跨标签事件、匹配的重新加载控件以及零会话存储条目。

localStorage、sessionStorage 和 Cookies

localStorage

由同源文档共享,仅限字符串,并且在清除之前会一直存在。应用程序在访问间故意保持状态时使用它。

sessionStorage

每个顶级页面会话分开。复制或打开标签页可能会创建不同的生命周期,即使在相同来源下。请从确切的状态相关页面检查它。

Cookies

根据 cookie 规则与匹配的 HTTP 请求一起发送。请使用 Playwright 的上下文 cookie API,而不是将它们与 Web 存储混淆。

安全提取边界

存储可能包含凭据、用户标识符或私有状态。将示例限制在公共演示页面,只收集执行所述任务所需的字段,且绝不能发布令牌或会话材料。通过浏览器的来源访问并不授予您重新使用所有可读取值的权限。

结论

当浏览器具有可见的DOM无法解释的状态时,Web Storage提取是有用的。读取完整的映射,保持源与记录一起,在需要来源的时候观察跨标签页的变化,并验证应用程序在导航后消耗存储的值。将工作流程转移到Scrapeless更改了浏览器连接,而不是存储逻辑。

开始使用Scrapeless,查看Scrapeless定价,并阅读CDP暴露的内容,然后再将有状态会话迁移到云端。

常见问题

问:Playwright能读取localStorage吗?

可以。在目标来源的页面内评估Object.fromEntries(Object.entries(localStorage))

问:为何不对所有内容使用context.storage_state()

存储状态对于导出支持的身份验证状态是有用的,但直接页面评估允许您检查实时键映射,并将其与UI更改和存储事件关联。

问:localStorage在重新加载时是否会保留?

是的,除非应用程序或浏览器将其清除。MDN演示的三个改变的值在经过测试的重新加载中存活,并重新填充了控件。

问:更改localStorage的标签页会收到存储事件吗?

不会。事件被传递给其他共享存储的同源文档,这就是示例在第二个页面中监听的原因。

问:sessionStorage在标签页间共享吗?

不共享。它的作用域限于顶级浏览上下文及其同源子上下文,因此请从特定页面会话中检查它。

问:Web Storage的值是否总是JSON?

不。每个值都是作为字符串存储的。仅在确认应用程序格式时解析JSON,并明确处理无效的JSON。

问:抓取浏览器存储是否安全?

仅在目标的权限范围内和您的数据处理义务下。避免凭据和私有用户状态,保持收集的边界,并在适用时查看网站的条款和爬虫政策

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

最受欢迎的文章

目录