fish2018/pansou · error

解析HTML失败

Error message

解析HTML失败: %w

What it means

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.

Solutions

  1. Inspect the wrapped error: unexpected EOF usually means a truncated response — add retry around the whole search request.
  2. Log a snippet of the raw body before parsing to confirm it is actually HTML.
  3. Verify the gzip decompression path isn't failing mid-stream (this error can surface from the gzReader).
  4. Check that the response Content-Type is text/html before parsing.
  5. Consider setting a larger client timeout so slow responses aren't cut off mid-body.

Example fix

// before
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
    return nil, fmt.Errorf("解析HTML失败: %w", err)
}
// after
bodyBytes, err := io.ReadAll(reader)
if err != nil {
    return nil, fmt.Errorf("读取响应体失败: %w", err)
}
if !utf8.Valid(bodyBytes) {
    bodyBytes, err = simplifiedchinese.GBK.NewDecoder().Bytes(bodyBytes)
    if err != nil {
        return nil, fmt.Errorf("响应编码转换失败: %w", err)
    }
}
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))
if err != nil {
    return nil, fmt.Errorf("解析HTML失败: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: sanity-check the body looks like HTML before parsing
body, err := io.ReadAll(reader)
if err != nil {
    return fmt.Errorf("body read failed: %w", err)
}
head := strings.TrimSpace(strings.ToLower(string(body[:min(200, len(body))])))
if !strings.Contains(head, "<html") && !strings.Contains(head, "<!doctype") {
    return fmt.Errorf("response is not HTML: %q", head)
}

Type guard

// Go: nil-safety on the parsed document before extraction
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil || doc == nil || doc.Selection.Length() == 0 {
    return nil, fmt.Errorf("no parseable document: %w", err)
}

Try / catch

doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
    if errors.Is(err, io.ErrUnexpectedEOF) {
        // truncated response: retry the whole search once
        return p.searchImpl(client, keyword, ext)
    }
    return nil, fmt.Errorf("解析HTML失败: %w", err)
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/405a03f078c59b19. Report an issue: GitHub.

Appendix: source

Thrown at plugin/xiaozhang/xiaozhang.go:177

	if p.debugMode {
		log.Printf("[Xiaozhang] Content-Encoding: %s", contentEncoding)
		log.Printf("[Xiaozhang] Content-Type: %s", resp.Header.Get("Content-Type"))
	}
	
	// 如果是gzip压缩,手动解压
	if contentEncoding == "gzip" {
		gzReader, err := gzip.NewReader(resp.Body)
		if err != nil {
			return nil, fmt.Errorf("创建gzip reader失败: %w", err)
		}
		defer gzReader.Close()
		reader = gzReader
	}
	
	// 解析HTML
	doc, err := goquery.NewDocumentFromReader(reader)
	if err != nil {
		return nil, fmt.Errorf("解析HTML失败: %w", err)
	}
	
	// 提取搜索结果
	results := p.extractSearchResults(doc, keyword)
	
	if p.debugMode {
		log.Printf("[Xiaozhang] 找到 %d 个搜索结果", len(results))
	}
	
	// 并发获取详情页链接
	results = p.enrichWithDetailLinks(client, results, keyword)
	
	// 过滤结果
	filteredResults := plugin.FilterResultsByKeyword(results, keyword)
	
	if p.debugMode {
		log.Printf("[Xiaozhang] 过滤后剩余 %d 个结果", len(filteredResults))
	}

View on GitHub (pinned to beaa561337)