From c47ef233aa04aee74593e8243924cc0300d397ef Mon Sep 17 00:00:00 2001 From: chekayo <9827969+chekayo@user.noreply.gitee.com> Date: Sat, 15 Nov 2025 14:58:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9B=B4=E5=A4=9A=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E8=AF=A6=E6=83=85=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xiaohongshu/feed_detail.go | 127 +++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 4 deletions(-) diff --git a/xiaohongshu/feed_detail.go b/xiaohongshu/feed_detail.go index 64d1a2d..7eecf6b 100644 --- a/xiaohongshu/feed_detail.go +++ b/xiaohongshu/feed_detail.go @@ -43,6 +43,8 @@ func (f *FeedDetailAction) GetFeedDetail(ctx context.Context, feedID, xsecToken const DELTA_MIN = 480; const SCROLL_TIMEOUT = 900; const MAX_ATTEMPTS = 200; + const CLICK_MORE_INTERVAL = 2; // 每滚动2次检查一次"更多"按钮 + const CLICK_WAIT_TIME = 300; // 点击后等待时间 const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const scrollRoot = () => document.scrollingElement || document.documentElement || document.body; @@ -97,6 +99,77 @@ func (f *FeedDetailAction) GetFeedDetail(ctx context.Context, feedID, xsecToken console.debug('dispatchWheel error', err); } }; + + // 点击所有"更多"按钮 - 使用多种策略确保不遗漏 + const clickShowMoreButtons = () => { + // 尝试多个可能的选择器 + const selectors = [ + '.show-more', + '.show-more-btn', + '[class*="show-more"]', + '[class*="showMore"]', + 'button:has-text("更多")', + 'span:has-text("更多")', + 'div:has-text("更多")' + ]; + + const clickedElements = new Set(); + let clickedCount = 0; + + selectors.forEach((selector) => { + try { + const elements = document.querySelectorAll(selector); + elements.forEach((el) => { + // 避免重复点击同一个元素 + if (clickedElements.has(el)) return; + + // 检查元素文本是否包含"更多"或者是否有相关class + const text = el.textContent || ''; + const className = el.className || ''; + const shouldClick = text.includes('更多') || + className.includes('show-more') || + className.includes('showMore'); + + if (!shouldClick) return; + + // 检查元素是否可见(放宽条件,不要求完全在视口内) + const rect = el.getBoundingClientRect(); + const style = window.getComputedStyle(el); + const isVisible = ( + rect.height > 0 && + rect.width > 0 && + style.display !== 'none' && + style.visibility !== 'hidden' && + style.opacity !== '0' && + rect.top < window.innerHeight + 500 && // 允许元素在视口下方500px内 + rect.bottom > -500 // 允许元素在视口上方500px内 + ); + + if (isVisible) { + try { + // 尝试多种点击方式 + el.click(); + + // 如果是嵌套元素,也尝试点击父元素 + if (el.parentElement && el.parentElement.classList.contains('show-more')) { + el.parentElement.click(); + } + + clickedElements.add(el); + clickedCount++; + } catch (err) { + console.debug('点击失败', err); + } + } + }); + } catch (err) { + console.debug('选择器错误: ' + selector, err); + } + }); + + return clickedCount; + }; + let cachedTarget = null; const collectCandidates = () => { const container = getContainer(); @@ -175,16 +248,45 @@ func (f *FeedDetailAction) GetFeedDetail(ctx context.Context, feedID, xsecToken let lastCount = 0; let stagnantChecks = 0; let noScrollChangeCount = 0; + let totalClickedButtons = 0; + for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { const container = getContainer(); if (!container) { await sleep(300); continue; } + + // 每隔一定次数检查并点击"更多"按钮 + if (attempt % CLICK_MORE_INTERVAL === 0) { + const clicked = clickShowMoreButtons(); + if (clicked > 0) { + totalClickedButtons += clicked; + console.log('点击了 ' + clicked + ' 个"更多"按钮,累计: ' + totalClickedButtons); + await sleep(CLICK_WAIT_TIME); // 等待内容展开 + + // 点击后再次检查是否有新的"更多"按钮出现 + await sleep(200); + const clicked2 = clickShowMoreButtons(); + if (clicked2 > 0) { + totalClickedButtons += clicked2; + console.log('二次检查点击了 ' + clicked2 + ' 个"更多"按钮'); + await sleep(CLICK_WAIT_TIME); + } + } + } + const total = getTotalCount(container); const count = getCommentCount(container); if (total && count >= total) { - return { status: 'complete', reason: 'total', attempts: attempt + 1, count, total }; + return { + status: 'complete', + reason: 'total', + attempts: attempt + 1, + count, + total, + clickedButtons: totalClickedButtons + }; } if (count === lastCount) { stagnantChecks += 1; @@ -193,7 +295,14 @@ func (f *FeedDetailAction) GetFeedDetail(ctx context.Context, feedID, xsecToken stagnantChecks = 0; } if (stagnantChecks >= STAGNANT_LIMIT) { - return { status: 'complete', reason: 'stagnant', attempts: attempt + 1, count, total }; + return { + status: 'complete', + reason: 'stagnant', + attempts: attempt + 1, + count, + total, + clickedButtons: totalClickedButtons + }; } const target = findScrollTarget(); const beforeTop = getScrollMetrics(target).top; @@ -206,13 +315,23 @@ func (f *FeedDetailAction) GetFeedDetail(ctx context.Context, feedID, xsecToken noScrollChangeCount = 0; } if (noScrollChangeCount >= NO_CHANGE_SCROLL_LIMIT) { - return { status: 'complete', reason: 'no-scroll-change', attempts: attempt + 1, count, total }; + return { + status: 'complete', + reason: 'no-scroll-change', + attempts: attempt + 1, + count, + total, + clickedButtons: totalClickedButtons + }; } if (INTERVAL_MS > SCROLL_TIMEOUT) { await sleep(INTERVAL_MS - SCROLL_TIMEOUT); } } - return { status: 'timeout' }; + return { + status: 'timeout', + clickedButtons: totalClickedButtons + }; })() .then((res) => JSON.stringify(res)) .catch((err) => JSON.stringify({ status: 'error', message: err && err.message ? err.message : String(err) }));