{"record":{"id":"e4aadfad81487118","repo":"fish2018/pansou","slug":"s-html-w-e4aadf","errorCode":null,"errorMessage":"[%s] HTML解析失败: %w","messagePattern":"\\[(.+?)\\] HTML解析失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/pianku/pianku.go","lineNumber":153,"sourceCode":"\t// 设置请求头\n\tp.setRequestHeaders(req)\n\t\n\t// 发送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\t\n\t// 检查状态码\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"[%s] 请求返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\t\n\t// 解析HTML\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\t\n\t// 提取搜索结果基本信息\n\tsearchResults := p.extractSearchResults(doc)\n\t\n\t// 为每个搜索结果获取详情页的下载链接\n\tvar finalResults []model.SearchResult\n\tfor _, result := range searchResults {\n\t\t// 获取详情页链接\n\t\tif len(result.Links) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tdetailURL := result.Links[0].URL\n\t\t\n\t\t// 请求详情页并解析下载链接\n\t\tdownloadLinks, err := p.fetchDetailPageLinks(client, detailURL)\n\t\tif err != nil {\n\t\t\t// 如果获取详情页失败，仍然保留原始结果","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/pianku/pianku.go#L135-L171","documentation":"goquery.NewDocumentFromReader failed to parse the search response body as HTML. goquery returns an error only when reading the body fails or the document cannot be constructed (e.g. empty/stream-broken input), not for ordinary malformed HTML.","triggerScenarios":"The response body could not be read as an HTML document — typically a truncated/empty body, an already-closed reader, or a body interrupted mid-transfer after a 200 status.","commonSituations":"Anti-bot layer returned an empty 200 body; connection cut mid-response; proxy mangling the body; the site now returns JSON/redirect content where HTML was expected.","solutions":["Check body length before parsing; treat empty bodies as an upstream/anti-bot issue","Retry the request on parse failure","Log a body snippet to see what the 200 response actually contained","Verify no middleware closes resp.Body before goquery reads it"],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(resp.Body)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] HTML解析失败: %w\", p.Name(), err)\n}\n// after\nbodyBytes, rerr := io.ReadAll(resp.Body)\nif rerr != nil || len(bodyBytes) == 0 {\n    return nil, fmt.Errorf(\"[%s] 空或不可读的响应体: %v\", p.Name(), rerr)\n}\ndoc, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] HTML解析失败: %w\", p.Name(), err)\n}","handlingStrategy":"fallback","validationCode":"b, _ := io.ReadAll(resp.Body)\nif len(b) == 0 || (!bytes.Contains(b, []byte(\"<html\")) && !bytes.Contains(bytes.ToLower(b), []byte(\"<!doctype\"))) {\n    return fmt.Errorf(\"response is not HTML\")\n}","typeGuard":"func isHTMLBody(body []byte) bool {\n    trimmed := bytes.TrimSpace(body)\n    return len(trimmed) > 0 && bytes.HasPrefix(trimmed, []byte(\"<\"))\n}","tryCatchPattern":"results, err := plugin.Search(keyword, ext)\nif err != nil {\n    if strings.Contains(err.Error(), \"HTML解析失败\") {\n        // empty/truncated body: retry or fall back to other plugins\n    }\n}","preventionTips":["Buffer the body and check emptiness before goquery parsing","Retry once on parse failure — bodies are often truncated transiently","Keep resp.Body open until goquery has consumed it"],"tags":["go","html","parsing","goquery","scraping"],"backgroundTag":"empty-response-body","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"}