fish2018/pansou · error
[ ] 搜索第一页失败
Error message
[%s] 搜索第一页失败: %w
What it means
WujiPlugin.searchImpl wraps any error from fetching/parsing page 1 of search results as "[%s] 搜索第一页失败" ("first page search failed"), including the plugin name and the underlying cause via %w. Since page 1 anchors pagination of all subsequent pages, the whole search is aborted when it fails. The root cause is always in the wrapped error (network, HTTP status, or parse failure inside searchPage).
Solutions
- Unwrap and read the inner %w error to identify network vs HTTP-status vs parse failure.
- Retry the search — transient upstream failures and rate limits often clear; add backoff.
- Verify the Wuji search endpoint is reachable (curl the same URL with the same keyword) to rule out site downtime.
- If parsing fails, check whether Wuji changed its response format and update searchPage.
- Confirm the http.Client has sane timeout/proxy/TLS settings for the environment.
Example fix
// before
firstPageResults, err := p.searchPage(client, keyword, 1)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索第一页失败: %w", p.Name(), err)
}
// after: one retry with backoff before failing
firstPageResults, err := p.searchPage(client, keyword, 1)
if err != nil {
time.Sleep(time.Second)
firstPageResults, err = p.searchPage(client, keyword, 1)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索第一页失败: %w", p.Name(), err)
}
} Defensive patterns
Strategy: retry
Validate before calling
// Go: cheap reachability pre-check before running a multi-page search
resp, err := client.Head(searchBaseURL)
if err != nil || resp.StatusCode >= 500 {
return fmt.Errorf("search upstream unreachable (status=%v, err=%v)", statusCodeOr(resp), err)
} Try / catch
results, err := plugin.Search(keyword, ext)
if err != nil {
var wrapped interface{ Unwrap() error }
if errors.As(err, &wrapped) || strings.Contains(err.Error(), "搜索第一页失败") {
// transient upstream failure: backoff and retry once
time.Sleep(2 * time.Second)
results, err = plugin.Search(keyword, ext)
}
if err != nil {
return err
}
} Prevention
- Set a reasonable http.Client timeout and retry idempotent first-page fetches with backoff.
- Respect rate limits; throttle consecutive searches to avoid being blocked by the Wuji site.
- Unwrap the error chain (errors.Unwrap) to distinguish network, HTTP-status, and parse causes.
- Monitor upstream availability; pin parsing logic to a saved fixture so format changes are caught in tests.
When it happens
Trigger: searchImpl calls p.searchPage(client, keyword, 1) and it returns an error — e.g. the HTTP request to the Wuji search endpoint fails, times out, returns a non-200 status, or the response cannot be parsed into results.
Common situations: Wuji site is down or rate-limiting the client; keyword triggers a server-side error; network/DNS/proxy problems in the environment; the site changed its response layout so searchPage's parsing fails; TLS or timeout misconfiguration in the passed http.Client.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/dfa819ffbabb2996.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/wuji/wuji.go:105
func (p *WujiPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
result, err := p.SearchWithResult(keyword, ext)
if err != nil {
return nil, err
}
return result.Results, nil
}
// SearchWithResult 执行搜索并返回包含IsFinal标记的结果
func (p *WujiPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {
return p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)
}
// searchImpl 实际的搜索实现
func (p *WujiPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
// 1. 首先搜索第一页
firstPageResults, err := p.searchPage(client, keyword, 1)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索第一页失败: %w", p.Name(), err)
}
// 存储所有结果
var allResults []model.SearchResult
allResults = append(allResults, firstPageResults...)
// 2. 并发搜索其他页面(第2页到第5页)
if MaxPages > 1 {
var wg sync.WaitGroup
var mu sync.Mutex
// 使用信号量控制并发数
semaphore := make(chan struct{}, MaxConcurrency)
// 存储每页结果
pageResults := make(map[int][]model.SearchResult)
for page := 2; page <= MaxPages; page++ {View on GitHub (pinned to beaa561337)