fish2018/pansou · error

[ ] 解析搜索结果HTML失败

Error message

[%s] 解析搜索结果HTML失败: %w

What it means

After a successful page fetch, executeSearch parses the response body with goquery.NewDocumentFromReader. If constructing the document fails (I/O error while reading the body, malformed stream), the error is wrapped with this message and the plugin name. This indicates the search response could not be turned into a parseable HTML document.

Solutions

  1. Unwrap the error to see the underlying body-read failure.
  2. Check Content-Encoding handling — ensure the scraper/client negotiates and decompresses encodings correctly.
  3. Retry the request; truncated bodies are usually transient.
  4. Capture the raw response (status, length) when it happens to diagnose whether the server sent a valid body.

Example fix

// before
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil { return err }
// after
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
    return fmt.Errorf("bad search response (status=%d): %w", resp.StatusCode, err)
}
Defensive patterns

Strategy: retry

Validate before calling

if resp.ContentLength == 0 {
    return errors.New("empty search response body, skipping parse")
}

Try / catch

doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
    return fmt.Errorf("unreadable response (status=%d): %w", resp.StatusCode, err) // caller retries
}

Prevention

When it happens

Trigger: goquery.NewDocumentFromReader(resp.Body) returns an error — typically a read failure on the response body stream (connection closed mid-body, decompression error) rather than invalid HTML, since goquery tolerates most markup.

Common situations: Server closes connection while streaming the body; gzip/brotli decompression mismatch because Accept-Encoding and actual encoding diverge; an interrupted proxy connection delivering a truncated response.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at plugin/diduan/diduan.go:181

// executeSearch 执行搜索请求
func (p *DiduanPlugin) executeSearch(keyword string) ([]model.SearchResult, error) {
	// 构建搜索URL
	searchURL := fmt.Sprintf("%s%s", BaseURL, fmt.Sprintf(SearchPath, url.QueryEscape(keyword)))

	resp, err := p.getPage(searchURL)
	if err != nil {
		return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, p.httpStatusError("搜索", resp)
	}

	// 解析HTML提取搜索结果
	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 解析搜索结果HTML失败: %w", p.Name(), err)
	}

	return p.parseSearchResults(doc)
}

// getPage 串行化 cloudscraper 调用,避免其 stealth 计数器并发竞争。
func (p *DiduanPlugin) getPage(rawURL string) (*http.Response, error) {
	p.scraperMu.Lock()
	defer p.scraperMu.Unlock()
	return p.scraper.Get(rawURL)
}

func (p *DiduanPlugin) httpStatusError(action string, resp *http.Response) error {
	if strings.EqualFold(resp.Header.Get("cf-mitigated"), "challenge") {
		return fmt.Errorf("[%s] %s触发 Cloudflare Managed Challenge (HTTP %d)", p.Name(), action, resp.StatusCode)
	}
	return fmt.Errorf("[%s] %sHTTP状态错误: %d", p.Name(), action, resp.StatusCode)
}

View on GitHub (pinned to beaa561337)