// ==UserScript== // @name 百度搜索广告清理 // @namespace local.baidu.ad.filter // @version 1.1.0 // @description 自动隐藏百度搜索结果页的商业推广、右侧广告、品牌广告;支持一键临时显示、按日统计(PC 端为主,移动端尽力而为) // @author local // @match https://www.baidu.com/* // @match https://m.baidu.com/* // @match https://baidu.com/* // @run-at document-start // @noframes // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // ==/UserScript== /* * 说明 * 1. 本脚本按 2026-10 实测的百度结果页结构编写(.EC_result / [data-cmatchid] / .ec-tuiguang 等)。 * 百度前端改版后选择器可能失效,此时把 CONFIG.debug 改成 true,控制台会打印实际命中的节点。 * 2. 不声明任何联网权限;统计只存在本地(油猴用 GM 存储,扩展模式退回 localStorage)。 * 3. 同时兼容两种安装方式: * - Tampermonkey / Violentmonkey 脚本 * - Chrome 本地扩展的 content script(见同目录 manifest.json) */ (function () { 'use strict'; /* ==================== 1. 配置区(要改就改这里) ==================== */ const CONFIG = { // 隐藏右侧整列商业推广(百度右侧栏实测全部为竞价广告) hideRightColumn: true, // 右下角显示「已净化 N 条广告」小浮标(同时提供临时显示入口) showCounter: true, // 打开后可在 F12 控制台看到被隐藏的节点,便于百度改版后自查 debug: false }; /* ==================== 2. 广告特征 ==================== */ // 结构化标记:新版结果页里竞价结果一定带其中之一 const AD_MARKERS = [ '.EC_result', // 竞价结果容器 '[data-cmatchid]', // 商业匹配 ID '[data-general-xst]', // 竞价透传参数 '.ec-tuiguang', // 文案为「广告 / 商业推广」的角标 '[data-tuiguang]', // 角标附带的说明数据 '.ec_tuiguang_pplink' // 品牌推广链接 ]; // 「一条结果」的容器形态,用来确定要隐藏到哪一层 const RESULT_SEL = '.c-container, .result-op, .result, .c-result'; // 兜底:按角标文字识别(百度换 class 名时,标签文字一般不变) const AD_TEXT = /^(广告|商业推广|品牌广告|广告[·•].{0,6})$/; /* ==================== 3. 样式:先斩后奏,避免广告闪现 ==================== */ const CSS = [ '/* 广告本体 */', '.EC_result,', '#content_left .c-container:has(.ec-tuiguang),', '#content_left .c-container:has([data-cmatchid]),', '#content_left .c-container:has([data-general-xst]),', '#content_left .result:has(.ec-tuiguang),', '#content_left .result-op:has(.ec-tuiguang),', '.c-result:has(.ec-tuiguang),', '.c-result:has([data-tuiguang]),', '/* 广告外壳:顺带去掉残留的空白和分隔线 */', '[data-placeid]:has(.EC_result),', '[data-aggregation-area]:has(.EC_result),', '[data-placeid]:has(.ec-tuiguang),', '[data-aggregation-area]:has(.ec-tuiguang),', '/* 其他广告位(存在才生效) */', '#top_ads, #brand_ads, .ec-pc-brand-ad, .ec-brand-ad', CONFIG.hideRightColumn ? ', #content_right' : '', ' { display: none !important; }' ].join('\n'); const BADGE_CSS = [ '#badf-badge {', ' position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;', ' display: none; align-items: center; gap: 6px;', ' padding: 7px 11px; border-radius: 999px;', ' background: rgba(24,28,36,.92); color: #e8eaed;', ' font: 12px/1.4 -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;', ' box-shadow: 0 4px 16px rgba(0,0,0,.22); user-select: none;', ' transition: opacity .25s ease; opacity: 1;', '}', '#badf-badge.badf-on { display: flex; }', '#badf-badge.badf-faded { opacity: .38; }', '#badf-badge.badf-faded:hover { opacity: 1; }', '#badf-badge .badf-dot { width: 7px; height: 7px; border-radius: 50%; background: #41d18a; }', '#badf-badge.badf-paused .badf-dot { background: #f0a531; }', '#badf-badge .badf-count { font-weight: 700; color: #41d18a; }', '#badf-badge.badf-paused .badf-count { color: #f0a531; }', '#badf-badge .badf-tip { cursor: pointer; }', '#badf-badge .badf-toggle {', ' margin-left: 2px; padding: 2px 8px; border: 1px solid rgba(255,255,255,.28);', ' border-radius: 999px; background: transparent; color: #e8eaed;', ' font: inherit; font-size: 11px; cursor: pointer;', '}', '#badf-badge .badf-toggle:hover { background: rgba(255,255,255,.14); }', '#badf-panel {', ' position: absolute; right: 0; bottom: 38px; display: none;', ' min-width: 148px; padding: 8px 10px; border-radius: 10px;', ' background: rgba(24,28,36,.97); box-shadow: 0 6px 20px rgba(0,0,0,.3);', ' font-size: 12px; line-height: 1.9; white-space: nowrap;', '}', '#badf-panel.badf-open { display: block; }', '#badf-panel .badf-row { display: flex; justify-content: space-between; gap: 18px; }', '#badf-panel b { color: #41d18a; font-weight: 700; }' ].join('\n'); /* ==================== 4. 本地统计(GM 存储 / localStorage 兜底) ==================== */ const STORE_KEY = 'badf_stats_v1'; const useGM = typeof GM_getValue === 'function' && typeof GM_setValue === 'function'; function readStats() { try { const raw = useGM ? GM_getValue(STORE_KEY, '') : localStorage.getItem(STORE_KEY); if (!raw) return {}; const data = typeof raw === 'string' ? JSON.parse(raw) : raw; return data && typeof data === 'object' ? data : {}; } catch (e) { return {}; } } function writeStats(data) { try { const raw = JSON.stringify(data); if (useGM) GM_setValue(STORE_KEY, raw); else localStorage.setItem(STORE_KEY, raw); } catch (e) { /* 存储不可用时静默跳过,不影响去广告 */ } } // 用本地日期而不是 UTC,避免早上 8 点前被算到前一天 function dayKey(date) { const d = date || new Date(); const p = function (n) { return n < 10 ? '0' + n : '' + n; }; return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()); } function bumpStats(n) { if (!n) return; const data = readStats(); const k = dayKey(); data[k] = (Number(data[k]) || 0) + n; writeStats(data); } function statsSummary() { const data = readStats(); let total = 0; for (const k in data) { if (Object.prototype.hasOwnProperty.call(data, k)) total += Number(data[k]) || 0; } return { today: Number(data[dayKey()]) || 0, total: total }; } /* ==================== 5. 状态与工具 ==================== */ const hiddenSet = new WeakSet(); // 当前被我们隐藏的节点 const prevDisplay = new WeakMap(); // 隐藏前的内联 display,用于还原 let hiddenList = []; // 计入条数的隐藏记录(结果广告) let extraList = []; // 不计入条数的隐藏记录(右侧整列、顶部广告位) let count = 0; // 本次浏览累计清理条数 let paused = false; // 用户点了「显示」后的暂停态 let cssEl = null; // 主样式表,暂停时整体禁用 let badge = null; let panel = null; let fadeTimer = 0; function addStyle(css) { const style = document.createElement('style'); style.textContent = css; (document.head || document.documentElement).appendChild(style); return style; } /** * 从「广告标记」向上找到该条广告应隐藏的整块容器。 * 返回 null 表示判定不安全(例如抓到了整个结果列表),宁可不隐藏也不误伤。 */ function blockOf(el) { if (!el || el.nodeType !== 1) return null; // 先定位到最近的一条结果 let block = el.closest(RESULT_SEL); if (!block) return null; // 再向上合并到「单条广告外壳」,否则会留下空白和分隔线 for (let i = 0; i < 4; i++) { const parent = block.parentElement; if (!parent) break; const isSlot = parent.hasAttribute('data-placeid') || parent.hasAttribute('data-aggregation-area'); const onlyThisOne = parent.querySelectorAll(RESULT_SEL).length === 1; if (!isSlot || !onlyThisOne) break; block = parent; } // 安全阀:块里若还有多条结果,说明抓到了外层容器,放弃 if (block.querySelectorAll(RESULT_SEL).length > 2) return null; if (block.id === 'content_left' || block.id === 'content_right') return null; return block; } function hide(el, tick) { if (!el || el.nodeType !== 1 || hiddenSet.has(el)) return false; hiddenSet.add(el); prevDisplay.set(el, el.style.getPropertyValue('display')); el.style.setProperty('display', 'none', 'important'); // 计入条数的(结果广告)与不计入的(右侧整列等)分开存,浮标数字才不会虚高 (tick ? hiddenList : extraList).push(el); if (tick) { count++; bumpStats(1); updateBadge(); } if (CONFIG.debug) console.log('[百度广告清理] 隐藏节点:', el); return true; } // 只保留仍挂在页面上的记录(翻页后旧节点会失效) function liveRecords() { hiddenList = hiddenList.filter(function (el) { return el.isConnected; }); extraList = extraList.filter(function (el) { return el.isConnected; }); return hiddenList.concat(extraList); } // 临时显示:禁用注入的样式表 + 撤销所有内联隐藏,并暂停后续清理 function restoreAll() { paused = true; if (cssEl) cssEl.disabled = true; for (const el of liveRecords()) { const prev = prevDisplay.get(el) || ''; if (prev) el.style.setProperty('display', prev, 'important'); else el.style.removeProperty('display'); hiddenSet.delete(el); } updateBadge(); } // 从「临时显示」切回净化 function rehideAll() { paused = false; if (cssEl) cssEl.disabled = false; for (const el of liveRecords()) { el.style.setProperty('display', 'none', 'important'); hiddenSet.add(el); } updateBadge(); run(); } /* ==================== 6. 扫描与清理 ==================== */ function scanMarkers(root) { const nodes = root.querySelectorAll(AD_MARKERS.join(',')); for (const el of nodes) hide(blockOf(el), true); } // 文字角标兜底扫描(class 改名时仍能识别) function scanLabels(root) { const labels = root.querySelectorAll('span, i, em'); for (const el of labels) { if (el.children.length) continue; // 只看最内层节点 const text = (el.textContent || '').trim(); if (!text || text.length > 8) continue; if (!AD_TEXT.test(text)) continue; // 正文里也可能出现「广告」二字,必须同时处在商业推广上下文中才算 const inAdContext = el.closest('[data-placeid], [data-aggregation-area], [data-tuiguang], ' + 'a[href*="e.baidu.com"], a[href*="baidu.php"]'); if (!inAdContext) continue; if (el.offsetWidth > 90) continue; // 角标很小,大块文字不算 hide(blockOf(el), true); } } function run() { if (paused) return; // 用户正在查看被隐藏的广告 const root = document.querySelector('#content_left, #content_right, #page, .c-result'); if (!root) return; // 不是搜索结果页,直接退出 scanMarkers(root); scanLabels(root); // 右侧整列广告(不计入条数,避免数字虚高) if (CONFIG.hideRightColumn) { hide(document.getElementById('content_right'), false); } // 顶部 / 品牌广告位(页面存在才处理) for (const el of document.querySelectorAll('#top_ads, #brand_ads')) { hide(el, false); } } /* ==================== 7. 右下角浮标 ==================== */ function buildBadge() { badge = document.createElement('div'); badge.id = 'badf-badge'; badge.innerHTML = [ '', '已净化 ', '0 条广告', '', '
' ].join(''); panel = badge.querySelector('#badf-panel'); const toggle = badge.querySelector('.badf-toggle'); const tip = badge.querySelector('.badf-tip'); toggle.addEventListener('click', function (e) { e.stopPropagation(); if (paused) rehideAll(); else restoreAll(); }); tip.addEventListener('click', function (e) { e.stopPropagation(); renderPanel(); panel.classList.toggle('badf-open'); }); document.addEventListener('click', function () { if (panel) panel.classList.remove('badf-open'); }); document.body.appendChild(badge); } function renderPanel() { const s = statsSummary(); const rows = [['今日', s.today], ['累计', s.total], ['本次浏览', count]]; panel.textContent = ''; for (const item of rows) { const row = document.createElement('div'); row.className = 'badf-row'; const k = document.createElement('span'); k.textContent = item[0]; const v = document.createElement('b'); v.textContent = String(item[1]); row.appendChild(k); row.appendChild(v); panel.appendChild(row); } } function updateBadge() { if (!CONFIG.showCounter || !document.body) return; // 没有清理记录、也没在暂停态时,不显示浮标 if (!count && !paused) { if (badge) badge.classList.remove('badf-on'); return; } if (!badge) { addStyle(BADGE_CSS); buildBadge(); } badge.classList.add('badf-on'); badge.classList.toggle('badf-paused', paused); badge.querySelector('.badf-label').textContent = paused ? '已还原' : '已净化'; liveRecords(); // 顺手清掉已脱离文档的记录 badge.querySelector('.badf-count').textContent = String(hiddenList.length); badge.querySelector('.badf-toggle').textContent = paused ? '隐藏' : '显示'; if (paused && panel) panel.classList.remove('badf-open'); // 3 秒后变淡,鼠标移上去恢复;暂停态保持清晰 clearTimeout(fadeTimer); badge.classList.remove('badf-faded'); if (!paused) { fadeTimer = setTimeout(function () { if (badge && !paused) badge.classList.add('badf-faded'); }, 3000); } } /* ==================== 8. 启动 ==================== */ function start() { cssEl = addStyle(CSS); run(); let timer = 0; const schedule = function () { clearTimeout(timer); timer = setTimeout(run, 120); }; // 百度是异步渲染 + 滚动加载,DOM 变化就重扫一次 new MutationObserver(schedule) .observe(document.documentElement, { childList: true, subtree: true }); window.addEventListener('load', schedule); window.addEventListener('popstate', schedule); // 油猴环境额外提供菜单入口(扩展模式没有 GM 接口,用浮标点击代替) if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand('查看广告净化统计', function () { const s = statsSummary(); alert('百度广告清理统计\n\n今日:' + s.today + ' 条\n累计:' + s.total + ' 条\n本次页面:' + count + ' 条'); }); } } if (document.documentElement) start(); else document.addEventListener('DOMContentLoaded', start, { once: true }); })();