{"record":{"id":"fab7dc4c7237add0","repo":"fish2018/pansou","slug":"d-w-fab7dc","errorCode":null,"errorMessage":"重试 %d 次后仍然失败: %w","messagePattern":"重试 (.+?) 次后仍然失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/cldi/cldi.go","lineNumber":207,"sourceCode":"\t\t\tbackoff := time.Duration(1<<uint(i-1)) * 200 * time.Millisecond\n\t\t\ttime.Sleep(backoff)\n\t\t}\n\n\t\t// 克隆请求\n\t\treqClone := req.Clone(req.Context())\n\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\n\t\tif resp != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t\tlastErr = err\n\t}\n\n\treturn nil, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// extractSearchResults 提取搜索结果\nfunc (p *CldiPlugin) extractSearchResults(doc *goquery.Document) []model.SearchResult {\n\tvar results []model.SearchResult\n\n\t// New cldi releases use article.resource cards and expose the BT hash in\n\t// /hash/<40-hex>.html links. The hash itself is a valid magnet identifier,\n\t// so no browser-only \"copy magnet\" action is required.\n\tdoc.Find(\"article.resource\").Each(func(_ int, article *goquery.Selection) {\n\t\tanchor := article.Find(\"h2 a[href]\").First()\n\t\thref, _ := anchor.Attr(\"href\")\n\t\thref = strings.TrimSpace(href)\n\t\tmatch := hashPathRegex.FindStringSubmatch(href)\n\t\tif len(match) < 2 {\n\t\t\treturn\n\t\t}\n\t\ttitle := p.cleanTitle(anchor.Text())","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/cldi/cldi.go#L189-L225","documentation":"CldiPlugin.doRequestWithRetry exhausts its full retry budget (maxRetries attempts) without ever getting a successful HTTP response. It keeps the last failure in lastErr and returns a wrapped error combining the retry count with the final underlying cause (timeout, connection reset, TLS error, etc.). Callers like searchPage only see this aggregate error, so the root cause is inside the %w chain.","triggerScenarios":"p.searchPage -> p.doRequestWithRetry makes maxRetries HTTP GET attempts to the CLDI search endpoint and every attempt fails (network unreachable, per-request context deadline expired, connection reset by peer, DNS failure). After the loop, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr) is returned.","commonSituations":"Target site is down or blocking the scraper (rate limiting, WAF/anti-bot dropping connections); the host has no outbound network or DNS fails; TimeoutSeconds is too short for a slow endpoint; a misconfigured proxy makes all attempts fail identically.","solutions":["Inspect the wrapped lastErr (errors.Unwrap / %v of the returned error) to identify the real cause — timeout vs connection refused vs TLS — before changing code.","Verify basic connectivity to the search endpoint with curl from the same host.","Increase maxRetries or the per-request timeout if the site is slow but reachable.","Add exponential backoff between attempts (currently linear/sleep-based) and respect Retry-After headers if the site rate-limits.","Check for anti-bot measures; update request headers (User-Agent, cookies) in the plugin if the site started blocking default clients."],"exampleFix":"// before\nreturn nil, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr)\n// after\nif errors.Is(lastErr, context.DeadlineExceeded) {\n    TimeoutSeconds *= 2 // or make it configurable\n}\nreturn nil, fmt.Errorf(\"cldi: request failed after %d retries: %w\", maxRetries, lastErr)","handlingStrategy":"retry","validationCode":"// caller-side reachability probe before invoking the plugin\nfunc reachable(rawURL string) bool {\n    resp, err := http.Head(rawURL)\n    return err == nil && resp != nil\n}","typeGuard":null,"tryCatchPattern":"results, err := plugin.Search(ctx, keyword)\nif err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() { /* increase timeout, retry */ }\n    log.Printf(\"cldi search failed after retries: %v\", err)\n    return fallbackResults\n}","preventionTips":["Pre-check endpoint reachability before batch searches.","Configure an adequate per-request timeout; a too-short timeout makes all retries fail identically.","Use jittered exponential backoff and honor rate-limit signals.","Monitor the target site for anti-bot changes; keep headers current.","Keep the wrapped lastErr in logs — always use %w, not %v."],"tags":["network","http","retry-exhausted","scraping"],"backgroundTag":"http-request-failed","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"}