如何使用 Playwright 和 Scrapeless 抓取 localStorage
Expert in Web Scraping Technologies
浏览器可以在 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 或更高版本。
playwright1.59.0 或当前兼容版本。- 本地 Chrome/Chromium 以完整的可运行示例。
- 一个有资金的 Scrapeless 账户和用于云连接的
SCRAPELESS_API_KEY。在验证账户上新的浏览器会话返回代码 14500,因此连接模块是一个明确的前提缺口。
安装
bash
python -m pip install "playwright==1.59.0"
连接到 Scrapeless Scraping Browser
注意:此模块需要有资金的抓取浏览器余额。最终验证账户返回
余额不足,请先充值。使用您自己的资金密钥运行;以下完整的存储工作流在本地 Chrome 下针对相同的公共页面运行。
python
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
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
初始存储: {'font': 'sans-serif', 'image': 'images/firefoxos.png', 'bgcolor': 'FF0000'}
会话存储: 0
读取所有条目可以避免将提取器绑定到一个键。将来源保留在输出中,因为相同的键名在另一个站点上可能意味着不同的内容。
在第二个标签页捕捉更改
storage 事件 在共享更改存储的其他同源文档中触发。在进行更改的标签页中不会触发该事件。
python
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
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
更新后的存储: {'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
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
重新加载后的存储: {'font': 'serif', 'image': 'images/crocodile.png', 'bgcolor': '00FF00'}
控件值: {'bgcolor': '00FF00', 'font': 'serif', 'image': 'images/crocodile.png'}
将可见控件与存储状态匹配比仅检查存储更为强大。这证明应用程序在导航后使用了保存的值。
完整可运行脚本
python
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,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



