Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""Минимальный драйвер Chrome/Edge через DevTools Protocol (без Selenium/Playwright)."""
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
import websockets
|
||||
|
||||
EDGE = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
|
||||
CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
|
||||
|
||||
|
||||
class Page:
|
||||
"""Страница, к которой подключаемся по websocket (Edge headless или Chrome на эмуляторе)."""
|
||||
|
||||
def __init__(self, ws_url):
|
||||
self.ws_url, self.n, self.ws = ws_url, 0, None
|
||||
|
||||
async def open(self):
|
||||
self.ws = await websockets.connect(self.ws_url, max_size=2**27)
|
||||
await self.call("Page.enable")
|
||||
await self.call("Runtime.enable")
|
||||
|
||||
async def close(self):
|
||||
if self.ws:
|
||||
await self.ws.close()
|
||||
|
||||
async def call(self, method, params=None):
|
||||
self.n += 1
|
||||
await self.ws.send(json.dumps({"id": self.n, "method": method, "params": params or {}}))
|
||||
while True:
|
||||
m = json.loads(await self.ws.recv())
|
||||
if m.get("id") == self.n:
|
||||
if "error" in m:
|
||||
raise RuntimeError(f"{method}: {m['error']}")
|
||||
return m.get("result", {})
|
||||
|
||||
async def eval(self, expr):
|
||||
r = await self.call("Runtime.evaluate", {"expression": expr, "awaitPromise": True, "returnByValue": True})
|
||||
if "exceptionDetails" in r:
|
||||
d = r["exceptionDetails"]
|
||||
raise RuntimeError("JS: " + str(d.get("exception", {}).get("description", d.get("text"))))
|
||||
return r["result"].get("value")
|
||||
|
||||
async def goto(self, url, wait=1.2):
|
||||
await self.call("Page.navigate", {"url": url})
|
||||
await asyncio.sleep(wait)
|
||||
|
||||
async def emulate(self, width, height, dpr=1, mobile=False, ua=None):
|
||||
await self.call("Emulation.setDeviceMetricsOverride", {"width": width, "height": height, "deviceScaleFactor": dpr, "mobile": mobile})
|
||||
await self.call("Emulation.setTouchEmulationEnabled", {"enabled": mobile})
|
||||
if ua:
|
||||
await self.call("Emulation.setUserAgentOverride", {"userAgent": ua})
|
||||
|
||||
async def screenshot(self, path):
|
||||
r = await self.call("Page.captureScreenshot", {"format": "png", "fromSurface": True})
|
||||
with open(path, "wb") as f:
|
||||
f.write(base64.b64decode(r["data"]))
|
||||
|
||||
|
||||
class Browser:
|
||||
def __init__(self, port=9333):
|
||||
self.port, self.proc, self.tmp = port, None, None
|
||||
|
||||
def start(self):
|
||||
self.tmp = tempfile.mkdtemp(prefix="guide_edge_")
|
||||
exe = EDGE if os.path.exists(EDGE) else CHROME
|
||||
self.proc = subprocess.Popen([exe, "--headless=new", f"--remote-debugging-port={self.port}", f"--user-data-dir={self.tmp}",
|
||||
"--no-first-run", "--no-default-browser-check", "--hide-scrollbars", "--lang=ru-RU",
|
||||
"--force-color-profile=srgb", "about:blank"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
for _ in range(60):
|
||||
try:
|
||||
urllib.request.urlopen(f"http://localhost:{self.port}/json/version", timeout=1)
|
||||
return
|
||||
except Exception:
|
||||
time.sleep(0.5)
|
||||
raise RuntimeError("браузер не запустился")
|
||||
|
||||
def page_ws(self):
|
||||
tabs = [t for t in json.load(urllib.request.urlopen(f"http://localhost:{self.port}/json")) if t["type"] == "page"]
|
||||
return tabs[0]["webSocketDebuggerUrl"]
|
||||
|
||||
def stop(self):
|
||||
if self.proc:
|
||||
self.proc.terminate()
|
||||
try:
|
||||
self.proc.wait(10)
|
||||
except Exception:
|
||||
self.proc.kill()
|
||||
Reference in New Issue
Block a user