Files
dy/backend/debug_qr_inspect.py
T
2026-08-27 18:32:03 +08:00

240 lines
10 KiB
Python

"""抖音登录二维码元素结构诊断脚本"""
import asyncio
import base64
import json
import os
import sys
from playwright.async_api import async_playwright
ROOT = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(ROOT)
BROWSERS_PATH = os.path.join(PROJECT_ROOT, "playwright-browsers")
os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH", BROWSERS_PATH)
OUT_DIR = os.path.join(ROOT, "debug_qr")
os.makedirs(OUT_DIR, exist_ok=True)
async def inspect():
async with async_playwright() as p:
print("launching browser")
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(
viewport={"width": 1280, "height": 900},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
)
page = await context.new_page()
print("goto douyin.com")
await page.goto("https://www.douyin.com", wait_until="load")
await asyncio.sleep(6)
print("page url:", page.url)
print("page title:", await page.title())
html = await page.content()
with open(os.path.join(OUT_DIR, "page_initial.html"), "w", encoding="utf-8") as f:
f.write(html)
print("saved page_initial.html")
# 点击登录按钮,触发登录弹窗
login_clicked = False
for sel in ["text=登录", "text=登录/注册", "text=立即登录", "button:has-text('登录')", "[class*='login']", "[class*='Login']"]:
try:
el = await page.wait_for_selector(sel, timeout=3000)
if el:
await el.click()
print("clicked via selector:", sel)
login_clicked = True
break
except Exception as e:
print(f"selector {sel} failed: {e}")
if not login_clicked:
for attempt in range(3):
try:
clicked = await page.evaluate("""() => {
const nodes = [...document.querySelectorAll('button, span, div, a, p')];
for (const el of nodes) {
const t = (el.innerText || '').trim();
if ((t.includes('登录') || t.toLowerCase().includes('login')) && el.offsetParent) {
el.click();
return t;
}
}
return '';
}""")
print("clicked via js:", clicked)
if clicked:
break
except Exception as e:
print(f"js click attempt {attempt} err: {e}")
await asyncio.sleep(1)
await asyncio.sleep(4)
html = await page.content()
with open(os.path.join(OUT_DIR, "page_after_login_click.html"), "w", encoding="utf-8") as f:
f.write(html)
print("saved page_after_login_click.html")
try:
await page.screenshot(path=os.path.join(OUT_DIR, "00_viewport.png"), full_page=False, timeout=10000)
print("saved 00_viewport.png")
except Exception as e:
print("viewport screenshot failed:", e)
# 尝试切换「扫码登录」
for frame in page.frames:
try:
switched = await frame.evaluate("""() => {
const nodes = [...document.querySelectorAll('span, div, a, button, p')];
let best = null;
for (const el of nodes) {
const t = (el.innerText || '').trim();
if ((t === '扫码登录' || t === '扫码') && el.offsetParent) {
if (!best || el.children.length < best.children.length) best = el;
}
}
if (best) { best.click(); return 'switched'; }
return '';
}""")
print(f"frame {frame.url[:60]} switched={switched}")
except Exception as e:
print(f"frame switch err: {e}")
await asyncio.sleep(2)
report = {"frames": [], "candidates": []}
# 遍历所有 frame,查找二维码相关元素
for idx, frame in enumerate(page.frames):
frame_report = {"index": idx, "url": frame.url, "qrcodes": [], "panels": []}
selectors = [
"[class*='qrcode'] img",
"[class*='QrCode'] img",
"[class*='qr-code'] img",
"img[class*='qrcode']",
"img[src*='qrcode']",
"img[alt*='二维码']",
"img[alt*='qr']",
"[class*='qrcode'] canvas",
"canvas[class*='qrcode']",
"[class*='scan'] img",
"[class*='scan'] canvas",
"img",
"canvas",
]
for sel in selectors:
try:
els = await frame.query_selector_all(sel)
for el in els:
try:
visible = await el.is_visible()
box = await el.bounding_box()
tag = await el.evaluate("e => e.tagName")
src = await el.get_attribute("src") or ""
alt = await el.get_attribute("alt") or ""
cls = await el.get_attribute("class") or ""
outer = await el.evaluate("e => e.outerHTML.slice(0, 300)")
info = {
"selector": sel,
"tag": tag,
"visible": visible,
"box": box,
"src_prefix": src[:120] if src else "",
"alt": alt,
"class": cls,
"outer": outer,
}
if (tag.lower() in ("img", "canvas") and box and box.get("width", 0) > 40 and visible):
frame_report["qrcodes"].append(info)
# 截图该元素
safe_name = f"frame{idx}_{tag}_{int(box['x'])}_{int(box['y'])}.png"
try:
await el.screenshot(path=os.path.join(OUT_DIR, safe_name))
info["screenshot"] = safe_name
except Exception as e:
info["screenshot_err"] = str(e)
except Exception as e:
print(f" el inspect err: {e}")
except Exception as e:
print(f"frame {idx} selector {sel} err: {e}")
# 登录面板/容器
panel_selectors = [
"[class*='qrcode-container']",
"[class*='qrcodeContainer']",
"[class*='qrcode']",
"[class*='QrCode']",
"[class*='login-scan']",
"[class*='scan-code']",
"#login-pannel",
"[class*='login_panel']",
"[class*='login-panel']",
"[class*='account_login']",
]
for sel in panel_selectors:
try:
els = await frame.query_selector_all(sel)
for el in els:
visible = await el.is_visible()
box = await el.bounding_box()
cls = await el.get_attribute("class") or ""
if visible and box and box.get("width", 0) > 80:
frame_report["panels"].append({
"selector": sel,
"class": cls,
"box": box,
})
safe_name = f"frame{idx}_panel_{int(box['x'])}_{int(box['y'])}.png"
try:
await el.screenshot(path=os.path.join(OUT_DIR, safe_name))
frame_report["panels"][-1]["screenshot"] = safe_name
except Exception as e:
frame_report["panels"][-1]["screenshot_err"] = str(e)
except Exception as e:
pass
report["frames"].append(frame_report)
# 尝试用 JS 暴力查找所有 img/canvas 中可能为二维码的
all_candidates = await page.evaluate("""() => {
const out = [];
document.querySelectorAll('img, canvas').forEach((el, i) => {
const rect = el.getBoundingClientRect();
if (rect.width > 30 && rect.height > 30 && rect.width < 600 && rect.height < 600) {
const style = window.getComputedStyle(el);
out.push({
tag: el.tagName,
index: i,
width: rect.width,
height: rect.height,
x: rect.x,
y: rect.y,
src: el.tagName === 'IMG' ? (el.src || '').slice(0, 200) : '',
alt: el.alt || '',
class: el.className || '',
parentText: (el.parentElement ? el.parentElement.innerText : '').slice(0, 80),
});
}
});
return out;
}""")
report["candidates"] = all_candidates
with open(os.path.join(OUT_DIR, "report.json"), "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print("report saved to", os.path.join(OUT_DIR, "report.json"))
print("found qrcode-like elements:", sum(len(f["qrcodes"]) for f in report["frames"]))
print("found panels:", sum(len(f["panels"]) for f in report["frames"]))
await browser.close()
if __name__ == "__main__":
try:
asyncio.run(inspect())
except Exception as e:
print("FATAL:", e, file=sys.stderr)
import traceback
traceback.print_exc()
raise