{"record":{"id":"6fbb2752a97d7d16","repo":"fish2018/pansou","slug":"404-not-found-buildid","errorCode":null,"errorMessage":"404 Not Found，buildId可能已过期","messagePattern":"404 Not Found，buildId可能已过期","errorType":"exception","errorClass":null,"httpStatus":404,"severity":"error","filePath":"plugin/pansearch/pansearch.go","lineNumber":611,"sourceCode":"\t// 设置完整的请求头\n\treq.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\")\n\treq.Header.Set(\"Referer\", \"https://www.pansearch.me/\")\n\treq.Header.Set(\"Accept\", \"application/json, text/plain, */*\")\n\treq.Header.Set(\"Accept-Language\", \"zh-CN,zh;q=0.9,en;q=0.8\")\n\treq.Header.Set(\"Connection\", \"keep-alive\")\n\treq.Header.Set(\"Cache-Control\", \"no-cache\")\n\treq.Header.Set(\"Pragma\", \"no-cache\")\n\n\t// 发送请求\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 检查状态码\n\tif resp.StatusCode == 404 {\n\t\treturn nil, 0, fmt.Errorf(\"404 Not Found，buildId可能已过期\")\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, 0, fmt.Errorf(\"服务器返回非200状态码: %d\", resp.StatusCode)\n\t}\n\n\t// 读取响应体\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\t// 解析响应\n\tvar apiResp PanSearchResponse\n\tif err := json.Unmarshal(respBody, &apiResp); err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n","sourceCodeStart":593,"sourceCodeEnd":629,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/pansearch/pansearch.go#L593-L629","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif resp.StatusCode == 404 {\n    return nil, 0, fmt.Errorf(\"404 Not Found，buildId可能已过期\")\n}\n// after\nif resp.StatusCode == 404 {\n    if refErr := p.refreshBuildID(ctx); refErr != nil {\n        return nil, 0, fmt.Errorf(\"404 Not Found，buildId刷新失败: %w\", refErr)\n    }\n    return p.fetchFirstPage(ctx, query)\n}","handlingStrategy":"retry","validationCode":"if !strings.HasPrefix(targetURL, \"https://www.pansearch.me/\") {\n    return fmt.Errorf(\"unexpected pansearch URL\")\n}\n// pre-check buildId liveness:\n// resp, _ := client.Head(\"https://www.pansearch.me/_next/data/\" + buildID + \"/search.json\")\n// if resp.StatusCode == 404 { refreshBuildID() }","typeGuard":"func isBuildIDExpired(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"404 Not Found\")\n}","tryCatchPattern":"items, total, err := p.fetchFirstPage(ctx, query)\nif isBuildIDExpired(err) {\n    if refErr := p.refreshBuildID(ctx); refErr == nil {\n        items, total, err = p.fetchFirstPage(ctx, query)\n    }\n}\nif err != nil { return fmt.Errorf(\"pansearch first page: %w\", err) }","preventionTips":["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"],"tags":["http","scraping","upstream","nextjs"],"backgroundTag":"http-error-response","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}