{"record":{"id":"a31218f9632b1a0c","repo":"fish2018/pansou","slug":"http-d-a31218","errorCode":null,"errorMessage":"HTTP状态码: %d","messagePattern":"HTTP状态码: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"plugin/huban/huban.go","lineNumber":618,"sourceCode":"\t\treturn matches[1]\n\t}\n\n\treturn \"\"\n}\n\n// doRequestWithRetry 带重试的HTTP请求\nfunc (p *HubanAsyncPlugin) doRequestWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {\n\tmaxRetries := 2\n\tvar lastErr error\n\n\tfor i := 0; i < maxRetries; i++ {\n\t\tresp, err := client.Do(req)\n\t\tif err == nil {\n\t\t\tif resp.StatusCode == http.StatusOK {\n\t\t\t\treturn resp, nil\n\t\t\t}\n\t\t\tresp.Body.Close()\n\t\t\tlastErr = fmt.Errorf(\"HTTP状态码: %d\", resp.StatusCode)\n\t\t} else {\n\t\t\tlastErr = err\n\t\t}\n\t\tif req.Context().Err() != nil {\n\t\t\treturn nil, req.Context().Err()\n\t\t}\n\n\t\t// 快速重试：只等待很短时间\n\t\tif i < maxRetries-1 {\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"[%s] 请求失败，重试%d次后仍失败: %w\", p.Name(), maxRetries, lastErr)\n}\n\n// GetPerformanceStats 获取性能统计信息\nfunc (p *HubanAsyncPlugin) GetPerformanceStats() map[string]interface{} {","sourceCodeStart":600,"sourceCodeEnd":636,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/huban/huban.go#L600-L636","documentation":"Inside doRequestWithRetry, each attempt that completes without a transport error but with a non-200 status closes the body and stores \"HTTP状态码: %d\" as lastErr, then retries. This is the internal error describing why an attempt was rejected.","triggerScenarios":"client.Do succeeds but resp.StatusCode != http.StatusOK on every retry attempt of doRequestWithRetry, called from searchAtBase or fetchDetailLinksAndImages (e.g. persistent 403/429/503 from the huban site).","commonSituations":"Persistent rate limiting (429) during burst scraping, IP banned by the target site, WAF/Cloudflare challenge, site-side 5xx outage across all retries.","solutions":["This error is internal: it surfaces wrapped by the retry-exhausted error (index 273); read the status code from the wrapped message","Add longer backoff between retries to survive 429 windows","Use proxies / rotate egress IPs if the site is blocking yours","Check target site health independently (browser/curl) to rule out an outage","Consider treating 4xx client errors as non-retryable to fail fast"],"exampleFix":"// before\nresp.Body.Close()\nlastErr = fmt.Errorf(\"HTTP状态码: %d\", resp.StatusCode)\n// after\nresp.Body.Close()\nlastErr = fmt.Errorf(\"HTTP状态码: %d\", resp.StatusCode)\nif resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusTooManyRequests {\n\ttime.Sleep(time.Duration(1<<i) * time.Second) // exponential backoff\n}","handlingStrategy":"retry","validationCode":"resp, err := http.Get(targetURL)\nif err == nil && resp.StatusCode == http.StatusTooManyRequests {\n\t// wait out the rate-limit window before calling the plugin\n}","typeGuard":null,"tryCatchPattern":"// this is an internal retry-loop error; consume it via the final wrapped error\nif _, err := plugin.Search(kw); err != nil {\n\tvar statusErr error\n\tif errors.Unwrap(err) != nil { statusErr = errors.Unwrap(err) }\n\tlog.Printf(\"retries exhausted, last cause: %v\", statusErr)\n}","preventionTips":["Keep upstream QPS low enough that each attempt succeeds within maxRetries","Add exponential backoff instead of fixed 100ms sleeps","Pre-check upstream health before bursts of requests","Use proxies when the target site blocks datacenter IPs"],"tags":["http","retry","status-code","network"],"backgroundTag":"http-non-200-response","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}