{"record":{"id":"405a03f078c59b19","repo":"fish2018/pansou","slug":"html-w-405a03","errorCode":null,"errorMessage":"解析HTML失败: %w","messagePattern":"解析HTML失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/xiaozhang/xiaozhang.go","lineNumber":177,"sourceCode":"\tif p.debugMode {\n\t\tlog.Printf(\"[Xiaozhang] Content-Encoding: %s\", contentEncoding)\n\t\tlog.Printf(\"[Xiaozhang] Content-Type: %s\", resp.Header.Get(\"Content-Type\"))\n\t}\n\t\n\t// 如果是gzip压缩，手动解压\n\tif contentEncoding == \"gzip\" {\n\t\tgzReader, err := gzip.NewReader(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"创建gzip reader失败: %w\", err)\n\t\t}\n\t\tdefer gzReader.Close()\n\t\treader = gzReader\n\t}\n\t\n\t// 解析HTML\n\tdoc, err := goquery.NewDocumentFromReader(reader)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"解析HTML失败: %w\", err)\n\t}\n\t\n\t// 提取搜索结果\n\tresults := p.extractSearchResults(doc, keyword)\n\t\n\tif p.debugMode {\n\t\tlog.Printf(\"[Xiaozhang] 找到 %d 个搜索结果\", len(results))\n\t}\n\t\n\t// 并发获取详情页链接\n\tresults = p.enrichWithDetailLinks(client, results, keyword)\n\t\n\t// 过滤结果\n\tfilteredResults := plugin.FilterResultsByKeyword(results, keyword)\n\t\n\tif p.debugMode {\n\t\tlog.Printf(\"[Xiaozhang] 过滤后剩余 %d 个结果\", len(filteredResults))\n\t}","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/xiaozhang/xiaozhang.go#L159-L195","documentation":"This error wraps a failure from goquery.NewDocumentFromReader, which fully reads the response body and parses it as an HTML document via goquery's HTML parser (golang.org/x/net/html). The plugin throws it when the body cannot be read (I/O error mid-stream, e.g. decompression failure or truncated connection) or the HTML is too malformed for the parser. Without a parsed document, search results cannot be extracted.","triggerScenarios":"Calling Search on the xiaozhang plugin when the (possibly gzip-decompressed) response body fails to read — unexpected EOF from a truncated response, gzip stream corrupted mid-body, connection reset while streaming — or the content is not parseable HTML (binary garbage, invalid encoding).","commonSituations":"The site's CDN cuts the connection mid-response on slow links; the body is actually JSON or a challenge page mislabelled as HTML; character encoding issues corrupt the stream; or a proxy mangles the response body.","solutions":["Inspect the wrapped error: unexpected EOF usually means a truncated response — add retry around the whole search request.","Log a snippet of the raw body before parsing to confirm it is actually HTML.","Verify the gzip decompression path isn't failing mid-stream (this error can surface from the gzReader).","Check that the response Content-Type is text/html before parsing.","Consider setting a larger client timeout so slow responses aren't cut off mid-body."],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(reader)\nif err != nil {\n    return nil, fmt.Errorf(\"解析HTML失败: %w\", err)\n}\n// after\nbodyBytes, err := io.ReadAll(reader)\nif err != nil {\n    return nil, fmt.Errorf(\"读取响应体失败: %w\", err)\n}\nif !utf8.Valid(bodyBytes) {\n    bodyBytes, err = simplifiedchinese.GBK.NewDecoder().Bytes(bodyBytes)\n    if err != nil {\n        return nil, fmt.Errorf(\"响应编码转换失败: %w\", err)\n    }\n}\ndoc, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))\nif err != nil {\n    return nil, fmt.Errorf(\"解析HTML失败: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"// Go: sanity-check the body looks like HTML before parsing\nbody, err := io.ReadAll(reader)\nif err != nil {\n    return fmt.Errorf(\"body read failed: %w\", err)\n}\nhead := strings.TrimSpace(strings.ToLower(string(body[:min(200, len(body))])))\nif !strings.Contains(head, \"<html\") && !strings.Contains(head, \"<!doctype\") {\n    return fmt.Errorf(\"response is not HTML: %q\", head)\n}","typeGuard":"// Go: nil-safety on the parsed document before extraction\ndoc, err := goquery.NewDocumentFromReader(reader)\nif err != nil || doc == nil || doc.Selection.Length() == 0 {\n    return nil, fmt.Errorf(\"no parseable document: %w\", err)\n}","tryCatchPattern":"doc, err := goquery.NewDocumentFromReader(reader)\nif err != nil {\n    if errors.Is(err, io.ErrUnexpectedEOF) {\n        // truncated response: retry the whole search once\n        return p.searchImpl(client, keyword, ext)\n    }\n    return nil, fmt.Errorf(\"解析HTML失败: %w\", err)\n}","preventionTips":["Check Content-Type is text/html before parsing.","Read the body into memory first so read errors are distinguishable from parse errors.","Handle charset/encoding conversion (GBK etc.) for Chinese sites before parsing.","Retry truncated responses (unexpected EOF) which are common with scraping.","Keep goquery and golang.org/x/net/html updated for parser robustness fixes."],"tags":["html-parsing","goquery","http-response","search-plugin"],"backgroundTag":"unexpected-response-shape","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"}