{"record":{"id":"cf40a2ad0cc0af5f","repo":"fish2018/pansou","slug":"d-cf40a2","errorCode":null,"errorMessage":"搜索响应状态码异常: %d","messagePattern":"搜索响应状态码异常: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/xiaozhang/xiaozhang.go","lineNumber":151,"sourceCode":"\n// searchImpl 实际的搜索实现\nfunc (p *XiaozhangPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {\n\tsearchURL := fmt.Sprintf(\"%s%s?keyword=%s\", BaseURL, SearchPath, url.QueryEscape(keyword))\n\t\n\tif p.debugMode {\n\t\tlog.Printf(\"[Xiaozhang] 开始搜索: %s\", keyword)\n\t\tlog.Printf(\"[Xiaozhang] 搜索URL: %s\", searchURL)\n\t}\n\t\n\t// 发送搜索请求\n\tresp, err := p.doRequest(client, searchURL, BaseURL, true)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"发送搜索请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\t\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"搜索响应状态码异常: %d\", resp.StatusCode)\n\t}\n\t\n\t// 处理响应体（可能是gzip压缩的）\n\tvar reader io.Reader = resp.Body\n\t\n\t// 检查Content-Encoding\n\tcontentEncoding := resp.Header.Get(\"Content-Encoding\")\n\tif p.debugMode {\n\t\tlog.Printf(\"[Xiaozhang] Content-Encoding: %s\", contentEncoding)\n\t\tlog.Printf(\"[Xiaozhang] Content-Type: %s\", resp.Header.Get(\"Content-Type\"))\n\t}\n\t\n\t// 如果是gzip压缩，手动解压\n\tif contentEncoding == \"gzip\" {\n\t\tgzReader, err := gzip.NewReader(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"创建gzip reader失败: %w\", err)\n\t\t}","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/xiaozhang/xiaozhang.go#L133-L169","documentation":"The xiaozhang plugin's searchImpl got a valid HTTP response from the search endpoint, but the status code was not 200. The plugin only treats HTTP 200 as a successful search result page; anything else (3xx followed into an unexpected page, 4xx anti-bot block, 5xx server error) triggers this error with the numeric status embedded. It indicates the upstream site responded but refused or failed the search request.","triggerScenarios":"Calling Search on the xiaozhang plugin when the search URL returns any non-200 status: 403/429 from anti-crawler WAF, 404 after a site path change, 500/502/503 server errors, or a redirect chain that lands on a non-200 page (redirects are followed since followRedirect=true).","commonSituations":"The site deployed anti-bot protection and starts serving 403/429 to the plugin's User-Agent; the site restructured and SearchPath now 404s; the site is rate-limiting after frequent scraping; or a CDN serves 5xx during an outage.","solutions":["Log the full status code and response body snippet to identify which non-200 class is returned.","For 403/429, update request headers (User-Agent, cookies) in setRequestHeaders to look less like a bot, or add rate limiting/backoff.","For 404, verify SearchPath against the current site layout and update the constant.","For 5xx, retry with exponential backoff — the upstream may be temporarily down.","Check whether the site now requires cookies/JS challenge (Cloudflare etc.) and handle accordingly."],"exampleFix":"// before\nif resp.StatusCode != http.StatusOK {\n    return nil, fmt.Errorf(\"搜索响应状态码异常: %d\", resp.StatusCode)\n}\n// after\nif resp.StatusCode != http.StatusOK {\n    body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n    return nil, fmt.Errorf(\"搜索响应状态码异常: %d, body: %s\", resp.StatusCode, string(body))\n}","handlingStrategy":"retry","validationCode":"// Go: probe the endpoint and inspect status before trusting search results\nresp, err := http.Head(searchURL)\nif err != nil {\n    return fmt.Errorf(\"probe failed: %w\", err)\n}\nresp.Body.Close()\nif resp.StatusCode != http.StatusOK {\n    return fmt.Errorf(\"upstream unhealthy, status %d\", resp.StatusCode)\n}","typeGuard":"// Go: distinguish rate-limit/blocks from server errors\nfunc classifyStatus(code int) string {\n    switch {\n    case code == 200:\n        return \"ok\"\n    case code == 429 || code == 403:\n        return \"blocked\"\n    case code >= 500:\n        return \"server-error\"\n    default:\n        return \"other\"\n    }\n}","tryCatchPattern":"results, err := plugin.Search(keyword, ext)\nif err != nil {\n    var statusErr *statusCodeError // if plugin exposes a typed error; otherwise parse message\n    if strings.Contains(err.Error(), \"状态码异常\") {\n        // non-200: back off then retry; if still blocked, skip this source\n        time.Sleep(backoff)\n        results, err = plugin.Search(keyword, ext)\n    }\n    if err != nil {\n        log.Printf(\"xinjuc/xiaozhang search skipped: %v\", err)\n    }\n}","preventionTips":["Send realistic browser headers (User-Agent, Accept, Referer) to reduce 403 anti-bot responses.","Rate-limit scraping requests per host to avoid 429s.","Monitor status codes per plugin and alert on sustained non-200 rates.","Verify SearchPath constants still exist on the live site after site redesigns.","Treat 5xx as retryable and 4xx as configuration/blocks — handle them differently."],"tags":["http-status","anti-bot","search-plugin","http-response"],"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"}