import asyncio, json, urllib.request async def recv_one(reader): b1 = await reader.readexactly(2) opcode = b1[0] & 0x0F masked_len = b1[1] length = masked_len & 0x7F if length == 126: length = int.from_bytes(await reader.readexactly(2), 'big') elif length == 127: length = int.from_bytes(await reader.readexactly(8), 'big') if masked_len & 0x80: mask = await reader.readexactly(4) payload = await reader.readexactly(length) if length else b'' payload = bytes(b ^ mask[i % 4] for i, b in enumerate(payload)) else: payload = await reader.readexactly(length) if length else b'' return opcode, payload async def send_text(writer, obj): data = json.dumps(obj).encode() mask = b'ABCD' frame = bytearray([0x81]) n = len(data) if n < 126: frame.append(0x80 | n) elif n < (1<<16): frame.append(0x80 | 126); frame += n.to_bytes(2,'big') else: frame.append(0x80 | 127); frame += n.to_bytes(8,'big') frame += mask + bytes(b ^ mask[i % 4] for i,b in enumerate(data)) writer.write(frame); await writer.drain() async def main(): ws = json.load(urllib.request.urlopen('http://127.0.0.1:9222/json/version'))['webSocketDebuggerUrl'] hp = ws[len('ws://'):] hostport, _, path = hp.partition('/') host, port = hostport.split(':'); port = int(port); path='/' + path reader, writer = await asyncio.open_connection(host, port) req = f'GET {path} HTTP/1.1\r\nHost: {host}:{port}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n' writer.write(req.encode()); await writer.drain(); await reader.readuntil(b'\r\n\r\n') next_id = 1 pending = {} async def call(method, params=None, sessionId=None): nonlocal next_id cid = next_id; next_id += 1 msg = {'id': cid, 'method': method} if params is not None: msg['params'] = params if sessionId is not None: msg['sessionId'] = sessionId await send_text(writer, msg) while True: op, payload = await recv_one(reader) if op != 1: continue data = json.loads(payload.decode(errors='ignore')) if data.get('id') == cid: return data tid = (await call('Target.createTarget', {'url':'about:blank'}))['result']['targetId'] sid = (await call('Target.attachToTarget', {'targetId': tid, 'flatten': True}))['result']['sessionId'] await call('Page.enable', sessionId=sid) await call('Runtime.enable', sessionId=sid) await call('Page.navigate', {'url':'https://www.google.com/search?q=lightpanda'}, sessionId=sid) await asyncio.sleep(4) js = "JSON.stringify(Array.from(document.querySelectorAll('a')).slice(0,20).map(a=>({text:(a.innerText||a.textContent||'').trim(), href:a.href})).filter(x=>x.text||x.href), null, 2)" res = await call('Runtime.evaluate', {'expression': js, 'returnByValue': True}, sessionId=sid) print(res['result']['result'].get('value','')) writer.close(); await writer.wait_closed() asyncio.run(main())