{"record":{"id":"a719d330c8f75525","repo":"fish2018/pansou","slug":"json-w-a719d3","errorCode":null,"errorMessage":"JSON解析失败: %w","messagePattern":"JSON解析失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/cyg/cyg.go","lineNumber":166,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"HTTP请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 检查状态码\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"HTTP错误状态码: %d\", resp.StatusCode)\n\t}\n\n\t// 解析响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tvar posts []CygPost\n\tif err := json.Unmarshal(body, &posts); err != nil {\n\t\treturn nil, fmt.Errorf(\"JSON解析失败: %w\", err)\n\t}\n\n\treturn posts, nil\n}\n\n// fetchDownloadLinksAsync 并发获取下载链接\nfunc (p *CygPlugin) fetchDownloadLinksAsync(client *http.Client, posts []CygPost, keyword string) []model.SearchResult {\n\tvar wg sync.WaitGroup\n\tresultChan := make(chan model.SearchResult, len(posts))\n\n\t// 限制并发数量\n\tsemaphore := make(chan struct{}, 10) // 最多10个并发\n\n\tfor _, post := range posts {\n\t\twg.Add(1)\n\t\tgo func(p *CygPlugin, post CygPost) {\n\t\t\tdefer wg.Done()\n","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/cyg/cyg.go#L148-L184","documentation":"fetchSearchResults unmarshals the response body into []CygPost; if the body is not valid JSON or does not match the expected array shape, this error wraps the json.Unmarshal failure. Typically the server returned HTML (error page/login page) instead of the REST JSON array.","triggerScenarios":"searchImpl gets a 200 whose body is not a JSON array of CygPost — e.g. an HTML block/interstitial page, a WordPress JSON error object ({\"code\":...}) instead of an array, or truncated/malformed JSON.","commonSituations":"WAF or anti-bot serves a 200 HTML challenge page; REST API returns an error object with 200 in some plugin setups; content-type/encoding mismatch; upstream changed response schema.","solutions":["Log the first ~500 bytes of the body on failure to see whether it's HTML, a JSON object, or truncated JSON.","Handle WordPress REST error objects: try unmarshalling into a struct with code/message fields and surface that message.","Use a browser-like User-Agent and cookies so the server returns real JSON instead of a challenge page.","Validate the Content-Type header is application/json before unmarshalling."],"exampleFix":"// before\nvar posts []CygPost\nif err := json.Unmarshal(body, &posts); err != nil {\n    return nil, fmt.Errorf(\"JSON解析失败: %w\", err)\n}\n// after\nvar posts []CygPost\nif err := json.Unmarshal(body, &posts); err != nil {\n    return nil, fmt.Errorf(\"JSON解析失败: %w (body head: %.200s)\", err, string(body))\n}","handlingStrategy":"type-guard","validationCode":"resp, _ := http.Get(searchURL)\nct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    // upstream returned HTML/other: don't attempt JSON parse\n}","typeGuard":null,"tryCatchPattern":"results, err := plugin.SearchWithResult(ctx, opts)\nif err != nil && strings.Contains(err.Error(), \"JSON解析失败\") {\n    // response was not the expected JSON array: log body, try alternate source\n}","preventionTips":["Check Content-Type before expecting JSON","Send realistic headers so WAFs don't serve HTML challenge pages with 200","Handle WordPress REST error objects (code/message) separately from result arrays","Keep CygPost struct fields tolerant (json:\"...\" with omitempty/pointers) to survive schema drift"],"tags":["json","parsing","wordpress","go"],"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"}