fish2018/pansou · error

[ ] 解析首页失败

Error message

[%s] 解析首页失败: %w

What it means

Returned when goquery.NewDocumentFromReader fails to parse the homepage response body as HTML. This wraps the goquery/html parsing error, which almost always means the body is empty, truncated, or not valid HTML (e.g. a gzip/brotli-encoded body that was not decompressed, or an error page in JSON/plain text).

Solutions

  1. Read and log a prefix of resp.Body before parsing to see what was actually received
  2. Do not manually set Accept-Encoding unless you handle decompression; let net/http negotiate it
  3. Check Content-Type of the response — ensure it is text/html
  4. Retry the request; a truncated body is often transient
  5. Verify the site still serves plain HTML rather than a JS-only shell with no HTML response

Example fix

// before
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
    return "", fmt.Errorf("[%s] 解析首页失败: %w", p.Name(), err)
}
// after
body, readErr := io.ReadAll(resp.Body)
if readErr != nil {
    return "", fmt.Errorf("[%s] 读取首页失败: %w", p.Name(), readErr)
}
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))
if err != nil {
    return "", fmt.Errorf("[%s] 解析首页失败: %w (body head: %.200s)", p.Name(), err, body)
}
Defensive patterns

Strategy: try-catch

Try / catch

key, err := plugin.fetchDataKey(client)
if err != nil {
    if strings.Contains(err.Error(), "解析首页失败") {
        // HTML parse failure: capture body sample for diagnostics, then retry once
        return retryWithBodyDump(err)
    }
    return err
}

Prevention

When it happens

Trigger: Response body is empty or closed mid-read, is binary/compressed content served without proper Content-Encoding handling, or the connection broke mid-body so the HTML parser hits malformed input it cannot recover from.

Common situations: Manually setting Accept-Encoding headers causing compressed bodies without decompression; a proxy returning a partial body; server streaming an error page in a non-HTML format; extremely large or truncated responses over flaky networks.

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/9ac8aed9eb2352a1. Report an issue: GitHub.

Appendix: source

Thrown at plugin/jsnoteclub/jsnoteclub.go:246

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://jsnoteclub.com/", nil)
	if err != nil {
		return "", fmt.Errorf("[%s] 创建首页请求失败: %w", p.Name(), err)
	}
	setHTMLHeaders(req, "https://jsnoteclub.com/")

	resp, err := p.doRequestWithRetry(req, client, maxRequestRetries)
	if err != nil {
		return "", fmt.Errorf("[%s] 访问首页失败: %w", p.Name(), err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("[%s] 首页返回状态码: %d", p.Name(), resp.StatusCode)
	}

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		return "", fmt.Errorf("[%s] 解析首页失败: %w", p.Name(), err)
	}

	var htmlBuilder strings.Builder
	doc.Find("script").Each(func(_ int, s *goquery.Selection) {
		if html, err := goquery.OuterHtml(s); err == nil {
			htmlBuilder.WriteString(html)
		}
	})

	match := dataKeyRegex.FindStringSubmatch(htmlBuilder.String())
	if len(match) < 2 {
		return "", fmt.Errorf("[%s] 未能在首页找到 data-key", p.Name())
	}

	return match[1], nil
}

func (p *JsNoteClubPlugin) fetchPosts(client *http.Client, dataKey string) ([]ghostPost, error) {

View on GitHub (pinned to beaa561337)