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

  1. Log a snippet of the raw body (body[:200]) to see what was actually returned
  2. Check whether an anti-bot/CDN challenge page is being served and consider updating the Referer/UA or base URL
  3. Verify the API schema still matches OugeAPIResponse (code/list/msg field types)
  4. 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

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)