fish2018/pansou · error

decode response failed

Error message

decode response failed (page %d, type %s): %w

What it means

json.Unmarshal failed on a sousou response body for a specific page/diskType. The server returned 200 but the body was not valid SousouResponse JSON — usually an anti-bot interstitial, truncated body, or schema change. Reported per-shard via errChan.

Solutions

  1. Log the first 500 bytes of respBody (the debugLog already does) to identify HTML vs truncated JSON
  2. Check whether a WAF challenge page is being returned and add required cookies/headers
  3. Update the SousouResponse struct if the API response schema changed
  4. Validate the body looks like JSON (e.g. starts with '{') and return a clearer message when it does not

Example fix

// before
var apiResp SousouResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
    errChan <- fmt.Errorf("decode response failed (page %d, type %s): %w", pageNum, diskType, err)
    return
}
// after
if len(respBody) > 0 && respBody[0] != '{' && respBody[0] != '[' {
    errChan <- fmt.Errorf("non-JSON response (page %d, type %s), likely WAF page: %.100s", pageNum, diskType, respBody)
    return
}
var apiResp SousouResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
    errChan <- fmt.Errorf("decode response failed (page %d, type %s): %w", pageNum, diskType, err)
    return
}
Defensive patterns

Strategy: type-guard

Validate before calling

trimmed := bytes.TrimSpace(respBody)
if len(trimmed) == 0 || (trimmed[0] != '{' && trimmed[0] != '[') {
    return fmt.Errorf("non-JSON body: %.100s", trimmed)
}

Type guard

func looksLikeJSON(b []byte) bool {
    b = bytes.TrimSpace(b)
    return len(b) > 0 && (b[0] == '{' || b[0] == '[')
}

Try / catch

var apiResp SousouResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
    return fmt.Errorf("unexpected sousou response: %w", err) // inspect logged body
}

Prevention

When it happens

Trigger: The API returned HTML (error page, WAF challenge, login page), an empty body, or truncated JSON — anything json.Unmarshal cannot decode into SousouResponse.

Common situations: Cloudflare/anti-bot HTML page returned with status 200; API endpoint changed its response schema; body truncated by a proxy; site moved and a redirect's HTML is captured.

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/6930bf3da3cc413a. Report an issue: GitHub.

Appendix: source

Thrown at plugin/sousou/sousou.go:444

				errChan <- fmt.Errorf("HTTP error (page %d, type %s): %d", pageNum, diskType, resp.StatusCode)
				return
			}

			// 读取响应体
			respBody, err := io.ReadAll(resp.Body)
			if err != nil {
				debugLog("读取响应失败 (page %d, type %s): %v", pageNum, diskType, err)
				errChan <- fmt.Errorf("read response body failed (page %d, type %s): %w", pageNum, diskType, err)
				return
			}

			debugLog("响应内容 (page %d, type %s, 前500字符): %s", pageNum, diskType, string(respBody[:min(500, len(respBody))]))

			// 解析响应
			var apiResp SousouResponse
			if err := json.Unmarshal(respBody, &apiResp); err != nil {
				debugLog("JSON解析失败 (page %d, type %s): %v", pageNum, diskType, err)
				errChan <- fmt.Errorf("decode response failed (page %d, type %s): %w", pageNum, diskType, err)
				return
			}

			// 检查响应状态
			if apiResp.Code != 200 {
				debugLog("API返回错误 (page %d, type %s): code=%d, msg=%s", pageNum, diskType, apiResp.Code, apiResp.Msg)
				errChan <- fmt.Errorf("API returned error (page %d, type %s): %s", pageNum, diskType, apiResp.Msg)
				return
			}

			debugLog("成功获取第 %d 页数据 (type %s),共 %d 条结果", pageNum, diskType, len(apiResp.Data.List))

			// 将结果发送到通道
			resultChan <- apiResp.Data.List
		}(page)
	}

	// 启动一个goroutine等待所有页面请求完成并关闭通道

View on GitHub (pinned to beaa561337)