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

  1. Log the first ~200 bytes of body on unmarshal failure to see the actual payload
  2. Verify the API is reachable and returns JSON via curl
  3. Update lingjiSearchResponse struct to match the current API schema
  4. Add a content-type check before unmarshaling and treat HTML as an upstream failure
  5. 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

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)