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

  1. Read the wrapped cause (%w) to see which of searchPage's errors fired (request failed / unexpected status code / api returned error)
  2. 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 个错误'
  3. If rate-limited (concurrent requests), serialize or throttle the per-page goroutines
  4. 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

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)