{"record":{"id":"9cac0d7a002fd092","repo":"fish2018/pansou","slug":"d-w-9cac0d","errorCode":null,"errorMessage":"重试 %d 次后仍然失败: %w","messagePattern":"重试 (.+?) 次后仍然失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/qingying/qingying.go","lineNumber":448,"sourceCode":"\tfor i := 0; i < maxRetries; i++ {\n\t\tif i > 0 {\n\t\t\tbackoff := time.Duration(1<<uint(i-1)) * 200 * time.Millisecond\n\t\t\ttime.Sleep(backoff)\n\t\t}\n\t\t\n\t\treqClone := req.Clone(req.Context())\n\t\tresp, err := client.Do(reqClone)\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn resp, nil\n\t\t}\n\t\t\n\t\tif resp != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t\tlastErr = err\n\t}\n\t\n\treturn nil, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr)\n}\n","sourceCodeStart":430,"sourceCodeEnd":450,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/qingying/qingying.go#L430-L450","documentation":"Qingying plugin's doRequestWithRetry wraps the last error after exhausting maxRetries attempts. It means the request failed on every try with a transport-level error; the final underlying error is preserved via %w. Callers fetchSearchResults and processDetailPage wrap it further.","triggerScenarios":"All maxRetries attempts inside the retry loop returned err — DNS failure, connection refused/reset, TLS error, or per-attempt context timeout — for either the search request or a detail page request.","commonSituations":"The target site is unreachable from this network (blocked or down); anti-bot drops connections; slow responses exceed the timeout on every attempt; transient ISP/network outage.","solutions":["Unwrap lastErr to classify the root cause (errors.As for net.Error timeout, *url.Error, syscall.ECONNREFUSED).","Test the exact URL with curl to confirm whether the site or the local network is at fault.","Increase maxRetries/timeout for transient slowness; add exponential backoff between attempts.","Configure a proxy or rotate exit IPs if the site blocks this client; skip failing URLs gracefully in callers."],"exampleFix":"// before\nreturn nil, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr)\n// after\nvar nerr net.Error\nif errors.As(lastErr, &nerr) && nerr.Timeout() {\n    return nil, fmt.Errorf(\"重试 %d 次后仍然失败(请求超时): %w\", maxRetries, lastErr)\n}\nreturn nil, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr)","handlingStrategy":"fallback","validationCode":"u, _ := url.Parse(baseURL)\nconn, err := net.DialTimeout(\"tcp\", net.JoinHostPort(u.Hostname(), \"443\"), 5*time.Second)\nif err != nil {\n    log.Println(\"site unreachable, using cached results\")\n    return\n}\nconn.Close()","typeGuard":"func isRetryExhausted(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"重试\")\n}","tryCatchPattern":"results, err := plugin.Search(keyword)\nif err != nil {\n    var nerr net.Error\n    if errors.As(err, &nerr) && nerr.Timeout() {\n        log.Println(\"qingying timed out after retries; serving cache\")\n    }\n    return cachedResults, nil\n}","preventionTips":["Keep cached/last-good results as a fallback for search failures.","Classify the unwrapped cause (timeout vs refused vs TLS) before retrying.","Use exponential backoff rather than tight retry loops.","Diversify sources so one unreachable site degrades gracefully."],"tags":["network","retry-exhausted","http-client","timeout"],"backgroundTag":"retry-exhausted","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"}