fish2018/pansou · error
解析响应失败
Error message
解析响应失败: %w
What it means
requestJSON in plugin/yingso wraps any failure from jsonutil.Unmarshal of the HTTP response body with '解析响应失败: %w'. It is thrown after a 200 status is confirmed, so it means the server returned 200 with a body that is not valid JSON (or not JSON at all, e.g. HTML). The underlying unmarshal error is preserved via %w for errors.Is/As inspection.
Solutions
- Print/log the raw response body (data) to see what was actually returned — usually HTML from an anti-bot page
- Re-run the request with browser-like headers (User-Agent, Cookie) to bypass the HTML challenge page
- Check whether the upstream API response schema changed and update the target struct to match
- If truncation is the cause, raise maxResponseSize and re-check Content-Length handling
- Retry on transient gateway corruption
Example fix
// before
if err := jsonutil.Unmarshal(data, target); err != nil {
return fmt.Errorf("解析响应失败: %w", err)
}
// after
if err := jsonutil.Unmarshal(data, target); err != nil {
return fmt.Errorf("解析响应失败: %w (body=%s)", err, truncate(data, 200))
} Defensive patterns
Strategy: try-catch
Validate before calling
if !json.Valid(body) { return fmt.Errorf("响应不是有效 JSON") } Type guard
func isHTMLBody(b []byte) bool { t := bytes.TrimSpace(b); return len(t) > 0 && t[0] == '<' } Try / catch
if err := jsonutil.Unmarshal(data, target); err != nil {
var uErr *json.UnmarshalTypeError
if errors.As(err, &uErr) { /* log field/type mismatch */ }
log.Printf("非 JSON 响应: %s", truncate(data, 200))
return fmt.Errorf("解析响应失败: %w", err)
} Prevention
- Log the raw body on unmarshal failure to spot anti-bot HTML pages quickly
- Send browser-like User-Agent/Cookie headers to avoid 200 HTML challenges
- Keep maxResponseSize comfortably above the largest expected response
- Pin/monitor the upstream API schema; update structs when the site changes
When it happens
Trigger: yingso fetchConfig/search/resolveItem receive a 200 response whose body fails jsonutil.Unmarshal — typically HTML error/login pages served with status 200, truncated or maxResponseSize-clipped bodies, or an API shape change.
Common situations: The upstream site serves a Cloudflare/anti-bot HTML challenge with 200; the site changed its JSON schema; a proxy or captive portal returns HTML; the response was cut off at maxResponseSize bytes mid-JSON.
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/48fce293b2edc331.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/yingso/yingso.go:319
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))
if err != nil {
return err
}
if len(data) > maxResponseSize {
return fmt.Errorf("响应超过 %d 字节", maxResponseSize)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(data)))
}
if err := jsonutil.Unmarshal(data, target); err != nil {
return fmt.Errorf("解析响应失败: %w", err)
}
return nil
}
func encryptPayload(payload interface{}, config apiConfig) (encryptedPayload, error) {
no, err := randomToken(24)
if err != nil {
return encryptedPayload{}, err
}
if config.Start < 0 || config.End <= config.Start || config.End > len(no) {
return encryptedPayload{}, fmt.Errorf("无效的加密区间 %d:%d", config.Start, config.End)
}
encoded, err := jsonutil.MarshalString(payload)
if err != nil {
return encryptedPayload{}, err
}
return encryptedPayload{View on GitHub (pinned to beaa561337)