fish2018/pansou · error

[ ] JSON解析失败

Error message

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

What it means

json.Unmarshal failed to decode the search response body into NSGameResponse. The plugin expects a JSON object with success/code/data fields; this error means the body was not valid JSON or its structure didn't match the struct. Usually the server returned an HTML anti-bot/login page or an error page instead of the API payload.

Solutions

  1. Log the first bytes of body on unmarshal failure to see what was actually returned
  2. Re-run/refresh the anti-bot session (ensureSession) before retrying the search
  3. Update NSGameResponse struct fields to match the current API schema
  4. Check whether nsthwj.cn moved to a new API domain and update apiURL
  5. Validate body starts with '{' before unmarshalling to give a clearer error

Example fix

// before
var apiResp NSGameResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
    return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
}
// after
var apiResp NSGameResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
    return nil, fmt.Errorf("[%s] JSON解析失败(响应开头: %.120q): %w", p.Name(), body, err)
}
Defensive patterns

Strategy: fallback

Validate before calling

// hint: validate content type before trusting the body
if !strings.Contains(resp.Header.Get("Content-Type"), "application/json") {
    log.Warn("expected JSON but got", "ct", resp.Header.Get("Content-Type"))
}

Try / catch

results, err := plugin.Search(keyword, ext)
if err != nil && strings.Contains(err.Error(), "JSON解析失败") {
    // likely anti-bot HTML: refresh session once, else fall back to other plugins
    return otherPlugins.Search(keyword, ext)
}

Prevention

When it happens

Trigger: Response body is HTML (WAF challenge, Cloudflare interstitial, login redirect), body is empty, or the API changed its JSON schema so unmarshal fails (e.g. data no longer an object).

Common situations: ensureSession cookies expired or the challenge failed so the site serves a JS challenge page; site redesign changed the response envelope; a captive portal or ISP interception page is returned.

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

Appendix: source

Thrown at plugin/nsgame/nsgame.go:179

	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] 请求返回状态码: %d", p.Name(), resp.StatusCode)
	}

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

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

	// 8. 检查响应状态
	if !apiResp.Success || (apiResp.Code != "" && apiResp.Code != "200") {
		return nil, fmt.Errorf("[%s] API返回错误: success=%v, code=%s", p.Name(), apiResp.Success, apiResp.Code)
	}

	// 9. 转换为标准格式
	items := apiResp.Data.PageData.Data
	var results []model.SearchResult
	var wg sync.WaitGroup
	var mu sync.Mutex
	sem := make(chan struct{}, 8)
	for _, item := range items {
		item := item
		if item.ID == 0 {
			continue
		}

View on GitHub (pinned to beaa561337)