fish2018/pansou · error
[ ] 解析搜索响应失败
Error message
[%s] 解析搜索响应失败: %w
What it means
fetchSearchItems received a response body from getVideoList but json.Unmarshal into lingjiSearchResponse failed. The API returned a body that is not JSON or does not match the expected envelope (success/code/data fields). This is a contract mismatch between the plugin's response struct and the actual API payload.
Solutions
- Log the first ~200 bytes of body on unmarshal failure to see the actual payload
- Verify the API is reachable and returns JSON via curl
- Update lingjiSearchResponse struct to match the current API schema
- Add a content-type check before unmarshaling and treat HTML as an upstream failure
- Retry, since a transient proxy error page can produce this
Example fix
// before
var resp lingjiSearchResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("[%s] 解析搜索响应失败: %w", p.Name(), err)
}
// after
if !json.Valid(body) || len(body) > 0 && body[0] != '{' {
return nil, fmt.Errorf("[%s] 响应不是JSON: %q", p.Name(), body[:min(len(body),200)])
}
var resp lingjiSearchResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("[%s] 解析搜索响应失败: %w", p.Name(), err)
} Defensive patterns
Strategy: validation
Validate before calling
// Validate JSON shape before unmarshaling into the typed struct
if len(body) == 0 || body[0] != '{' {
return fmt.Errorf("响应不是JSON对象: %q", body[:min(len(body),100)])
}
var probe map[string]json.RawMessage
if err := json.Unmarshal(body, &probe); err != nil {
return fmt.Errorf("响应JSON非法: %w", err)
}
if _, ok := probe["data"]; !ok {
return fmt.Errorf("响应缺少data字段")
} Type guard
func looksLikeLingjiSearch(body []byte) bool {
var probe struct {
Success bool `json:"success"`
Code int `json:"code"`
Data struct {
Data json.RawMessage `json:"data"`
List json.RawMessage `json:"list"`
} `json:"data"`
}
return json.Unmarshal(body, &probe) == nil
} Try / catch
items, err := fetchSearchItems(...)
if err != nil && strings.Contains(err.Error(), "解析搜索响应失败") {
log.Printf("lingjisp schema drift: %v", err)
return emptyResults, nil // degrade instead of failing whole search
} Prevention
- Log raw bodies on unmarshal failure to catch schema changes early
- Keep lingjiSearchResponse in sync with the live API contract
- Check Content-Type before parsing
- Add json.RawMessage probes for schema drift detection
When it happens
Trigger: doLingjiGET succeeded but the body is HTML (error/anti-bot page), empty, or JSON with a different schema than lingjiSearchResponse, causing json.Unmarshal to return an error.
Common situations: API domain hijacked/parked returning an HTML page; API version changed its response schema; a CDN error page (502 HTML) was returned with 200/other status; truncated response body.
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/d98bd69c8a2db4d4.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/lingjisp/lingjisp.go:199
}
func (p *LingjiPlugin) fetchSearchItems(client *http.Client, keyword string) ([]lingjiVideoItem, error) {
params := url.Values{}
params.Set("app_id", lingjiAppID)
params.Set("identity", lingjiIdentity)
params.Set("sb", keyword)
params.Set("page", "1")
params.Set("limit", "20")
apiURL := lingjiAPIBase + "getVideoList?" + params.Encode()
body, err := doLingjiGET(client, apiURL, lingjiSearchTimeout)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
var resp lingjiSearchResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("[%s] 解析搜索响应失败: %w", p.Name(), err)
}
if !resp.Success || resp.Code != http.StatusOK {
return nil, fmt.Errorf("[%s] 搜索接口返回异常: success=%v code=%d", p.Name(), resp.Success, resp.Code)
}
items := resp.Data.Data
if len(items) == 0 {
items = resp.Data.List
}
return dedupeLingjiItems(items), nil
}
func (p *LingjiPlugin) fetchDetail(client *http.Client, doubID int) (lingjiVideoItem, error) {
params := url.Values{}
params.Set("app_id", lingjiAppID)
params.Set("identity", lingjiIdentity)
params.Set("id", fmt.Sprintf("%d", doubID))
View on GitHub (pinned to beaa561337)