{"record":{"id":"e8d00c87cb0170b3","repo":"fish2018/pansou","slug":"s-w-e8d00c","errorCode":null,"errorMessage":"[%s] 搜索请求失败: %w","messagePattern":"\\[(.+?)\\] 搜索请求失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/cldi/cldi.go","lineNumber":145,"sourceCode":"\tsearchURL := fmt.Sprintf(\"%s/search-%s-0-2-%d.html\", baseURL, url.QueryEscape(keyword), page)\n\n\t// 创建带超时的上下文\n\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer cancel()\n\n\t// 创建请求\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", searchURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建请求失败: %w\", p.Name(), err)\n\t}\n\n\t// 设置请求头\n\tp.setRequestHeaders(req)\n\n\t// 发送请求\n\tresp, err := p.doRequestWithRetry(req, client)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 检查状态码\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"[%s] 请求返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n\t}\n\n\t// 解析HTML\n\tdoc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] HTML解析失败: %w\", p.Name(), err)","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/cldi/cldi.go#L127-L163","documentation":"CldiPlugin.searchPage wraps errors from doRequestWithRetry with the plugin name. This covers transport-level failures of the search request after internal retries: DNS failure, connection refused/reset, TLS errors, or context deadline exceeded (30s timeout). The underlying cause is preserved via %w.","triggerScenarios":"p.doRequestWithRetry(req, client) returns err in searchPage — network unreachable, server down, or the 30-second context timeout elapsed before a response.","commonSituations":"Target site blocking the client's IP, firewall/proxy interference, slow site exceeding the 30s timeout, or transient network outages.","solutions":["Unwrap with errors.Is(err, context.DeadlineExceeded) to distinguish timeout from connection failure.","Test reachability of the search URL with curl and the same headers.","Increase the 30s timeout if the site is slow, or add proxy rotation if blocked.","Retry with backoff; check for rate limiting or IP bans."],"exampleFix":"// before\nresp, err := p.doRequestWithRetry(req, client)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n}\n// after\nresp, err := p.doRequestWithRetry(req, client)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        return nil, fmt.Errorf(\"[%s] 搜索请求超时(30s): %w\", p.Name(), err)\n    }\n    return nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n}","handlingStrategy":"retry","validationCode":"// preflight reachability\nconn, err := net.DialTimeout(\"tcp\", host+\":443\", 5*time.Second)\nif err != nil { log.Printf(\"搜索站点不可达: %v\", err) } else { conn.Close() }","typeGuard":null,"tryCatchPattern":"results, err := plugin.Search(ctx, keyword)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        // timeout: retry with backoff or raise the 30s limit\n    } else if strings.Contains(err.Error(), \"搜索请求失败\") {\n        // transport failure: check network/proxy, consider fallback plugin\n    }\n}","preventionTips":["Size the 30s context timeout to the site's real latency (measure it)","Use retry-with-backoff inside doRequestWithRetry for transient errors","Route through healthy proxies when the site blocks datacenter IPs","Distinguish timeouts from connection errors when logging"],"tags":["network","http","timeout","plugin"],"backgroundTag":"network-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"}