{"record":{"id":"0415ac6c949465c5","repo":"fish2018/pansou","slug":"unexpected-http-status-s","errorCode":null,"errorMessage":"unexpected HTTP status: %s","messagePattern":"unexpected HTTP status: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/meitizy/meitizy.go","lineNumber":393,"sourceCode":"\t\tif req.Body != nil {\n\t\t\t// 读取原始body\n\t\t\tbodyBytes, err := io.ReadAll(req.Body)\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))\n\t\t\treqClone.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))\n\t\t}\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\tif err == nil {\n\t\t\t\tlastErr = fmt.Errorf(\"unexpected HTTP status: %s\", resp.Status)\n\t\t\t}\n\t\t\tresp.Body.Close()\n\t\t}\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"[%s] 重试 %d 次后仍然失败: %w\", p.Name(), maxRetries, lastErr)\n}\n","sourceCodeStart":375,"sourceCodeEnd":404,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/meitizy/meitizy.go#L375-L404","documentation":"doRequestWithRetry in the Meitizy plugin only accepts HTTP 200 responses; any other status (e.g. 403, 429, 5xx) is recorded as \"unexpected HTTP status: %s\" using resp.Status. It is a retry wrapper around client.Do called by searchImpl and fetchDetailLinks; a non-200 on each attempt means the upstream meitizy endpoint rejected the request or is failing.","triggerScenarios":"Any call to p.searchImpl or p.fetchDetailLinks where the meitizy server answers with a status other than 200 on all retry attempts: rate limiting (429), WAF/anti-bot blocking (403), gateway errors (502/503), or a moved endpoint returning 404.","commonSituations":"The site's anti-scraping protection blocks the plugin's requests (missing/invalid cookies or fingerprint), the target URL changed, heavy keyword use triggers rate limiting, or the upstream service is temporarily down.","solutions":["Check the full wrapped status string (e.g. 429 Too Many Requests) to identify the class of problem; for 429 back off and retry later","Inspect and refresh any required cookies/anti-bot tokens or headers for meitizy; 403 usually means WAF blocking","Verify the upstream endpoint URL is still valid (404/301 suggest the site changed)","If 5xx, treat as upstream outage and retry after a delay; check site availability manually"],"exampleFix":"// before: only 200 accepted, error loses status code detail beyond resp.Status\nif err == nil && resp.StatusCode == 200 {\n\treturn resp, nil\n}\n// after: log body snippet for non-200 to aid diagnosis\nif err == nil && resp.StatusCode != 200 {\n\tb, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\tlastErr = fmt.Errorf(\"unexpected HTTP status: %s body=%q\", resp.Status, b)\n}","handlingStrategy":"retry","validationCode":"if resp.StatusCode == http.StatusTooManyRequests {\n\tif ra, err := strconv.Atoi(resp.Header.Get(\"Retry-After\")); err == nil {\n\t\ttime.Sleep(time.Duration(ra) * time.Second)\n\t}\n}","typeGuard":null,"tryCatchPattern":"resp, err := doRequestWithRetry(req, client)\nif err != nil {\n\tvar statusErr *fmt.Errorf\n\tif errors.As(err, &statusErr) && strings.Contains(err.Error(), \"unexpected HTTP status\") {\n\t\t// handle non-200: back off, refresh credentials, or degrade\n\t}\n\treturn nil, err\n}","preventionTips":["Log resp.Status and a body snippet on every non-200 to distinguish 403/429/5xx","Refresh anti-bot cookies/headers before they expire","Throttle request rate to stay under the site's limits","Alert on repeated retry-exhaustion for the same endpoint"],"tags":["http","network","retry","scraping"],"backgroundTag":"unexpected-http-status","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"}