fish2018/pansou · error
[ ] API JSON 解析失败
Error message
[%s] API JSON 解析失败: %w
What it means
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.
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
Example fix
// before
var apiResp FeikuaiAPIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] API JSON 解析失败: %w", p.Name(), err))
}
// after
var apiResp FeikuaiAPIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
log.Printf("feikuai non-JSON response (first 200B): %.200s", body)
return p.searchWeb(client, keyword, fmt.Errorf("[%s] API JSON 解析失败: %w", p.Name(), err))
} Defensive patterns
Strategy: validation
Validate before calling
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
return fmt.Errorf("expected JSON, got %q", ct)
}
if len(body) == 0 { return fmt.Errorf("empty body") } Type guard
func looksLikeFeikuaiAPI(b []byte) bool {
var probe struct { Code int `json:"code"` }
return json.Unmarshal(b, &probe) == nil
} Try / catch
results, err := plugin.Search(keyword)
if err != nil && strings.Contains(err.Error(), "JSON 解析失败") {
log.Printf("feikuai returned non-JSON (likely challenge page); using fallback results: %v", err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Site replaced API with an HTML challenge page (anti-bot), API version change altering the response shape, proxy injecting a captive-portal page.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/ad8669b3366e04e0.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/feikuai/feikuai.go:144
if err != nil {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 搜索 API 请求失败: %w", p.Name(), err))
}
defer resp.Body.Close()
// 检查状态码
if resp.StatusCode != 200 {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 搜索 API 返回状态码: %d", p.Name(), resp.StatusCode))
}
// 读取并解析JSON响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 读取 API 响应失败: %w", p.Name(), err))
}
var apiResp FeikuaiAPIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] API JSON 解析失败: %w", p.Name(), err))
}
// 检查API响应状态
if apiResp.Code != 0 {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] API 返回错误: %s (code: %d)", p.Name(), apiResp.Msg, apiResp.Code))
}
// 解析搜索结果
var results []model.SearchResult
for _, item := range apiResp.Items {
// 每个item可能包含多个种子
for _, torrent := range item.Torrents {
result := p.parseTorrent(keyword, item, torrent)
if result.Title != "" && len(result.Links) > 0 {
results = append(results, result)
}
}
}View on GitHub (pinned to beaa561337)