{"record":{"id":"6b65b16393f46c4c","repo":"fish2018/pansou","slug":"s-http-d-6b65b1","errorCode":null,"errorMessage":"[%s] 搜索请求返回 HTTP %d","messagePattern":"\\[(.+?)\\] 搜索请求返回 HTTP (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/haitunsou/haitunsou.go","lineNumber":101,"sourceCode":"\t\tclient = http.DefaultClient\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), requestTimeout)\n\tdefer cancel()\n\tsearchURL := fmt.Sprintf(\"%s/s/%s.html\", strings.TrimRight(p.baseURL, \"/\"), url.PathEscape(keyword))\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建搜索请求失败: %w\", p.Name(), err)\n\t}\n\tsetRequestHeaders(req, p.baseURL)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求返回 HTTP %d\", p.Name(), resp.StatusCode)\n\t}\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取搜索响应失败: %w\", p.Name(), err)\n\t}\n\tif len(body) > maxResponseSize {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索响应超过 %d 字节\", p.Name(), maxResponseSize)\n\t}\n\n\titems, err := parseEmbeddedList(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索结果失败: %w\", p.Name(), err)\n\t}\n\tresults := make([]model.SearchResult, 0, len(items))\n\tseen := make(map[string]struct{}, len(items))\n\tfor _, item := range items {\n\t\tresult, ok := convertItem(item)\n\t\tif !ok {","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/haitunsou/haitunsou.go#L83-L119","documentation":"Returned by searchImpl when the HTTP response status code is anything other than 200 OK. The plugin treats only a 200 as a valid search response, so 403/429 anti-bot blocks, 5xx outages, and 3xx redirects that the client did not follow all land here. Note the status code is embedded with %d and NOT wrapped with %w, so it cannot be unwrapped programmatically — parse the message text to recover the code.","triggerScenarios":"The haitunsou server responds with HTTP status != 200: 403 (Cloudflare/WAF block of the User-Agent), 429 rate limiting, 5xx server errors, 404 if the site changes its URL scheme from /s/<keyword>.html.","commonSituations":"Site deploys stricter anti-bot protection (JS challenge/Cloudflare) that the static Chrome User-Agent headers no longer satisfy; the site migrates domains or changes its search path; burst querying triggers rate limits.","solutions":["Log the status code to identify the class: 403/503 suggests bot-blocking, 404 suggests URL scheme change, 429/5xx suggests retry with backoff","Update setRequestHeaders to mimic current browser headers (fresh User-Agent, cookie handling, Accept-Encoding)","Handle site domain migration by updating the plugin's baseURL","Add retry with exponential backoff for 429/5xx responses"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var apiErr *APIStatusError // if your wrapper exposes one\nif errors.As(err, &apiErr) {\n    switch {\n    case apiErr.Code == 429: time.Sleep(backoff); retry()\n    case apiErr.Code >= 500: retry()\n    case apiErr.Code == 404: alertURLSchemeChanged()\n    }\n}","preventionTips":["Parse the status code out of the message text (it is not wrapped with %w)","Retry only 429/5xx with backoff; alert on 404 (URL scheme change) and 403 (bot block)","Keep browser-like headers current","Throttle request rate to avoid rate limiting"],"tags":["http","anti-bot","status-code"],"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"}