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

  1. Print/log the raw response body (data) to see what was actually returned — usually HTML from an anti-bot page
  2. Re-run the request with browser-like headers (User-Agent, Cookie) to bypass the HTML challenge page
  3. Check whether the upstream API response schema changed and update the target struct to match
  4. If truncation is the cause, raise maxResponseSize and re-check Content-Length handling
  5. 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

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)