Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Pyppeteer does not expose a documented page event for individual WebSocket messages. To continuously print incoming responses, attach a Chrome DevTools Protocol (CDP) session to the page, enable the Network domain, subscribe to Network.webSocketCreated and Network.webSocketFrameReceived, and keep the process alive after navigation. The frame event supplies the socket request ID and payload; the creation event lets you map that ID to a URL.
Why page.on('response') is not enough
Pyppeteer’s normal response event belongs to the HTTP request lifecycle. It can report the WebSocket handshake, but it does not stream the application messages exchanged after the connection opens. A WebSocket can remain open while the page continues receiving frames, so waiting for navigation or listening only for HTTP responses will miss the data you want.
CDP’s Network domain provides the per-frame events. Pyppeteer exposes the raw protocol through CDPSession.send() and its event-emitter interface. The practical sequence is:
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall- Create a page and a CDP session attached to that page target.
- Send
Network.enable. - Register listeners before navigation or before page code opens its socket.
- Record each socket’s URL from
Network.webSocketCreated. - Print incoming frames from
Network.webSocketFrameReceived. - Keep an awaitable task alive until you intentionally stop the program.
Complete Pyppeteer example
The following script prints text frames as they arrive and labels binary frames without incorrectly treating them as text. It also records close and frame-error events for diagnostics.
#1 Best Overall
import asyncio
from contextlib import suppress
from pyppeteer import launch
TARGET = "https://example.com"
async def main():
browser = await launch()
page = await browser.newPage()
client = await page.target.createCDPSession()
sockets = {}
await client.send("Network.enable")
def on_created(event):
request_id = event["requestId"]
url = event.get("url", "<unknown socket>")
sockets[request_id] = url
print(f"WebSocket opened: {url}", flush=True)
def on_received(event):
request_id = event["requestId"]
frame = event["response"]
url = sockets.get(request_id, "<unknown socket>")
opcode = frame.get("opcode")
payload = frame.get("payloadData", "")
if opcode == 1:
# CDP defines opcode 1 as a UTF-8 text payload.
print(f"<< {url}: {payload}", flush=True)
else:
# Non-text payloads are represented by CDP as base64 data.
print(
f"<< {url}: binary payload (opcode={opcode}): {payload}",
flush=True,
)
def on_closed(event):
request_id = event["requestId"]
print(f"WebSocket closed: {sockets.get(request_id, request_id)}", flush=True)
def on_error(event):
request_id = event["requestId"]
print(f"WebSocket frame error for {sockets.get(request_id, request_id)}: {event}", flush=True)
client.on("Network.webSocketCreated", on_created)
client.on("Network.webSocketFrameReceived", on_received)
client.on("Network.webSocketClosed", on_closed)
client.on("Network.webSocketFrameError", on_error)
try:
await page.goto(TARGET)
# Navigation finishing does not close a live WebSocket.
await asyncio.Event().wait()
finally:
with suppress(Exception):
await client.detach()
await browser.close()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
Install Pyppeteer in the environment that will run the script, then start it normally. Replace TARGET with the page that creates the socket. The listeners are installed before page.goto(), which avoids losing early creation or frame events.
How the CDP events fit together
Network.webSocketCreated
This event reports a requestId and the socket URL. Store the pair in a dictionary. A page may open several sockets, so a single global “current URL” is unsafe.
Network.webSocketFrameReceived
For every incoming frame, CDP supplies the same requestId plus a response object. Read response["payloadData"] and inspect response["opcode"] before decoding or printing it. Opcode 1 is text and is represented as a UTF-8 string. Other opcodes are represented as base64 data; decode them only when the site’s binary protocol is known.
Optional direction and lifecycle events
Subscribe to Network.webSocketFrameSent when you also need client-to-server frames. Use Network.webSocketClosed to remove a socket from your map or report normal shutdown, and Network.webSocketFrameError to expose protocol-level errors.
Filtering and parsing a continuous stream
Listen to one socket
Keep the URL map, then filter in the receive callback:
if not url.startswith("wss://feed.example/"):
return
For a stronger filter, store additional metadata when the URL is created or maintain a set of accepted request IDs. Request IDs, rather than URLs, are the reliable identity for subsequent frame events.
Parse JSON text safely
import json
if opcode == 1:
try:
message = json.loads(payload)
except json.JSONDecodeError:
print(f"non-JSON text from {url}: {payload}")
else:
print(message)
A WebSocket frame is not automatically a complete business record. The application may put compressed data, multiplexed messages, or another binary format inside the payload. Interpret it according to that site’s protocol rather than assuming every text frame is JSON.
Handle binary payloads
CDP reports non-text payload data as base64. Convert it only when you know the expected format:
import base64
raw = base64.b64decode(payload)
# Pass raw to the site's documented binary decoder.
Do not call UTF-8 decoding blindly; it can corrupt or reject valid binary traffic.
Keeping the printer alive without leaking resources
After page.goto(), an ordinary script may reach its end and exit before the next frame arrives. An unresolved event, an application task, or an explicit shutdown signal keeps the loop running. The example uses asyncio.Event().wait() and closes both the CDP session and browser in finally.
Rank #3
In a service, replace the never-set event with an asyncio.Event controlled by your shutdown handler. On cancellation, detach the session, close the page or browser, and let the process terminate. Add bounded output, file rotation, or a queue if the site sends high-rate traffic; printing every frame synchronously can become the bottleneck.
Free tools Windows power users keep installed
One-click scans. No signup required.
Navigation, timing and reliability
- Register first: create the CDP session, enable Network, and attach handlers before navigation or any script that opens the socket.
- Do not equate page load with stream completion: WebSockets commonly continue after the load event.
- Expect reconnects: a page can close one socket and create another. Treat each new
requestIdas a separate connection. - Preserve ordering per callback: send received events to a queue if downstream parsing or storage is slower than the browser.
- Control log volume: redact tokens, cookies, authorization data, and personal information before writing frames to shared logs.
- Use a realistic browser target: Pyppeteer 0.0.25 says it works best with its bundled Chromium and does not guarantee arbitrary Chromium versions. CDP tip-of-tree documentation also changes and does not promise backward compatibility. Verify event names and fields against the Chromium build you actually launch.
Troubleshooting
No frames appear
- Confirm
await client.send("Network.enable")completed before navigation. - Make sure listeners are attached to the page’s target session, not to a different page or browser target.
- Check that the page really uses WebSockets; Server-Sent Events, polling, and WebTransport use different instrumentation.
- Keep the process alive after navigation.
You see the handshake but not messages
The handshake is an HTTP response, not a frame stream. Use Network.webSocketFrameReceived and match its requestId to webSocketCreated.
The callback raises a missing-key error
Do not assume every event has identical fields. Use event.get() for optional values and retain a fallback such as <unknown socket>. Keep the frame object separate from the outer event: payload data is under event["response"]["payloadData"].
Output is unreadable
Check the opcode. Text is printable UTF-8; non-text data is base64 and requires the application’s decoder. Compression or a binary serialization format must be handled before attempting JSON parsing.
Events differ after a Chromium or Pyppeteer upgrade
Compare the installed versions and inspect the protocol exposed by that Chromium build. Pyppeteer’s session-creation method is commonly page.target.createCDPSession(), but confirm it for your installed release. Treat CDP event schemas as version-sensitive rather than assuming every browser revision is interchangeable.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Or skip the browser setup
If your actual goal is a clean image or PDF of a page rather than observing its live socket traffic, ScreenshotNeo provides a single HTTP call. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
See the ScreenshotNeo API documentation for all options, including full-page and selector captures, device and retina settings, custom CSS or JavaScript, waits, request blocking, headers, cookies, geolocation, caching, signed links, asynchronous jobs and bulk capture.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Every plan includes the features. The Free plan provides 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots, with yearly billing giving two months free. Create a free ScreenshotNeo account to start.
FAQ
Can Pyppeteer’s page emitter print WebSocket messages directly?
Not through its documented page-event list. Use the page-target CDP session and Network frame events.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesDoes webSocketFrameReceived guarantee a complete application message?
It reports a WebSocket message payload, but the application can encode multiple logical records, compression, or binary structures inside it. Parsing remains application-specific.
How can I capture messages sent by the browser?
Subscribe to Network.webSocketFrameSent and use the same request-ID mapping.
Why does the script stop immediately?
Navigation completion does not keep Python alive. Await a long-lived task or shutdown event, then close the browser deliberately when stopping.
Frequently Asked Questions
Can Pyppeteer’s page emitter print WebSocket messages directly?
Not through its documented page-event list. Use the page-target CDP session and Network frame events.
Does webSocketFrameReceived guarantee a complete application message?
It reports a WebSocket message payload, but application-level records and formats remain site-specific.
How can I capture messages sent by the browser?
Subscribe to Network.webSocketFrameSent and use the same request-ID mapping.
Why does the script stop immediately?
Keep the event loop alive with an awaitable task or shutdown event, then close the browser deliberately.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

