{"record":{"id":"8c6af16bb7c23164","repo":"fish2018/pansou","slug":"unexpected-status-code-d-8c6af1","errorCode":null,"errorMessage":"unexpected status code: %d","messagePattern":"unexpected status code: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/melost/melost.go","lineNumber":177,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create request failed: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Accept\", \"application/json, text/plain, */*\")\n\treq.Header.Set(\"Accept-Language\", \"zh-CN,zh;q=0.9,en;q=0.8\")\n\treq.Header.Set(\"Origin\", \"https://www.melost.cn\")\n\treq.Header.Set(\"Referer\", DefaultReferer)\n\treq.Header.Set(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"request failed: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read response failed: %w\", err)\n\t}\n\n\tvar apiResp MelostResponse\n\tif err := json.Unmarshal(respBody, &apiResp); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode response failed: %w\", err)\n\t}\n\n\tif apiResp.Code != 200 {\n\t\treturn nil, fmt.Errorf(\"api returned error: %s\", apiResp.Msg)\n\t}\n\n\treturn apiResp.Data.List, nil\n}","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/melost/melost.go#L159-L195","documentation":"After client.Do succeeds, searchPage requires HTTP 200; any other status returns \"unexpected status code: %d\". This means melost.cn responded but rejected the search request — the plugin treats anything non-200 as a failed page search.","triggerScenarios":"The melost.cn search API returns 403 (anti-bot/WAF block), 429 (rate limited by the concurrent per-page requests), 404 (endpoint moved), or 5xx (server error) for a searchPage POST.","commonSituations":"Firing DefaultMaxPages requests simultaneously trips rate limiting; site anti-scraping protections reject the plugin's fixed headers/UA; melost.cn changed its API path or is temporarily down.","solutions":["Log resp.Status (not just the code) and, ideally, the response body to see whether it's 403 WAF, 429 rate-limit, or 5xx","For 429: throttle or serialize the per-page goroutines and add backoff","For 403: update headers/cookies/UA to match what the site currently expects","For 404: update the MelostSearchAPI constant after a site migration","Remember doSearch tolerates partial failures — this only fails the whole search when all pages fail"],"exampleFix":"// before\nif resp.StatusCode != http.StatusOK {\n\treturn nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n}\n// after\nif resp.StatusCode != http.StatusOK {\n\tb, _ := io.ReadAll(io.LimitReader(resp.Body, 256))\n\treturn nil, fmt.Errorf(\"unexpected status code: %d (%s) body=%q\", resp.StatusCode, resp.Status, b)\n}","handlingStrategy":"retry","validationCode":"// pre-flight: confirm endpoint still exists\nresp, err := client.Head(MelostSearchAPI)\nif err == nil && (resp.StatusCode == 404 || resp.StatusCode == 410) {\n\tlog.Printf(\"melost endpoint moved (status %d)\", resp.StatusCode)\n}","typeGuard":null,"tryCatchPattern":"if resp.StatusCode != http.StatusOK {\n\tswitch {\n\tcase resp.StatusCode == 429:\n\t\t// back off and retry the page\n\tcase resp.StatusCode == 403:\n\t\t// refresh cookies/headers\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}","preventionTips":["Limit concurrency of page requests to avoid 429s","Keep UA/headers in sync with the site's current expectations","Alert on 404/410 to catch endpoint migrations early","Log resp.Status and body snippet for every non-200"],"tags":["http","scraping","rate-limit","anti-bot"],"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"}