{"record":{"id":"bb028b223d9683da","repo":"fish2018/pansou","slug":"document-returned-status-d","errorCode":null,"errorMessage":"document returned status %d","messagePattern":"document returned status (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/sousou/sousou.go","lineNumber":257,"sourceCode":"\t}\n\treturn results, nil\n}\n\nfunc (p *SousouAsyncPlugin) fetchPansoDocument(client *http.Client, item pansoSearchItem) (model.SearchResult, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)\n\tdefer cancel()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, item.DocURL, nil)\n\tif err != nil {\n\t\treturn model.SearchResult{}, err\n\t}\n\tsetSousouWebHeaders(req, SousouWebURL+\"?q=\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn model.SearchResult{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn model.SearchResult{}, fmt.Errorf(\"document returned status %d\", resp.StatusCode)\n\t}\n\tdoc, err := goquery.NewDocumentFromReader(resp.Body)\n\tif err != nil {\n\t\treturn model.SearchResult{}, err\n\t}\n\tlinkURL := strings.TrimSpace(doc.Find(\"a.jump-link[href]\").First().AttrOr(\"href\", \"\"))\n\tif linkURL == \"\" {\n\t\treturn model.SearchResult{}, fmt.Errorf(\"document has no share link\")\n\t}\n\tlinkType := util.GetLinkType(linkURL)\n\tif linkType == \"others\" || linkType == \"\" {\n\t\treturn model.SearchResult{}, fmt.Errorf(\"unsupported share link: %s\", linkURL)\n\t}\n\ttitle := cleanPansoTitle(doc.Find(\".resource-box h1\").First().Text())\n\tif title == \"\" {\n\t\ttitle = cleanPansoTitle(item.Title)\n\t}\n\tdatetime := item.Datetime","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/sousou/sousou.go#L239-L275","documentation":"fetchPansoDocument scrapes a panso document page via HTTP; when the response status is not 200 OK it aborts and returns this error instead of trying to parse the body. It signals the remote page was not served successfully, so goquery parsing would operate on an error page or empty body.","triggerScenarios":"The GET to the panso document URL returns any non-200 status (403 anti-bot block, 404 removed resource, 429 rate limit, 5xx server error) and resp.StatusCode != http.StatusOK.","commonSituations":"Site adds WAF/anti-scraping that returns 403 without a browser fingerprint; the shared resource was deleted (404); too many rapid requests trigger 429; transient upstream 502/503 during scraping.","solutions":["Log resp.StatusCode and retry the request with backoff for transient 429/5xx statuses","Send browser-like headers (User-Agent, Referer, Accept-Language) on the request to pass anti-bot checks","Handle 404 distinctly as 'resource no longer exists' and skip the item instead of surfacing an error","If 403 persists, check whether the site requires cookies/JS challenge and consider updating the scraper"],"exampleFix":"// before\nif resp.StatusCode != http.StatusOK {\n    return model.SearchResult{}, fmt.Errorf(\"document returned status %d\", resp.StatusCode)\n}\n// after\nif resp.StatusCode == http.StatusNotFound {\n    return model.SearchResult{}, ErrResourceGone // skip item\n}\nif resp.StatusCode != http.StatusOK {\n    return model.SearchResult{}, fmt.Errorf(\"document returned status %d (waf/rate-limit possible)\", resp.StatusCode)\n}","handlingStrategy":"retry","validationCode":"resp, err := client.Do(req)\nif err == nil && resp.StatusCode != http.StatusOK {\n    return fmt.Errorf(\"unexpected status %d\", resp.StatusCode)\n}","typeGuard":"func isTransientStatus(code int) bool { return code == 429 || code >= 500 }","tryCatchPattern":"resp, err := fetchPansoDocument(...)\nif err != nil {\n    var httpErr interface{ HTTPStatus() int }\n    if errors.As(err, &httpErr) && isTransientStatus(...) { /* retry */ }\n    return model.SearchResult{}, err\n}","preventionTips":["Send complete browser-like headers to avoid 403 anti-bot blocks","Throttle request rate to avoid 429","Distinguish 404 as a skippable 'gone' resource","Check sousou/panso reachability from the host before blaming the code"],"tags":["http","scraping","status-code"],"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"}