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, }