fish2018/pansou · error
重试 次后仍然失败
Error message
重试 %d 次后仍然失败: %w
What it means
This final error from yunsou's doRequestWithRetry means every one of maxRetries attempts failed — either with a network error or a non-200 status (the last failure is wrapped via %w). The page fetch is abandoned and fetchPage surfaces it as '第%d页搜索请求失败'. It is the plugin's definitive 'upstream unreachable or rejecting us' signal.
Solutions
- Read the wrapped lastErr to distinguish network failure vs bad status code.
- Test reachability manually with curl using the same headers.
- Increase maxRetries and add exponential backoff between attempts.
- If IP is blocked, use a proxy or wait for the block to expire.
- Fail gracefully: skip the failing page instead of aborting the entire search.
Example fix
// before
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
// after
for attempt := 1; attempt <= maxRetries; attempt++ {
...
time.Sleep(time.Duration(attempt) * time.Second) // exponential backoff
}
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr) Defensive patterns
Strategy: retry
Validate before calling
func siteUp(u string) bool {
c := &http.Client{Timeout: 5 * time.Second}
resp, err := c.Head(u)
return err == nil && resp.StatusCode < 500
} Try / catch
results, err := plugin.Search(keyword)
if err != nil {
var retried *RetryExhaustedError
if errors.As(err, &retried) {
return fallbackPlugin.Search(keyword) // switch to another plugin/mirror
}
return nil, err
} Prevention
- Pre-verify mirror availability before searching
- Use exponential backoff, not tight retry loops
- Keep multiple mirrors configured for fallback
- Alert on sustained retry exhaustion per host
When it happens
Trigger: Loop in doRequestWithRetry completes with resp==nil or status!=200 on all attempts; lastErr (network error or '状态码 %d') is wrapped and returned to fetchPage.
Common situations: Sustained site outage, IP blocked by the site's firewall, no internet connection, or DNS misconfiguration in a container/CI environment.
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/623d27284779b29b.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/yunsou/yunsou.go:156
func (p *YunsouAsyncPlugin) doRequestWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {
var lastErr error
for attempt := 0; attempt < maxRetries; attempt++ {
if attempt > 0 {
time.Sleep(time.Duration(1<<(attempt-1)) * 200 * time.Millisecond)
}
resp, err := client.Do(req.Clone(req.Context()))
if err == nil && resp.StatusCode == http.StatusOK {
return resp, nil
}
if resp != nil {
lastErr = fmt.Errorf("状态码 %d", resp.StatusCode)
resp.Body.Close()
} else {
lastErr = err
}
}
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
}
func (p *YunsouAsyncPlugin) parseSearchResults(doc *goquery.Document) []model.SearchResult {
results := make([]model.SearchResult, 0)
doc.Find(".list .item").Each(func(_ int, item *goquery.Selection) {
if len(results) >= maxResults {
return
}
title, _ := item.Attr("data-title")
if title == "" {
title = item.Find(".title").First().Text()
}
title = cleanText(html.UnescapeString(title))
if title == "" {
return
}
shareURL, password := "", ""View on GitHub (pinned to beaa561337)