{"record":{"id":"dede525398695bd6","repo":"fish2018/pansou","slug":"s-html-w-dede52","errorCode":null,"errorMessage":"[%s] HTML解析失败: %w","messagePattern":"\\[(.+?)\\] HTML解析失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/jutoushe/jutoushe.go","lineNumber":82,"sourceCode":"\treq.Header.Set(\"Connection\", \"keep-alive\")\n\treq.Header.Set(\"Referer\", baseURL+\"/\")\n\n\t// 5. 发送HTTP请求（带重试机制）\n\tresp, err := p.doRequestWithRetry(req, client)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 6. 检查状态码\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"[%s] 请求返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\t// 7. 解析搜索结果页面\n\tdoc, err := goquery.NewDocumentFromReader(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] HTML解析失败: %w\", p.Name(), err)\n\t}\n\n\t// 8. 提取搜索结果\n\tvar results []model.SearchResult\n\tdoc.Find(\"ul.erx-list li.item\").Each(func(i int, s *goquery.Selection) {\n\t\t// 提取标题和链接\n\t\tlinkElem := s.Find(\".a a.main\")\n\t\ttitle := strings.TrimSpace(linkElem.Text())\n\t\tdetailPath, exists := linkElem.Attr(\"href\")\n\t\t\n\t\tif !exists || title == \"\" {\n\t\t\treturn // 跳过无效项\n\t\t}\n\n\t\t// 构建完整的详情页URL\n\t\tdetailURL := baseURL + detailPath\n\n\t\t// 提取发布时间","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/jutoushe/jutoushe.go#L64-L100","documentation":"jutoushe searchImpl wraps errors from goquery.NewDocumentFromReader, which parses the response body as HTML. This fails when the body is not valid HTML — empty body, truncated response, JSON error page, or compressed content not decompressed.","triggerScenarios":"goquery.NewDocumentFromReader(resp.Body) errors: empty response body, charset/gzip issues, or a non-HTML response (e.g. a JSON error from a CDN).","commonSituations":"Anti-bot layer returns an empty or challenge page; response gzip-compressed but not handled; connection closed early producing truncated HTML.","solutions":["Read the body first and check it is non-empty before parsing","Verify Content-Encoding handling (set Accept-Encoding or use an auto-decompressing transport)","Log the first bytes of the body to see what the server actually returned","Check that resp.Body was not already consumed by a prior read"],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(resp.Body)\nif err != nil { return nil, fmt.Errorf(\"[%s] HTML解析失败: %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 or unreadable response body\", p.Name())\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 {\n    return errors.New(\"empty response body; skip parsing\")\n}","typeGuard":null,"tryCatchPattern":"results, err := plugin.Search(keyword)\nif err != nil {\n    if strings.Contains(err.Error(), \"HTML解析失败\") {\n        return useAlternativeSource(keyword) // fall back to another plugin\n    }\n    return err\n}","preventionTips":["Check body non-empty before HTML parsing","Ensure gzip/charset handling on the transport","Log first body bytes when parsing fails","Fall back to another search source when parsing fails"],"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"}