fish2018/pansou · error
[ ] 解析详情响应失败
Error message
[%s] 解析详情响应失败: %w
What it means
The getVideoDetail response body failed to json.Unmarshal into lingjiDetailResponse — the body was not JSON matching the expected envelope. Analogous to the search parse failure but on the detail path.
Solutions
- Log a prefix of the raw body on failure to identify what came back
- Update the lingjiDetailResponse struct to match the current detail schema
- Check content-type before unmarshaling and convert HTML responses into a distinct upstream error
- Retry on transient corruption
- Verify the id parameter is valid — some APIs return non-JSON for unknown ids
Example fix
// before
var resp lingjiDetailResponse
if err := json.Unmarshal(body, &resp); err != nil {
return lingjiVideoItem{}, fmt.Errorf("[%s] 解析详情响应失败: %w", p.Name(), err)
}
// after
if len(body) == 0 {
return lingjiVideoItem{}, fmt.Errorf("[%s] 详情响应为空", p.Name())
}
var resp lingjiDetailResponse
if err := json.Unmarshal(body, &resp); err != nil {
return lingjiVideoItem{}, fmt.Errorf("[%s] 解析详情响应失败: %w (body=%q)", p.Name(), err, body[:min(len(body),200)])
} Defensive patterns
Strategy: validation
Validate before calling
// Require non-empty JSON object body before typed unmarshal
if len(body) == 0 || body[0] != '{' {
return lingjiVideoItem{}, fmt.Errorf("详情响应非JSON: %q", body[:min(len(body),100)])
} Type guard
func looksLikeLingjiDetail(body []byte) bool {
var probe struct {
Success *bool `json:"success"`
Code *int `json:"code"`
Data json.RawMessage `json:"data"`
}
return json.Unmarshal(body, &probe) == nil && probe.Success != nil && probe.Data != nil
} Try / catch
item, err := fetchDetail(doubID)
if err != nil && strings.Contains(err.Error(), "解析详情响应失败") {
log.Printf("lingjisp detail schema drift: %v", err)
return emptyItem, nil // skip this title, keep scraping
} Prevention
- Log raw detail bodies on failure to detect schema changes
- Keep lingjiDetailResponse in sync with the live API
- Check Content-Type/first bytes before unmarshaling
- Treat empty bodies as a distinct error for easier triage
When it happens
Trigger: doLingjiGET succeeded but returned HTML (error page), an empty body, or JSON whose shape differs from lingjiDetailResponse (e.g. data is a string instead of object), breaking unmarshal.
Common situations: CDN/WAF returned an HTML block page; API schema changed for detail responses; truncated or corrupted body on flaky networks; parked domain serving ads HTML.
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/524c0af319c7cb5a.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/lingjisp/lingjisp.go:226
}
return dedupeLingjiItems(items), nil
}
func (p *LingjiPlugin) fetchDetail(client *http.Client, doubID int) (lingjiVideoItem, error) {
params := url.Values{}
params.Set("app_id", lingjiAppID)
params.Set("identity", lingjiIdentity)
params.Set("id", fmt.Sprintf("%d", doubID))
apiURL := lingjiAPIBase + "getVideoDetail?" + params.Encode()
body, err := doLingjiGET(client, apiURL, lingjiDetailTimeout)
if err != nil {
return lingjiVideoItem{}, fmt.Errorf("[%s] 详情请求失败: %w", p.Name(), err)
}
var resp lingjiDetailResponse
if err := json.Unmarshal(body, &resp); err != nil {
return lingjiVideoItem{}, fmt.Errorf("[%s] 解析详情响应失败: %w", p.Name(), err)
}
if !resp.Success || resp.Code != http.StatusOK {
return lingjiVideoItem{}, fmt.Errorf("[%s] 详情接口返回异常: success=%v code=%d", p.Name(), resp.Success, resp.Code)
}
return resp.Data, nil
}
func doLingjiGET(client *http.Client, requestURL string, timeout time.Duration) ([]byte, error) {
var lastErr error
for attempt := 0; attempt < lingjiMaxRetries; attempt++ {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
if err != nil {
cancel()
return nil, err
}
View on GitHub (pinned to beaa561337)