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
- Log the first bytes of body on unmarshal failure to see what was actually returned
- Re-run/refresh the anti-bot session (ensureSession) before retrying the search
- Update NSGameResponse struct fields to match the current API schema
- Check whether nsthwj.cn moved to a new API domain and update apiURL
- 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
- Keep the anti-bot session (ensureSession) fresh before searching
- Check Content-Type header before parsing JSON
- Log response prefixes on parse failure to catch schema changes fast
- Pin/verify the API response shape with a small contract test
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)