{"record":{"id":"0da9d03b7b29ba3c","repo":"fish2018/pansou","slug":"w-0da9d0","errorCode":null,"errorMessage":"解析响应失败: %w","messagePattern":"解析响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/pansearch/pansearch.go","lineNumber":627,"sourceCode":"\t// 检查状态码\n\tif resp.StatusCode == 404 {\n\t\treturn nil, 0, fmt.Errorf(\"404 Not Found，buildId可能已过期\")\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, 0, fmt.Errorf(\"服务器返回非200状态码: %d\", resp.StatusCode)\n\t}\n\n\t// 读取响应体\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\t// 解析响应\n\tvar apiResp PanSearchResponse\n\tif err := json.Unmarshal(respBody, &apiResp); err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\t// 获取total和结果\n\ttotal := apiResp.PageProps.Data.Total\n\titems := apiResp.PageProps.Data.Data\n\n\treturn items, total, nil\n}\n\n// fetchPage 获取指定偏移量的页面\nfunc (p *PanSearchAsyncPlugin) fetchPage(keyword string, offset int, baseURL string, client *http.Client) ([]PanSearchItem, error) {\n\t// 构建请求URL\n\treqURL := fmt.Sprintf(\"%s?keyword=%s&offset=%d\", baseURL, url.QueryEscape(keyword), offset)\n\n\t// 创建带超时的上下文\n\tctx, cancel := context.WithTimeout(context.Background(), p.timeout)\n\tdefer cancel()\n","sourceCodeStart":609,"sourceCodeEnd":645,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/pansearch/pansearch.go#L609-L645","documentation":"fetchFirstPage wraps json.Unmarshal failure when the pansearch.me response body cannot be decoded into PanSearchResponse. Because the target is a Next.js /_next/data endpoint, the body is usually JSON — so this error means the body was empty, HTML (an error/interstitial page), or the site's response shape changed so PanSearchResponse no longer matches.","triggerScenarios":"fetchFirstPage (called by doSearch) gets a 200 response but json.Unmarshal(respBody, &apiResp) fails: body is HTML from a challenge page, gzip/encoding mismatch, empty body, or new JSON schema after a site redeploy.","commonSituations":"Cloudflare/anti-bot interstitial served with 200 status; site redeploy renamed pageProps.data fields; response compressed but not decompressed; truncated body from an earlier partial read.","solutions":["Log a snippet of respBody on unmarshal failure to see what was actually returned","Verify the body starts with '{' before unmarshalling; treat HTML/empty bodies as upstream errors","Re-check the current JSON structure on the live site and update the PanSearchResponse struct tags (json:\"...\")","Ensure Content-Encoding is handled (set Accept-Encoding: gzip and decompress, or disable compression)"],"exampleFix":"// before\nvar apiResp PanSearchResponse\nif err := json.Unmarshal(respBody, &apiResp); err != nil {\n    return nil, 0, fmt.Errorf(\"解析响应失败: %w\", err)\n}\n// after\nif len(respBody) == 0 || respBody[0] != '{' {\n    return nil, 0, fmt.Errorf(\"解析响应失败: 非JSON响应(可能是反爬页面): %.200s\", respBody)\n}\nvar apiResp PanSearchResponse\nif err := json.Unmarshal(respBody, &apiResp); err != nil {\n    return nil, 0, fmt.Errorf(\"解析响应失败: %w, body: %.200s\", err, respBody)\n}","handlingStrategy":"validation","validationCode":"if len(respBody) == 0 {\n    return fmt.Errorf(\"empty response body\")\n}\nif !json.Valid(respBody) {\n    return fmt.Errorf(\"non-JSON response (anti-bot page?): %.200s\", respBody)\n}","typeGuard":"func isJSONBody(b []byte) bool {\n    t := bytes.TrimSpace(b)\n    return len(t) > 0 && (t[0] == '{' || t[0] == '[')\n}","tryCatchPattern":"items, total, err := p.fetchFirstPage(ctx, query)\nif err != nil && strings.Contains(err.Error(), \"解析响应失败\") {\n    log.Printf(\"pansearch returned unparseable body; check anti-bot/schema: %v\", err)\n    return ErrUpstreamChanged\n}","preventionTips":["Always validate the body is JSON before unmarshalling","Log body snippets on parse failure to detect anti-bot HTML pages","Re-verify struct json tags after any site redeploy","Handle Content-Encoding (gzip) explicitly"],"tags":["json","parsing","scraping","upstream"],"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"}