{"record":{"id":"ad8669b3366e04e0","repo":"fish2018/pansou","slug":"s-api-json-w","errorCode":null,"errorMessage":"[%s] API JSON 解析失败: %w","messagePattern":"\\[(.+?)\\] API JSON 解析失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/feikuai/feikuai.go","lineNumber":144,"sourceCode":"\tif err != nil {\n\t\treturn p.searchWeb(client, keyword, fmt.Errorf(\"[%s] 搜索 API 请求失败: %w\", p.Name(), err))\n\t}\n\tdefer resp.Body.Close()\n\n\t// 检查状态码\n\tif resp.StatusCode != 200 {\n\t\treturn p.searchWeb(client, keyword, fmt.Errorf(\"[%s] 搜索 API 返回状态码: %d\", p.Name(), resp.StatusCode))\n\t}\n\n\t// 读取并解析JSON响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn p.searchWeb(client, keyword, fmt.Errorf(\"[%s] 读取 API 响应失败: %w\", p.Name(), err))\n\t}\n\n\tvar apiResp FeikuaiAPIResponse\n\tif err := json.Unmarshal(body, &apiResp); err != nil {\n\t\treturn p.searchWeb(client, keyword, fmt.Errorf(\"[%s] API JSON 解析失败: %w\", p.Name(), err))\n\t}\n\n\t// 检查API响应状态\n\tif apiResp.Code != 0 {\n\t\treturn p.searchWeb(client, keyword, fmt.Errorf(\"[%s] API 返回错误: %s (code: %d)\", p.Name(), apiResp.Msg, apiResp.Code))\n\t}\n\n\t// 解析搜索结果\n\tvar results []model.SearchResult\n\tfor _, item := range apiResp.Items {\n\t\t// 每个item可能包含多个种子\n\t\tfor _, torrent := range item.Torrents {\n\t\t\tresult := p.parseTorrent(keyword, item, torrent)\n\t\t\tif result.Title != \"\" && len(result.Links) > 0 {\n\t\t\t\tresults = append(results, result)\n\t\t\t}\n\t\t}\n\t}","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/feikuai/feikuai.go#L126-L162","documentation":"searchImpl unmarshals the API body into FeikuaiAPIResponse; if the payload is not valid JSON or does not match the struct, it falls back to searchWeb with this wrapped error. It means the endpoint answered but not with the expected JSON schema.","triggerScenarios":"json.Unmarshal(body, &apiResp) fails — HTML error page instead of JSON, empty body, changed field types (e.g. code became string), gzip content not auto-decompressed.","commonSituations":"Site replaced API with an HTML challenge page (anti-bot), API version change altering the response shape, proxy injecting a captive-portal page.","solutions":["Log a snippet of body on failure to see what was actually returned","Check Content-Type; handle non-JSON (HTML challenge) explicitly","Verify FeikuaiAPIResponse fields still match the current API schema","If Content-Encoding is gzip and not auto-decompressed, wrap with gzip.NewReader"],"exampleFix":"// before\nvar apiResp FeikuaiAPIResponse\nif err := json.Unmarshal(body, &apiResp); err != nil {\n    return p.searchWeb(client, keyword, fmt.Errorf(\"[%s] API JSON 解析失败: %w\", p.Name(), err))\n}\n// after\nvar apiResp FeikuaiAPIResponse\nif err := json.Unmarshal(body, &apiResp); err != nil {\n    log.Printf(\"feikuai non-JSON response (first 200B): %.200s\", body)\n    return p.searchWeb(client, keyword, fmt.Errorf(\"[%s] API JSON 解析失败: %w\", p.Name(), err))\n}","handlingStrategy":"validation","validationCode":"ct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    return fmt.Errorf(\"expected JSON, got %q\", ct)\n}\nif len(body) == 0 { return fmt.Errorf(\"empty body\") }","typeGuard":"func looksLikeFeikuaiAPI(b []byte) bool {\n    var probe struct { Code int `json:\"code\"` }\n    return json.Unmarshal(b, &probe) == nil\n}","tryCatchPattern":"results, err := plugin.Search(keyword)\nif err != nil && strings.Contains(err.Error(), \"JSON 解析失败\") {\n    log.Printf(\"feikuai returned non-JSON (likely challenge page); using fallback results: %v\", err)\n}","preventionTips":["Check Content-Type before unmarshaling","Log a body snippet on every unmarshal failure to spot HTML challenge pages fast","Keep the response struct annotated with json tags matching the live API","Treat schema drift as a monitored event — alert when unmarshal failures spike"],"tags":["json","parsing","api"],"backgroundTag":"json-unmarshal-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"}