From 812db29ed8f43f3794f429f2e800481cba85839a Mon Sep 17 00:00:00 2001 From: ylongwang <67491174+ylongw@users.noreply.github.com> Date: Fri, 20 Mar 2026 14:11:20 +0800 Subject: [PATCH] fix(smzdm): navigate to search page directly instead of deprecated ajax API (#113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old adapter called `search.smzdm.com/ajax/?c=&s=` which now returns 404. This caused opencli smzdm search to always return empty results regardless of keyword. Fix: navigate directly to `search.smzdm.com/?c=home&s=&v=b` and scrape the rendered DOM via querySelectorAll('li.feed-row-wide'). Also switched from async IIFE to sync IIFE since all data is already in the DOM after page load — no fetch needed. Tested: opencli smzdm search --keyword A7M5 returns correct results with prices and mall names. --- src/clis/smzdm/search.ts | 69 +++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/src/clis/smzdm/search.ts b/src/clis/smzdm/search.ts index e69b609..0ac9534 100644 --- a/src/clis/smzdm/search.ts +++ b/src/clis/smzdm/search.ts @@ -1,6 +1,9 @@ /** - * 什么值得买搜索好价 — browser cookie, HTML parse. - * Source: bb-sites/smzdm/search.js + * 什么值得买搜索好价 — browser cookie, DOM scraping. + * + * Fix: The old adapter used `search.smzdm.com/ajax/` which returns 404. + * New approach: navigate to `search.smzdm.com/?c=home&s=&v=b` + * and scrape the rendered DOM directly. */ import { cli, Strategy } from '../../registry.js'; @@ -18,46 +21,34 @@ cli({ func: async (page, kwargs) => { const q = encodeURIComponent(kwargs.keyword); const limit = kwargs.limit || 20; - await page.goto('https://www.smzdm.com'); + + // Navigate directly to search results page + await page.goto(`https://search.smzdm.com/?c=home&s=${q}&v=b`); await page.wait(2); + const data = await page.evaluate(` - (async () => { - const q = '${q}'; + (() => { const limit = ${limit}; - // Try youhui channel first, then home - for (const channel of ['youhui', 'home']) { - try { - const resp = await fetch('https://search.smzdm.com/ajax/?c=' + channel + '&s=' + q + '&p=1&v=b', { - credentials: 'include', - headers: {'X-Requested-With': 'XMLHttpRequest'} - }); - if (!resp.ok) continue; - const html = await resp.text(); - if (html.indexOf('feed-row-wide') === -1) continue; - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const items = doc.querySelectorAll('li.feed-row-wide'); - const results = []; - items.forEach((li, i) => { - if (results.length >= limit) return; - const titleEl = li.querySelector('h5.feed-block-title > a') - || li.querySelector('h5 > a'); - if (!titleEl) return; - const title = (titleEl.getAttribute('title') || titleEl.textContent || '').trim(); - const url = titleEl.getAttribute('href') || ''; - const priceEl = li.querySelector('.z-highlight'); - const price = priceEl ? priceEl.textContent.trim() : ''; - let mall = ''; - const extrasSpan = li.querySelector('.z-feed-foot-r .feed-block-extras span'); - if (extrasSpan) mall = extrasSpan.textContent.trim(); - const commentEl = li.querySelector('.feed-btn-comment'); - const comments = commentEl ? parseInt(commentEl.textContent.trim()) || 0 : 0; - results.push({rank: results.length + 1, title, price, mall, comments, url}); - }); - if (results.length > 0) return results; - } catch(e) { continue; } - } - return {error: 'No results'}; + const items = document.querySelectorAll('li.feed-row-wide'); + const results = []; + items.forEach((li) => { + if (results.length >= limit) return; + const titleEl = li.querySelector('h5.feed-block-title > a') + || li.querySelector('h5 > a'); + if (!titleEl) return; + const title = (titleEl.getAttribute('title') || titleEl.textContent || '').trim(); + const url = titleEl.getAttribute('href') || titleEl.href || ''; + const priceEl = li.querySelector('.z-highlight'); + const price = priceEl ? priceEl.textContent.trim() : ''; + let mall = ''; + const mallEl = li.querySelector('.z-feed-foot-r .feed-block-extras span') + || li.querySelector('.z-feed-foot-r span'); + if (mallEl) mall = mallEl.textContent.trim(); + const commentEl = li.querySelector('.feed-btn-comment'); + const comments = commentEl ? parseInt(commentEl.textContent.trim()) || 0 : 0; + results.push({ rank: results.length + 1, title, price, mall, comments, url }); + }); + return results; })() `); if (!Array.isArray(data)) return [];