fish2018/pansou · error

404 Not Found,buildId可能已过期

Error message

404 Not Found,buildId可能已过期

What it means

fetchFirstPage in plugin/pansearch/pansearch.go returns this error when the pansearch.me upstream API responds with HTTP 404. The scraper calls a Next.js buildId-based data endpoint; the buildId is scraped from the site's HTML and rotates on every site redeploy, so a cached/stale buildId makes the API path vanish and return 404. It is a wrapper around the upstream site's structural change, not a local bug.

Solutions

  1. Re-scrape the current buildId from https://www.pansearch.me/ (e.g. from the __NEXT_DATA__ script or buildId in the HTML) before each search instead of caching it
  2. Add retry logic: on 404, refresh the buildId once and retry fetchFirstPage
  3. Verify the target URL by curl-ing it manually with the same headers (User-Agent, Referer) to confirm the 404 is from the upstream site
  4. Check for site layout changes and update the buildId extraction regex/JSON path

Example fix

// before
if resp.StatusCode == 404 {
    return nil, 0, fmt.Errorf("404 Not Found,buildId可能已过期")
}
// after
if resp.StatusCode == 404 {
    if refErr := p.refreshBuildID(ctx); refErr != nil {
        return nil, 0, fmt.Errorf("404 Not Found,buildId刷新失败: %w", refErr)
    }
    return p.fetchFirstPage(ctx, query)
}
Defensive patterns

Strategy: retry

Validate before calling

if !strings.HasPrefix(targetURL, "https://www.pansearch.me/") {
    return fmt.Errorf("unexpected pansearch URL")
}
// pre-check buildId liveness:
// resp, _ := client.Head("https://www.pansearch.me/_next/data/" + buildID + "/search.json")
// if resp.StatusCode == 404 { refreshBuildID() }

Type guard

func isBuildIDExpired(err error) bool {
    return err != nil && strings.Contains(err.Error(), "404 Not Found")
}

Try / catch

items, total, err := p.fetchFirstPage(ctx, query)
if isBuildIDExpired(err) {
    if refErr := p.refreshBuildID(ctx); refErr == nil {
        items, total, err = p.fetchFirstPage(ctx, query)
    }
}
if err != nil { return fmt.Errorf("pansearch first page: %w", err) }

Prevention

When it happens

Trigger: fetchFirstPage (called by doSearch) performs an HTTP GET to the pansearch.me /_next/data/<buildId>/... endpoint and resp.StatusCode == 404. This happens when the buildId discovered earlier no longer exists on the site.

Common situations: Site redeployed between buildId discovery and the data fetch; a hardcoded or cached buildId from a previous run; the site changed its Next.js route structure; intermediate proxies/CDN returning 404 for the data route.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/6fbb2752a97d7d16. Report an issue: GitHub.

Appendix: source

Thrown at plugin/pansearch/pansearch.go:611

	// 设置完整的请求头
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
	req.Header.Set("Referer", "https://www.pansearch.me/")
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Cache-Control", "no-cache")
	req.Header.Set("Pragma", "no-cache")

	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		return nil, 0, fmt.Errorf("请求失败: %w", err)
	}
	defer resp.Body.Close()

	// 检查状态码
	if resp.StatusCode == 404 {
		return nil, 0, fmt.Errorf("404 Not Found,buildId可能已过期")
	}

	if resp.StatusCode != 200 {
		return nil, 0, fmt.Errorf("服务器返回非200状态码: %d", resp.StatusCode)
	}

	// 读取响应体
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, 0, fmt.Errorf("读取响应失败: %w", err)
	}

	// 解析响应
	var apiResp PanSearchResponse
	if err := json.Unmarshal(respBody, &apiResp); err != nil {
		return nil, 0, fmt.Errorf("解析响应失败: %w", err)
	}

View on GitHub (pinned to beaa561337)