fish2018/pansou · error

[ ] JSON解析失败

Error message

[%s] JSON解析失败: %w

What it means

The XYS plugin's executeSearch received the HTTP response body but json.Unmarshal failed to parse it into SearchResponse. This means the response body is not valid JSON (or JSON that doesn't match SearchResponse's shape, e.g. an array instead of an object). The plugin wraps the parse error with the plugin name and %w so the underlying Go error is preserved.

Solutions

  1. Log the raw respBody (or its first few hundred bytes) to see what the server actually returned.
  2. Check the HTTP status code before unmarshalling; handle non-200 responses separately instead of attempting to parse HTML as JSON.
  3. Retry after a delay — anti-bot/CDN pages are often transient.
  4. Update the plugin if the XYS API response schema changed.

Example fix

// before
var searchResp SearchResponse
if err := json.Unmarshal(respBody, &searchResp); err != nil {
    return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
}
// after
if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("[%s] 搜索API返回非200状态: %d, body: %.200s", p.Name(), resp.StatusCode, string(respBody))
}
var searchResp SearchResponse
if err := json.Unmarshal(respBody, &searchResp); err != nil {
    return nil, fmt.Errorf("[%s] JSON解析失败: %w, body: %.200s", p.Name(), err, string(respBody))
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: validate before trusting the parse
if resp.StatusCode != http.StatusOK {
    return fmt.Errorf("unexpected status %d", resp.StatusCode)
}
if !json.Valid(respBody) {
    return fmt.Errorf("response body is not valid JSON")
}

Try / catch

results, err := search(keyword)
if err != nil {
    if strings.Contains(err.Error(), "JSON解析失败") {
        log.Printf("XYS returned non-JSON body; retrying later: %v", err)
        return fallbackResults
    }
    return err
}

Prevention

When it happens

Trigger: A GET to the XYS search endpoint returned a body that json.Unmarshal rejects: HTML error pages, Cloudflare/WAF interstitials, empty bodies, gzip/charset garbage, or a JSON top-level array when SearchResponse expects an object.

Common situations: Upstream site is temporarily down or behind an anti-bot page; reverse proxy (nginx) returns an HTML 502 page; the API changed its response envelope after a site update; a misconfigured proxy injects its own error page.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at plugin/xys/xys.go:280

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

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[%s] 搜索请求HTTP状态错误: %d", p.Name(), resp.StatusCode)
	}

	// 读取响应体
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 读取响应体失败: %w", p.Name(), err)
	}

	// 解析JSON响应
	var searchResp SearchResponse
	if err := json.Unmarshal(respBody, &searchResp); err != nil {
		return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
	}

	if searchResp.Code != 0 {
		return nil, fmt.Errorf("[%s] 搜索API返回错误: %s", p.Name(), searchResp.Msg)
	}

	if p.debugMode {
		log.Printf("[XYS] 搜索API响应成功,data长度: %d", len(searchResp.Data))
	}

	// 解析HTML内容
	return p.parseSearchResults(searchResp.Data, keyword)
}

// parseSearchResults 解析搜索结果HTML
func (p *XysPlugin) parseSearchResults(htmlData, keyword string) ([]model.SearchResult, error) {
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlData))
	if err != nil {

View on GitHub (pinned to beaa561337)