{"record":{"id":"7714d748e6e74ea4","repo":"fish2018/pansou","slug":"s-w-7714d7","errorCode":null,"errorMessage":"[%s] 解析搜索页面失败: %w","messagePattern":"\\[(.+?)\\] 解析搜索页面失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/kkmao/kkmao.go","lineNumber":134,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建请求失败: %w\", p.Name(), err)\n\t}\n\n\tsetCommonHeaders(req, \"https://www.kuakemao.com/\")\n\n\tresp, err := p.doRequestWithRetry(req, client, searchMaxRetries, retryBaseDelay)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\tdoc, err := goquery.NewDocumentFromReader(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索页面失败: %w\", p.Name(), err)\n\t}\n\n\tvar (\n\t\tresults []model.SearchResult\n\t\twg      sync.WaitGroup\n\t\tmu      sync.Mutex\n\t\tsem     = make(chan struct{}, maxConcurrency)\n\t)\n\n\tdoc.Find(\"article.excerpt\").Each(func(_ int, item *goquery.Selection) {\n\t\ttitleSel := item.Find(\"header h2 a\")\n\t\ttitle := strings.TrimSpace(titleSel.Text())\n\t\tdetailURL, ok := titleSel.Attr(\"href\")\n\t\tif !ok || title == \"\" || detailURL == \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tarticleID := extractArticleID(detailURL)","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/kkmao/kkmao.go#L116-L152","documentation":"Wrapped parse error in KkMaoPlugin.searchImpl (plugin/kkmao/kkmao.go:134): the 200 response body could not be turned into a goquery document — usually an HTML challenge or non-HTML error page instead of search results.","triggerScenarios":"goquery.NewDocumentFromReader(resp.Body) errors: empty body from a blocked request, truncated transfer, charset/compression mismatch, or a JSON/challenge page instead of HTML.","commonSituations":"站点改版返回 JSON 或错误页。","solutions":["Read and size-check the body before parsing; log the first bytes on failure","Ensure the transport handles gzip/deflate (http.DefaultTransport does; custom ones may not)","Confirm no earlier code consumed resp.Body","Handle encoding indicated by the Content-Type charset"],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(resp.Body)\nif err != nil { return nil, fmt.Errorf(\"[%s] 解析搜索页面失败: %w\", p.Name(), err) }\n// after\nbody, rerr := io.ReadAll(resp.Body)\nif rerr != nil || len(bytes.TrimSpace(body)) == 0 {\n    return nil, fmt.Errorf(\"[%s] empty response body (status %d)\", p.Name(), resp.StatusCode)\n}\ndoc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))","handlingStrategy":"fallback","validationCode":"body, _ := io.ReadAll(io.LimitReader(resp.Body, 256))\nif len(bytes.TrimSpace(body)) == 0 || !utf8.Valid(body) {\n    return errors.New(\"response body empty or invalid; skip parsing\")\n}","typeGuard":null,"tryCatchPattern":"results, err := plugin.Search(keyword)\nif err != nil {\n    if strings.Contains(err.Error(), \"解析搜索页面失败\") {\n        return useFallbackSource(keyword)\n    }\n    return err\n}","preventionTips":["Verify body non-empty before goquery parsing","Use a transport with automatic gzip decoding","Never read resp.Body twice","Log response Content-Type on parse failures"],"tags":["go","html","parsing","goquery"],"backgroundTag":"invalid-json-response","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"}