{"record":{"id":"24c648a6723afcb6","repo":"fish2018/pansou","slug":"cloudflare","errorCode":null,"errorMessage":"站点触发 Cloudflare 浏览器验证","messagePattern":"站点触发 Cloudflare 浏览器验证","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/xdpan/xdpan.go","lineNumber":130,"sourceCode":"\tresp, err := p.doRequestWithRetry(req, client)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"GET请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"请求返回状态码: %d\", resp.StatusCode)\n\t}\n\n\t// 解析HTML\n\tdoc, err := goquery.NewDocumentFromReader(io.LimitReader(resp.Body, maxPageSize))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"解析HTML失败: %w\", err)\n\t}\n\n\tresults := p.extractSearchResults(doc)\n\tif len(results) == 0 && doc.Find(\"title\").First().Text() == \"Just a moment...\" {\n\t\treturn nil, fmt.Errorf(\"站点触发 Cloudflare 浏览器验证\")\n\t}\n\treturn results, nil\n}\n\n// extractSearchResults 从搜索页面提取结果\nfunc (p *XdpanPlugin) extractSearchResults(doc *goquery.Document) []model.SearchResult {\n\tvar results []model.SearchResult\n\n\t// 查找所有包含详情页链接的van-row元素\n\tdoc.Find(\"van-row\").Each(func(i int, s *goquery.Selection) {\n\t\t// 检查是否包含详情页链接\n\t\tdetailLink := s.Find(\"a[href^='/s/']\")\n\t\tif detailLink.Length() == 0 {\n\t\t\treturn\n\t\t}\n\n\t\tresult := p.parseSearchResult(s)\n\t\tif result.Title != \"\" {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/xdpan/xdpan.go#L112-L148","documentation":"This plugin-level heuristic detects Cloudflare's interstitial challenge page: if no search results were extracted and the page <title> is exactly 'Just a moment...', the site is serving a browser-verification challenge that server-side HTTP clients cannot pass, so a dedicated descriptive error is returned.","triggerScenarios":"Cloudflare bot management serves the JS challenge page with status 200; extractSearchResults finds zero results and the title matches 'Just a moment...'.","commonSituations":"Target site recently enabled Cloudflare under-attack mode or bot fight mode; datacenter IP or non-browser User-Agent flagged; scraping from a cloud provider IP range.","solutions":["Treat the site as migrated and stop server-side scraping (see the plugin's own detection at doRequestWithRetry via cf-mitigated header)","Use a headless browser (chromedp/rod) to pass the challenge","Route requests through a residential proxy or retry later","Check the cf-mitigated: challenge header proactively and surface a user-facing message"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"if doc.Find(\"title\").First().Text() == \"Just a moment...\" {\n    // challenge detected before extracting results\n    return errChallenge\n}","typeGuard":null,"tryCatchPattern":"results, err := plugin.Search(ctx, kw)\nif err != nil && strings.Contains(err.Error(), \"Cloudflare\") {\n    return useHeadlessBrowserFallback(ctx, kw)\n}","preventionTips":["Watch for Cloudflare 'Just a moment...' pages","Prefer headless browsers for protected sites","Use residential egress IPs","Disable scraping when cf-mitigated: challenge appears"],"tags":["cloudflare","anti-bot","scraping"],"backgroundTag":"cloudflare-challenge-blocked","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"}