Skip to content

Commit 0215bba

Browse files
committed
bro: 修复必应/百度搜索无结果,并支持网页内直接下载扩展
- 扩展改为用后台标签页真实加载搜索结果页并读取渲染后 DOM(fetchPage), 规避搜索引擎防爬校验(百度安全验证、必应 JS 校验)导致的空结果,裸 fetch 作兜底 - 前端识别安全验证页并给出明确提示 - 搜索页提示条与设置弹层新增「下载扩展 .zip」按钮,点击即可直接下载,无需前往仓库 - 同步重建 bro-bridge.zip
1 parent 2c4a2ed commit 0215bba

5 files changed

Lines changed: 83 additions & 4 deletions

File tree

‎bro/bro-bridge.zip‎

609 Bytes
Binary file not shown.

‎bro/css/style.css‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,22 @@ main { max-width: 1080px; margin: 0 auto; padding: 26px 22px 60px; }
242242
margin-bottom: 18px; line-height: 1.6;
243243
}
244244

245+
/* 提示条内的下载链接 */
246+
.banner-link {
247+
display: inline-block; margin: 0 4px; padding: 1px 8px;
248+
background: #fff; border: 1px solid #cfe0ff; border-radius: 999px;
249+
color: var(--brand); text-decoration: none; font-size: 12px; font-weight: 600;
250+
white-space: nowrap;
251+
}
252+
.banner-link:hover { background: #f2f7ff; }
253+
245254
/* 扩展安装引导 */
255+
.download-row {
256+
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
257+
background: #f5f8ff; border: 1px solid #dbe6ff; border-radius: 10px;
258+
padding: 10px 12px; margin: 10px 0 14px;
259+
}
260+
.download-row a.primary { text-decoration: none; display: inline-block; }
246261
.guide-list { color: var(--muted); font-size: 13px; line-height: 1.9; padding-left: 20px; margin: 8px 0 0; }
247262
.guide-list code, .modal-card code { background: #f3f5fb; padding: 1px 5px; border-radius: 5px; font-size: 12px; }
248263
#guide .state { margin: 4px 0 12px; }

‎bro/extension/background.js‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,45 @@ async function fetchText(url) {
2828
return { status: res.status, finalUrl: res.url, text };
2929
}
3030

31+
// 等待标签页加载完成(status === 'complete'),带超时
32+
function waitForTabComplete(tabId, timeout = 25000) {
33+
return new Promise((resolve, reject) => {
34+
let settled = false;
35+
const finish = (err) => {
36+
if (settled) return;
37+
settled = true;
38+
chrome.tabs.onUpdated.removeListener(onUpdated);
39+
clearTimeout(timer);
40+
err ? reject(err) : resolve();
41+
};
42+
const onUpdated = (id, info) => { if (id === tabId && info.status === 'complete') finish(); };
43+
chrome.tabs.onUpdated.addListener(onUpdated);
44+
const timer = setTimeout(() => finish(new Error('页面加载超时')), timeout);
45+
// 可能监听前就已加载完成
46+
chrome.tabs.get(tabId).then((t) => { if (t && t.status === 'complete') finish(); }).catch(() => {});
47+
});
48+
}
49+
50+
// 后台标签页真实加载目标页,读取渲染后的 DOM。
51+
// 搜索引擎的防爬校验(如百度安全验证、必应 JS 校验)会拦截裸 fetch;
52+
// 用真实标签页加载等价于用户在浏览器里打开,可带回完整请求头、Cookie 与脚本渲染结果。
53+
async function fetchViaTab(url, { timeout = 25000, settle = 700 } = {}) {
54+
const tab = await chrome.tabs.create({ url, active: false });
55+
try {
56+
await waitForTabComplete(tab.id, timeout);
57+
await sleep(settle); // 等待结果脚本渲染进 DOM
58+
const injected = await chrome.scripting.executeScript({
59+
target: { tabId: tab.id },
60+
func: () => document.documentElement.outerHTML,
61+
});
62+
const html = injected && injected[0] ? injected[0].result : '';
63+
if (!html) throw new Error('未能读取页面内容');
64+
return { status: 200, finalUrl: url, text: html };
65+
} finally {
66+
chrome.tabs.remove(tab.id).catch(() => {});
67+
}
68+
}
69+
3170
/* ------------------------------ 站点代理 ------------------------------ */
3271
async function ensureTab(site) {
3372
const cfg = SITES[site];
@@ -75,6 +114,16 @@ async function handle(method, params = {}) {
75114
case 'fetch':
76115
return fetchText(params.url);
77116

117+
// 搜索引擎抓取主通道:后台标签页真实加载,规避防爬校验与 JS 渲染问题
118+
case 'fetchPage':
119+
try {
120+
return await fetchViaTab(params.url);
121+
} catch (err) {
122+
// 标签页方式失败时回退到网络抓取,尽量给出结果
123+
const r = await fetchText(params.url);
124+
return { ...r, fallback: String(err?.message || err) };
125+
}
126+
78127
case 'ai.open':
79128
return aiCall(params.site, 'state');
80129
case 'ai.state':

‎bro/index.html‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
<!-- 搜索视图 -->
2929
<section id="view-search" class="view active">
3030
<div id="bridgeBanner" class="banner hidden">
31-
未检测到 bro bridge 扩展。搜索与 AI 功能依赖该扩展提供浏览器原生特权接口(不依赖任何外部运行时)。点右上角「设置」查看安装步骤。
31+
<span>未检测到 bro bridge 扩展。搜索与 AI 功能依赖该扩展提供浏览器原生特权接口(不依赖任何外部运行时)。</span>
32+
<a class="banner-link" href="bro-bridge.zip" download="bro-bridge.zip">下载扩展 .zip</a>
33+
<span>解压后在扩展管理页「加载已解压的扩展程序」中选择解压目录即可。点右上角「设置」查看完整步骤。</span>
3234
</div>
3335

3436
<div class="hero">
@@ -72,11 +74,19 @@ <h1 class="hero-title">搜索</h1>
7274
<div class="modal-card" id="guide">
7375
<h3>bro bridge 扩展</h3>
7476
<p class="muted state"></p>
77+
78+
<div class="download-row">
79+
<a id="downloadZip" class="primary" href="bro-bridge.zip" download="bro-bridge.zip">下载扩展 .zip</a>
80+
<span class="muted">无需前往仓库,点击即可直接下载。</span>
81+
</div>
82+
7583
<p class="muted">安装步骤:</p>
7684
<ol class="guide-list">
85+
<li>点击上方「下载扩展 .zip」,把 <code>bro-bridge.zip</code> 下载到本地</li>
86+
<li>解压该压缩包,得到一个包含 <code>manifest.json</code> 的文件夹</li>
7787
<li>打开浏览器扩展管理页(Chrome / Edge 地址栏输入 <code>chrome://extensions</code>)</li>
7888
<li>打开右上角「开发者模式」</li>
79-
<li>点「加载已解压的扩展程序」,选择本仓库中的 <code>bro/extension</code> 目录</li>
89+
<li>点「加载已解压的扩展程序」,选择第 2 步解压出的文件夹</li>
8090
<li>回到本页刷新,右上角状态应变为「扩展已连接」</li>
8191
</ol>
8292
<div class="modal-actions">

‎bro/js/app.js‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,15 @@
241241

242242
try {
243243
const url = window.BRO_ENGINES.url(engine, query, mode, page);
244-
const data = await bridgeCall('fetch', { url });
244+
// 用后台标签页真实加载目标结果页,避免裸 fetch 被搜索引擎防爬校验拦截
245+
const data = await bridgeCall('fetchPage', { url }, 45000);
245246
const list = window.BRO_ENGINES.parse(engine, mode, data.text);
246247
if (!list.length) {
247-
status.textContent = `没有解析到结果(${currentEngine()?.label})。目标站点可能改版或被限制,换一个引擎再试。`;
248+
if (/wappass\.baidu|安全验证|请输入验证码|verify|unusual traffic/i.test(data.text || '')) {
249+
status.textContent = `「${currentEngine()?.label}」返回了安全验证页。请先在浏览器中正常打开一次该引擎并完成验证后重试,或更换其它引擎。`;
250+
} else {
251+
status.textContent = `没有解析到结果(${currentEngine()?.label})。目标站点可能改版或被限制,换一个引擎再试。`;
252+
}
248253
return;
249254
}
250255
status.textContent = `来自「${currentEngine()?.label}」的 ${list.length} 条结果(扩展静默抓取)`;

0 commit comments

Comments
 (0)