From ceaa2cc8396833bf72469def41f6e474ee01f7d6 Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Sat, 9 May 2026 23:15:41 +0100 Subject: [PATCH] =?UTF-8?q?music(chord):=20=E9=80=89=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E9=87=8C=E7=9A=84=E5=8A=9F=E8=83=BD=E8=B0=B1?= =?UTF-8?q?=EF=BC=88=E6=95=B0=E5=AD=97=E7=BA=A7=E6=95=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E4=B8=8D=E8=A6=81=E5=AD=97=E6=AF=8D=E8=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit yopu 搜索结果同一首歌通常有多个版本,区分方式: - 字母谱:nier-snippet 里 SVG 渲染 chord 字母(G/Em7/C 等) - 功能谱:nier-snippet 里没 SVG ,直接 HTML/CSS 显示 1/4/5/6m 按 svgTextCount === 0 优先选第一个功能谱,没功能谱才 fallback 到字母谱。 view 页里没有「谱面样式」「和弦样式」row(要登录 APP 才有),所以这是唯一可行路径。 实测 独家记忆/倔强/Casablanca 三首都拿到正确的功能谱截图。 --- apps/music/chord/yopu.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/apps/music/chord/yopu.py b/apps/music/chord/yopu.py index 3cf1586..a139ecf 100644 --- a/apps/music/chord/yopu.py +++ b/apps/music/chord/yopu.py @@ -55,10 +55,13 @@ def setup_driver(window="1920,5000"): def find_first_chord_chart(driver, search_url): - """在搜索页找第一个结果(yopu 现在默认全部是和弦谱),返回 view url 和 title。 + """在搜索页找最佳的「功能谱」结果。 - 旧版 yopu UI 在 .one-line-info 里有「和弦谱」字样可以过滤, - 新版(svelte 重写后)已经没有,只有调号 (G调/C调/F调)。直接取第一个 a.post-main。 + yopu 现在搜索结果里同一首歌有多个版本: + - 字母谱(chord chart):nier-snippet 里有 SVG 渲染的 chord 字母(G/Em7/C) + - 功能谱(数字 / 级数):nier-snippet 里没 SVG (用 HTML/CSS 显示数字 1/4/5) + + 我们优先取第一个**功能谱**(svgTextCount === 0),fallback 到第一个字母谱。 """ logger.info("loading search: %s", search_url) driver.get(search_url) @@ -72,35 +75,48 @@ def find_first_chord_chart(driver, search_url): var titleEl = p.querySelector('.title-line .title, .title'); var subEl = p.querySelector('.title-line .subtitle, .subtitle'); var info = p.querySelector('.one-line-info'); + var snippet = p.querySelector('.nier-snippet'); + var svgTextCount = snippet ? snippet.querySelectorAll('svg text').length : 0; out.push({ href: p.href, title: titleEl ? (titleEl.textContent || '').trim() : '', subtitle: subEl ? (subEl.textContent || '').trim() : '', info: info ? (info.textContent || '').trim() : '', + isFunctional: svgTextCount === 0, + svgTextCount: svgTextCount, }); } return out; """) if not hits: - logger.warning("no a.post-main found in search results — yopu DOM changed?") + logger.warning("no a.post-main found — yopu DOM changed?") return None - # MVP:直接取第一个。前 N 个一般是同一首歌的不同 key (G/C/F),第一个通常是默认 key。 - first = hits[0] - href = first['href'] + # 优先功能谱 + functional = [h for h in hits if h['isFunctional']] + if functional: + chosen = functional[0] + kind = 'functional' + else: + chosen = hits[0] + kind = 'letter-chord (no functional version found)' + + href = chosen['href'] if href.startswith('/'): p = urlparse(search_url) href = f"{p.scheme}://{p.netloc}{href}" elif not href.startswith('http'): href = urljoin(search_url, href) - logger.info("matched %d/%d hits, picking #1: %s — %s [%s]", - 1, len(hits), first.get('title'), first.get('subtitle'), first.get('info')) + logger.info("[%s] %s — %s [%s] (%d total: %d functional, %d letter)", + kind, chosen['title'], chosen['subtitle'], chosen['info'], + len(hits), len(functional), len(hits) - len(functional)) return { 'url': href, - 'title': first.get('title') or '', - 'subtitle': first.get('subtitle') or '', - 'text': first.get('info') or '', + 'title': chosen.get('title') or '', + 'subtitle': chosen.get('subtitle') or '', + 'text': chosen.get('info') or '', + 'kind': kind, }