{"record":{"id":"08a716eacd48f849","repo":"fish2018/pansou","slug":"s-d-08a716","errorCode":null,"errorMessage":"[%s] 搜索页面返回状态码: %d","messagePattern":"\\[(.+?)\\] 搜索页面返回状态码: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/ting77/ting77.go","lineNumber":86,"sourceCode":"\tif keyword == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), requestTimeout)\n\tdefer cancel()\n\tsearchURL := p.baseURL + \"/search?q=\" + url.QueryEscape(keyword)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建搜索请求失败: %w\", p.Name(), err)\n\t}\n\tsetRequestHeaders(req, p.baseURL+\"/\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索页面返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\tdoc, err := goquery.NewDocumentFromReader(io.LimitReader(resp.Body, maxResponseBytes))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索页面失败: %w\", p.Name(), err)\n\t}\n\tentries := parseSearchEntries(doc)\n\tif len(entries) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tresults, resolveErr := p.resolveEntries(ctx, client, entries)\n\tif len(results) == 0 && resolveErr != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 获取网盘链接失败: %w\", p.Name(), resolveErr)\n\t}\n\treturn plugin.FilterResultsByKeyword(results, keyword), nil\n}\n\ntype searchEntry struct {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/ting77/ting77.go#L68-L104","documentation":"searchImpl requires the search page response to have status 200. Any other status — 403/503 from Cloudflare or anti-bot protection, 404 for a moved path, 429 rate limit, 5xx outage — raises this error including the plugin name and status code. The HTTP exchange itself succeeded; the server rejected or failed the request.","triggerScenarios":"client.Do in searchImpl returns a response with StatusCode != http.StatusOK: bot-challenge pages on the ting77 domain, expired/moved search path, too-frequent searches, or server-side errors.","commonSituations":"Anti-bot (Cloudflare) interstitials when scraping without cookies; site moved to a new domain leaving the old one returning 404; hammering search triggers 429; transient 502/503 during site maintenance.","solutions":["Capture and log a body snippet on non-200 to tell anti-bot pages from genuine errors.","Update baseURL to the current working domain for the site.","Throttle search requests and add randomized delays to avoid 429/403 rate limiting.","Maintain cookies/session from the initial page load so challenge pages are passed.","Retry 5xx responses with backoff; treat 403 as a signal to rotate headers or use a resolver service."],"exampleFix":"// before\nif resp.StatusCode != http.StatusOK {\n    return nil, fmt.Errorf(\"[%s] 搜索页面返回状态码: %d\", p.Name(), resp.StatusCode)\n}\n// after: include a body hint for diagnosis\nif resp.StatusCode != http.StatusOK {\n    snippet, _ := io.ReadAll(io.LimitReader(resp.Body, 256))\n    return nil, fmt.Errorf(\"[%s] 搜索页面返回状态码: %d, body=%q\", p.Name(), resp.StatusCode, string(snippet))\n}","handlingStrategy":"fallback","validationCode":"resp, err := http.Get(p.baseURL + \"/\")\nif err != nil {\n    return err\n}\ndefer resp.Body.Close()\nif resp.StatusCode != http.StatusOK {\n    return fmt.Errorf(\"ting77 homepage returned %d — domain likely moved or blocked\", resp.StatusCode)\n}","typeGuard":"func isBlockedStatus(code int) bool {\n    return code == 403 || code == 429 || code == 503\n}","tryCatchPattern":"results, err := plugin.Search(keyword)\nif err != nil && strings.Contains(err.Error(), \"搜索页面返回状态码\") {\n    if strings.Contains(err.Error(), \": 403\") || strings.Contains(err.Error(), \": 503\") {\n        results, err = searchViaResolver(keyword) // challenge page: alternate route\n    } else {\n        results, err = retryWithBackoff(2, func() ([]model.Item, error) {\n            return plugin.Search(keyword)\n        })\n    }\n}","preventionTips":["Monitor the site's homepage status to detect domain moves early.","Throttle and jitter search requests to avoid rate-limit statuses.","Persist cookies from an initial page visit to pass anti-bot challenges.","Log a body snippet on non-200 to distinguish Cloudflare pages from real errors."],"tags":["http","status-code","scraping","anti-bot"],"backgroundTag":"http-non-200-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"}