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
- 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
- Add retry logic: on 404, refresh the buildId once and retry fetchFirstPage
- 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
- 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
- Never persist buildId between runs; scrape it fresh each run
- On any 404, refresh the buildId once before surfacing failure
- Monitor pansearch.me for layout changes affecting buildId extraction
- Keep browser-like headers on all requests to avoid edge-route 404s
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)