fish2018/pansou · error
page search failed
Error message
page %d search failed: %w
What it means
Melost plugin's doSearch fans out one goroutine per page calling p.searchPage; any error from a page is wrapped as "page %d search failed: %w" and sent to errChan. If all pages fail (len(allItems)==0), the first such error is returned, so this wrapper typically carries an underlying cause like a network failure, non-200 status, or API error from searchPage.
Solutions
- Read the wrapped cause (%w) to see which of searchPage's errors fired (request failed / unexpected status code / api returned error)
- Note errors are only surfaced when ALL pages return zero items; if some pages succeed the search still returns partial results — check debug logs '收集到 N 条原始结果,M 个错误'
- If rate-limited (concurrent requests), serialize or throttle the per-page goroutines
- Verify melost.cn API availability and that MelostSearchAPI is still the correct endpoint
Example fix
// before: all errors dropped except errors[0]
if len(allItems) == 0 && len(errors) > 0 {
return nil, errors[0]
}
// after: join all page errors for full context
if len(allItems) == 0 && len(errors) > 0 {
return nil, errors.Join(errors...)
} Defensive patterns
Strategy: try-catch
Validate before calling
// check upstream health before fanning out
if _, err := client.Get("https://www.melost.cn"); err != nil {
return nil, fmt.Errorf("melost unavailable: %w", err)
} Try / catch
results, err := p.doSearch(client, keyword, ext)
if err != nil {
var pageErr error
if errors.As(err, &pageErr) && strings.Contains(pageErr.Error(), "page ") {
log.Printf("melost page failure: %v", pageErr)
}
return nil, err
} Prevention
- Check the underlying wrapped error before blaming the fan-out logic
- Throttle per-page goroutines to avoid tripping server rate limits
- Treat partial success (some pages OK) as acceptable; log skipped pages
- Keep debug logging enabled to correlate item/error counts per search
When it happens
Trigger: Any of the DefaultMaxPages concurrent searchPage goroutines fails — e.g. the melost.cn API returns a non-200 status, times out, or returns apiResp.Code != 200 for that page; this wrapper adds the page number.
Common situations: melost.cn is down or rate-limiting concurrent requests (all goroutines fire simultaneously), timeout under DefaultTimeout due to slow upstream, or the search API changed its endpoint/contract.
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/fca96abd43657b29.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/melost/melost.go:88
}
// doSearch 实际搜索实现
func (p *MelostAsyncPlugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
debugLog("开始搜索,关键词: %s", keyword)
resultChan := make(chan []MelostItem, DefaultMaxPages)
errChan := make(chan error, DefaultMaxPages)
var wg sync.WaitGroup
for page := 1; page <= DefaultMaxPages; page++ {
wg.Add(1)
go func(pageNum int) {
defer wg.Done()
items, err := p.searchPage(client, keyword, pageNum)
if err != nil {
errChan <- fmt.Errorf("page %d search failed: %w", pageNum, err)
return
}
resultChan <- items
}(page)
}
go func() {
wg.Wait()
close(resultChan)
close(errChan)
}()
var allItems []MelostItem
for items := range resultChan {
allItems = append(allItems, items...)
}
View on GitHub (pinned to beaa561337)