"""抖音登录二维码元素结构诊断脚本""" 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