{"record":{"id":"524c0af319c7cb5a","repo":"fish2018/pansou","slug":"s-w-524c0a","errorCode":null,"errorMessage":"[%s] 解析详情响应失败: %w","messagePattern":"\\[(.+?)\\] 解析详情响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/lingjisp/lingjisp.go","lineNumber":226,"sourceCode":"\t}\n\treturn dedupeLingjiItems(items), nil\n}\n\nfunc (p *LingjiPlugin) fetchDetail(client *http.Client, doubID int) (lingjiVideoItem, error) {\n\tparams := url.Values{}\n\tparams.Set(\"app_id\", lingjiAppID)\n\tparams.Set(\"identity\", lingjiIdentity)\n\tparams.Set(\"id\", fmt.Sprintf(\"%d\", doubID))\n\n\tapiURL := lingjiAPIBase + \"getVideoDetail?\" + params.Encode()\n\tbody, err := doLingjiGET(client, apiURL, lingjiDetailTimeout)\n\tif err != nil {\n\t\treturn lingjiVideoItem{}, fmt.Errorf(\"[%s] 详情请求失败: %w\", p.Name(), err)\n\t}\n\n\tvar resp lingjiDetailResponse\n\tif err := json.Unmarshal(body, &resp); err != nil {\n\t\treturn lingjiVideoItem{}, fmt.Errorf(\"[%s] 解析详情响应失败: %w\", p.Name(), err)\n\t}\n\tif !resp.Success || resp.Code != http.StatusOK {\n\t\treturn lingjiVideoItem{}, fmt.Errorf(\"[%s] 详情接口返回异常: success=%v code=%d\", p.Name(), resp.Success, resp.Code)\n\t}\n\treturn resp.Data, nil\n}\n\nfunc doLingjiGET(client *http.Client, requestURL string, timeout time.Duration) ([]byte, error) {\n\tvar lastErr error\n\n\tfor attempt := 0; attempt < lingjiMaxRetries; attempt++ {\n\t\tctx, cancel := context.WithTimeout(context.Background(), timeout)\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/lingjisp/lingjisp.go#L208-L244","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nvar resp lingjiDetailResponse\nif err := json.Unmarshal(body, &resp); err != nil {\n    return lingjiVideoItem{}, fmt.Errorf(\"[%s] 解析详情响应失败: %w\", p.Name(), err)\n}\n// after\nif len(body) == 0 {\n    return lingjiVideoItem{}, fmt.Errorf(\"[%s] 详情响应为空\", p.Name())\n}\nvar resp lingjiDetailResponse\nif err := json.Unmarshal(body, &resp); err != nil {\n    return lingjiVideoItem{}, fmt.Errorf(\"[%s] 解析详情响应失败: %w (body=%q)\", p.Name(), err, body[:min(len(body),200)])\n}","handlingStrategy":"validation","validationCode":"// Require non-empty JSON object body before typed unmarshal\nif len(body) == 0 || body[0] != '{' {\n    return lingjiVideoItem{}, fmt.Errorf(\"详情响应非JSON: %q\", body[:min(len(body),100)])\n}","typeGuard":"func looksLikeLingjiDetail(body []byte) bool {\n    var probe struct {\n        Success *bool `json:\"success\"`\n        Code    *int  `json:\"code\"`\n        Data    json.RawMessage `json:\"data\"`\n    }\n    return json.Unmarshal(body, &probe) == nil && probe.Success != nil && probe.Data != nil\n}","tryCatchPattern":"item, err := fetchDetail(doubID)\nif err != nil && strings.Contains(err.Error(), \"解析详情响应失败\") {\n    log.Printf(\"lingjisp detail schema drift: %v\", err)\n    return emptyItem, nil // skip this title, keep scraping\n}","preventionTips":["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"],"tags":["json","parsing","api","schema"],"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"}