{"record":{"id":"30c13898b8da1211","repo":"fish2018/pansou","slug":"s-w-30c138","errorCode":null,"errorMessage":"[%s] 解析搜索结果失败: %w","messagePattern":"\\[(.+?)\\] 解析搜索结果失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/dygang/dygang.go","lineNumber":197,"sourceCode":"\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求返回 HTTP %d\", p.Name(), resp.StatusCode)\n\t}\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取搜索结果失败: %w\", p.Name(), err)\n\t}\n\tdecoded, err := decodeGB18030(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解码搜索结果失败: %w\", p.Name(), err)\n\t}\n\tdoc, err := goquery.NewDocumentFromReader(strings.NewReader(decoded))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索结果失败: %w\", p.Name(), err)\n\t}\n\treturn doc, nil\n}\n\nfunc (p *Plugin) fetchDetail(client *http.Client, detailURL string) ([]magnetItem, string, string) {\n\tctx, cancel := context.WithTimeout(context.Background(), requestTimeout)\n\tdefer cancel()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, detailURL, nil)\n\tif err != nil {\n\t\treturn nil, \"\", \"\"\n\t}\n\tsetHeaders(req, baseURL+\"/\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, \"\", \"\"\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/dygang/dygang.go#L179-L215","documentation":"The dygang plugin failed to build a goquery document from the decoded GB18030 HTML of the search page. This is thrown when goquery.NewDocumentFromReader cannot parse the response body, typically malformed or truncated HTML. The plugin wraps the parse error with its plugin name for context.","triggerScenarios":"fetchSearch received a 200 response whose body decoded via decodeGB18030 but goquery could not parse it — e.g. empty body, binary/garbage content misidentified as GB18030, or a CDN error page with invalid markup.","commonSituations":"Site deploys a WAF/anti-bot interstitial serving non-HTML bytes; the site changes character encoding so decodeGB18030 corrupts the stream; response body truncated by proxy; site temporarily down returning empty body.","solutions":["Log the first bytes of `decoded` to check whether it is actually HTML","Verify the site's real charset and that decodeGB18030 matches it (GBK vs UTF-8)","Check whether the site is serving an anti-bot/challenge page and add proper headers or cookies","Add a guard for empty decoded body before calling goquery","Retry the request — transient truncation often resolves"],"exampleFix":"// before\ndoc, err := goquery.NewDocumentFromReader(strings.NewReader(decoded))\nif err != nil {\n\treturn nil, fmt.Errorf(\"[%s] 解析搜索结果失败: %w\", p.Name(), err)\n}\n// after\nif strings.TrimSpace(decoded) == \"\" {\n\treturn nil, fmt.Errorf(\"[%s] 搜索结果为空\", p.Name())\n}\ndoc, err := goquery.NewDocumentFromReader(strings.NewReader(decoded))\nif err != nil {\n\treturn nil, fmt.Errorf(\"[%s] 解析搜索结果失败: %w (前64字节: %q)\", p.Name(), err, decoded[:min(64, len(decoded))])\n}","handlingStrategy":"try-catch","validationCode":"if body == nil || len(body) == 0 { return errors.New(\"empty response body\") }","typeGuard":"func isHTMLBody(contentType string, head []byte) bool {\n\tct := strings.ToLower(contentType)\n\tif !strings.Contains(ct, \"text/html\") && !strings.Contains(ct, \"application/xhtml\") { return false }\n\ts := strings.TrimSpace(string(head))\n\treturn strings.HasPrefix(s, \"<\") || strings.HasPrefix(s, \"\\xef\\xbb\\xbf<\")\n}","tryCatchPattern":"doc, err := goquery.NewDocumentFromReader(strings.NewReader(decoded))\nif err != nil {\n\tlog.Printf(\"html parse failed, head=%q\", decoded[:min(64, len(decoded))])\n\treturn fallbackEmptyResults() // degrade gracefully instead of aborting\n}","preventionTips":["Check Content-Type header before parsing","Guard against empty bodies before goquery","Verify charset handling matches the site's real encoding","Log a body prefix on parse failure for diagnosis"],"tags":["html-parsing","go","scraper","encoding"],"backgroundTag":"html-parse-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"}