fish2018/pansou · error
[ ] 解析JSON响应失败
Error message
[%s] 解析JSON响应失败: %w
What it means
Thrown when json.Unmarshal fails to decode the response body into OugeAPIResponse. The request and body read succeeded, but the payload is not valid JSON or its shape does not fit the struct (e.g. an HTML error page from a captive portal or CDN challenge).
Solutions
- Log a snippet of the raw body (body[:200]) to see what was actually returned
- Check whether an anti-bot/CDN challenge page is being served and consider updating the Referer/UA or base URL
- Verify the API schema still matches OugeAPIResponse (code/list/msg field types)
- Validate the body starts with '{' before unmarshalling to give a clearer error
Example fix
// before
if err := json.Unmarshal(body, &apiResponse); err != nil {
return nil, fmt.Errorf("[%s] 解析JSON响应失败: %w", p.Name(), err)
}
// after
if !json.Valid(body) || len(body) == 0 || body[0] != '{' {
return nil, fmt.Errorf("[%s] 非JSON响应: %.200s", p.Name(), body)
}
if err := json.Unmarshal(body, &apiResponse); err != nil {
return nil, fmt.Errorf("[%s] 解析JSON响应失败: %w (body=%.200s)", p.Name(), err, body)
} Defensive patterns
Strategy: validation
Validate before calling
func looksLikeOugeJSON(body []byte) bool {
trimmed := bytes.TrimSpace(body)
return len(trimmed) > 0 && trimmed[0] == '{' && json.Valid(trimmed)
} Try / catch
if err := json.Unmarshal(body, &apiResponse); err != nil {
var syn *json.SyntaxError
if errors.As(err, &syn) {
log.Printf("bad JSON at offset %d, body head: %.200s", syn.Offset, body)
}
return fallbackSearch(keyword)
} Prevention
- Always log a body snippet on unmarshal failure
- Check Content-Type is application/json before parsing
- Detect HTML challenge pages (starts with '<') early and treat as block
- Re-verify struct fields against the live API when it upgrades
When it happens
Trigger: searchImpl receives a 200 whose body is not the expected JSON object: an HTML block page, empty body, a JSON array where an object is expected, or a field type mismatch (e.g. code as string instead of number).
Common situations: Domain fronted/CDN serving anti-bot HTML with status 200; site migration changed the API response schema; proxy returning an error page; truncated JSON from an interrupted body read that io.ReadAll still 'succeeded' on.
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/1f18e78b1bf98004.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/ouge/ouge.go:154
req.Header.Set("Referer", "https://woog.nxog.eu.org/")
req.Header.Set("Cache-Control", "no-cache")
// 发送请求
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
// 解析JSON响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
}
var apiResponse OugeAPIResponse
if err := json.Unmarshal(body, &apiResponse); err != nil {
return nil, fmt.Errorf("[%s] 解析JSON响应失败: %w", p.Name(), err)
}
// 检查API响应状态
if apiResponse.Code != 1 {
return nil, fmt.Errorf("[%s] API返回错误: %s", p.Name(), apiResponse.Msg)
}
// 解析搜索结果
var results []model.SearchResult
for _, item := range apiResponse.List {
if result := p.parseAPIItem(item); result.Title != "" {
results = append(results, result)
}
}
return results, nil
}
View on GitHub (pinned to beaa561337)