fish2018/pansou · error
[ ] 执行搜索失败
Error message
[%s] 执行搜索失败: %w
What it means
Generic wrapper in dyyj.searchImpl for any failure returned by p.executeSearch (which itself may use HTML scraping or the Flarum JSON API). The inner error is preserved via %w, so this is a contextual wrapper, not a distinct failure. It indicates the whole search pipeline for this plugin failed.
Solutions
- Inspect the wrapped cause with errors.Unwrap / %v of the returned error — fix the root cause first
- Run with debugMode=true to get the pre-wrap log line identifying the failing stage
- Verify network access to BaseURL from the host (curl the search URL)
- Check whether the site/API changed and update selectors or API params
- Ensure doRequestWithRetry backoff is adequate for a rate-limited site
Example fix
// before
results, err := plugin.Search(ctx, keyword)
if err != nil {
log.Fatal(err)
}
// after
results, err := plugin.Search(ctx, keyword)
if err != nil {
var rootErr error
for e := err; e != nil; e = errors.Unwrap(e) {
rootErr = e
}
log.Fatalf("search failed: %v (root cause: %v)", err, rootErr)
} Defensive patterns
Strategy: try-catch
Validate before calling
url, err := url.Parse(pluginBaseURL); if err != nil || url.Host == "" { return errors.New("plugin base URL invalid") } Try / catch
results, err := p.searchImpl(ctx, keyword)
if err != nil {
log.Printf("[%s] search failed: %v", p.Name(), err) // %v shows the full wrap chain
return nil, err
} Prevention
- Always unwrap to the root cause before troubleshooting
- Enable debugMode in development to see the failing stage's own log line
- Monitor wrapped error chains with errors.Is/As rather than string matching
- Keep per-stage errors distinct so the wrapper stays diagnosable
When it happens
Trigger: Any underlying cause inside executeSearch: request construction failure, network error after retries, non-200 status, HTML/JSON parse failure, or empty results path that returns an error.
Common situations: Site blocked the client (cloudflare challenge); API endpoint changed; keyword triggers server-side rate limiting; network/proxy outage in the deployment environment.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/35cce650c703537f.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/dyyj/dyyj.go:171
// SearchWithResult 执行搜索并返回包含IsFinal标记的结果
func (p *DyyjPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {
return p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)
}
// searchImpl 搜索实现
func (p *DyyjPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
if p.debugMode {
log.Printf("[DYYJ] 开始搜索: %s", keyword)
}
// 第一步:执行搜索获取结果列表
// 使用优化的客户端(连接池)而不是传入的client
searchResults, err := p.executeSearch(p.optimizedClient, keyword)
if err != nil {
if p.debugMode {
log.Printf("[DYYJ] 执行搜索失败: %v", err)
}
return nil, fmt.Errorf("[%s] 执行搜索失败: %w", p.Name(), err)
}
if p.debugMode {
log.Printf("[DYYJ] 搜索获取到 %d 个结果", len(searchResults))
}
// The Flarum API response already includes each relevant post's content and
// extracted links. Do not fall back to HTML detail pages, which are protected
// by a Cloudflare challenge.
if hasInlineLinks(searchResults) {
return plugin.FilterResultsByKeyword(searchResults, keyword), nil
}
// 第二步:先对标题进行关键词过滤,只处理包含关键词的结果(避免不必要的详情页请求)
titleFilteredResults := p.filterByTitleKeyword(searchResults, keyword)
if p.debugMode {
log.Printf("[DYYJ] 标题关键词过滤后剩余 %d 个结果(将只对这些结果获取详情页)", len(titleFilteredResults))
}
View on GitHub (pinned to beaa561337)