diff --git a/example/browser/plg.json b/example/browser/plg.json index 2131638..86e1c10 100644 --- a/example/browser/plg.json +++ b/example/browser/plg.json @@ -2,7 +2,7 @@ "name": "browser", "name_zh": "浏览器", "name_en": "Browser", - "version": "2.4.0", + "version": "2.4.1", "description": "统一浏览器插件:搜索、HTTP抓取(quick)、无头渲染(normal)、交互式浏览器(interactive/CDP)", "author": "HomeAgent", "entry": "plugin.so", diff --git a/example/browser/plugin.go b/example/browser/plugin.go index bed747f..81b7662 100644 --- a/example/browser/plugin.go +++ b/example/browser/plugin.go @@ -6,6 +6,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "html" "io" "log" "net" @@ -484,7 +485,9 @@ type searchResult struct { } func (p *Plugin) bingSearch(query string, count int) ([]searchResult, error) { - u := fmt.Sprintf("https://www.bing.com/search?q=%s&count=%d", url.QueryEscape(query), count) + // 用 cn.bing.com:www.bing.com 对程序化请求常回 302(同意/重定向页),拿不到结果块。 + // 另:Bing 忽略 count 参数,翻页靠 first=,这里保留 count 只为兼容旧调用语义。 + u := fmt.Sprintf("https://cn.bing.com/search?q=%s&first=1&count=%d&setlang=zh-CN", url.QueryEscape(query), count) req, _ := http.NewRequest("GET", u, nil) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") @@ -494,38 +497,112 @@ func (p *Plugin) bingSearch(query string, count int) ([]searchResult, error) { } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) - return parseBingResults(string(body), count), nil + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Bing 返回 HTTP %d(%d 字节)", resp.StatusCode, len(body)) + } + results := parseBingResults(string(body), count) + if len(results) == 0 { + // 关键:把「解析不出来」与「真的没结果」区分开。 + // 以前两者都变成 "No results found.",版式一变就静默退化成「搜不到」。 + return nil, fmt.Errorf("Bing 返回 %d 字节但未解析出结果(可能被反爬或版式变更,可改用 deepsearch 插件)", len(body)) + } + return results, nil } -func parseBingResults(html string, count int) []searchResult { +var ( + bingBlockRe = regexp.MustCompile(`
]*>(.*?)
`) + bingCaptionRe = regexp.MustCompile(`(?s)(.*?)
`) - if sm := snipRe.FindStringSubmatch(block); len(sm) > 1 { - r.Snippet = stripTags(sm[1]) - } - if r.URL != "" && r.Title != "" { - results = append(results, r) + + title, snippet = strings.TrimSpace(title), strings.TrimSpace(snippet) + if href == "" || title == "" || !strings.HasPrefix(href, "http") { + continue } + results = append(results, searchResult{Title: title, URL: href, Snippet: snippet}) } return results } +// bingRealURL 解开 Bing 的跳转包装:/ck/a?...&u=a1` +// 对现代 Bing 命中 0/N(摘要已迁到 p.b_lineclamp*),于是结果「有标题没摘要」, +// 模型只好反复换词重搜。夹具 testdata/bing_cn.html 是真实 cn.bing.com 响应裁剪。 + +func TestParseBingResultsRealBingHTML(t *testing.T) { + page, err := os.ReadFile("testdata/bing_cn.html") + if err != nil { + t.Fatalf("读取夹具失败: %v", err) + } + results := parseBingResults(string(page), 3) + if len(results) != 3 { + t.Fatalf("应解析出 3 条,实际 %d 条: %+v", len(results), results) + } + for i, r := range results { + if !strings.HasPrefix(r.URL, "http") { + t.Errorf("第 %d 条 URL 不是真实地址: %q", i+1, r.URL) + } + if strings.Contains(r.Title, "http") || strings.Contains(r.Title, "://") { + t.Errorf("第 %d 条标题混入了 URL(旧 bug 的典型症状): %q", i+1, r.Title) + } + if r.Snippet == "" { + t.Errorf("第 %d 条没有摘要(旧 bug 的典型症状): %+v", i+1, r) + } + } + // 第一条必须与样本里的真实结果一致 + if results[0].URL != "https://www.deepin.org/" { + t.Errorf("第一条 URL 应为 https://www.deepin.org/,实际 %q", results[0].URL) + } + if !strings.Contains(results[0].Title, "deepin") { + t.Errorf("第一条标题不对: %q", results[0].Title) + } + if len(results[0].Snippet) < 10 || strings.Contains(results[0].Snippet, "://") { + t.Errorf("第一条摘要不对(应是有内容的文本): %q", results[0].Snippet) + } +} + +// 块内嵌套
博客 活动 开发者文档 DTK文档 软件包选型 联系社区 安全公告 贡献 贡献指引 参与社区 贡献代码 贡献文档 国际化 上游贡献 应用投递 …
2009年7月26日 · 另外,通过深度商店还能够获得近千款应用软件的支持,满足您对操作系统的扩展需求。 深度操作系统由专业的操作 …
2026年9月4日 · 博客 活动 开发者文档 DTK文档 软件包选型 联系社区 安全公告 贡献 贡献指引 参与社区 贡献代码 贡献文档 国际化 上 …