{"record":{"id":"774c874f5bc6fa45","repo":"fish2018/pansou","slug":"s-429-s","errorCode":null,"errorMessage":"[%s] 429限流，%s","messagePattern":"\\[(.+?)\\] 429限流，(.+?)","errorType":"exception","errorClass":null,"httpStatus":429,"severity":"warning","filePath":"plugin/javdb/javdb.go","lineNumber":315,"sourceCode":"\t\t\tcontinue\n\t\t}\n\t\t\n\t\t// 如果不是429，直接返回（无论成功还是其他错误）\n\t\tif resp.StatusCode != 429 {\n\t\t\treturn resp, nil\n\t\t}\n\t\t\n\t\t// 遇到429\n\t\tatomic.AddInt32(&p.rateLimitCount, 1)\n\t\tif p.debugMode {\n\t\t\tlog.Printf(\"[JAVDB] 遇到429限流，尝试 %d/%d\", attempt+1, MaxRetryOnRateLimit+1)\n\t\t}\n\t\t\n\t\t// 如果不允许重试或已达到最大重试次数\n\t\tif MaxRetryOnRateLimit == 0 || attempt >= MaxRetryOnRateLimit {\n\t\t\tatomic.StoreInt32(&p.rateLimited, 1)\n\t\t\tresp.Body.Close()\n\t\t\treturn nil, fmt.Errorf(\"[%s] 429限流，%s\", p.Name(), \n\t\t\t\tfunc() string {\n\t\t\t\t\tif MaxRetryOnRateLimit == 0 {\n\t\t\t\t\t\treturn \"不重试\"\n\t\t\t\t\t}\n\t\t\t\t\treturn fmt.Sprintf(\"重试%d次后仍然限流\", MaxRetryOnRateLimit)\n\t\t\t\t}())\n\t\t}\n\t\t\n\t\tresp.Body.Close()\n\t\tlastErr = fmt.Errorf(\"429 Too Many Requests\")\n\t}\n\t\n\treturn nil, lastErr\n}\n\n// parseSearchResults 解析搜索结果HTML\nfunc (p *JavdbPlugin) parseSearchResults(doc *goquery.Document) ([]model.SearchResult, error) {\n\tvar results []model.SearchResult","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/javdb/javdb.go#L297-L333","documentation":"doRequestWithRateLimitRetry returns '[javdb] 429限流，%s' (429 rate limited) when the server responds 429 and the plugin either has MaxRetryOnRateLimit == 0 (no retries allowed) or has already exhausted the allowed retry attempts. It also sets the plugin's atomic rateLimited flag so the caller knows the source is throttled.","triggerScenarios":"fetchDetailPageMagnetLinks (or another caller) issues requests fast enough that javdb answers 429; either retries are disabled by configuration or attempt >= MaxRetryOnRateLimit is reached while still receiving 429.","commonSituations":"Scraping many detail pages in a tight loop without delays; shared IP (VPN/datacenter) already throttled; MaxRetryOnRateLimit configured to 0; bursty concurrent searches from multiple users.","solutions":["Increase MaxRetryOnRateLimit or remove a 0 setting that disables retries","Add per-request delay/rate limiting between detail-page fetches","Respect any Retry-After header from the 429 response before retrying","Back off globally when the rateLimited flag is set (skip javdb for a cooldown period)","Reduce concurrency/parallel requests to javdb"],"exampleFix":"// before\nfor _, link := range links {\n    fetchDetail(link)\n}\n// after\nfor _, link := range links {\n    fetchDetail(link)\n    time.Sleep(requestInterval)\n}","handlingStrategy":"fallback","validationCode":"// Go: throttle before issuing detail requests\nif atomic.LoadInt32(&p.rateLimited) == 1 {\n    return nil, errors.New(\"javdb cooling down after 429\")\n}\n<-time.Tick(requestInterval)","typeGuard":"func isRateLimited(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"429限流\")\n}","tryCatchPattern":"magnets, err := p.fetchDetailPageMagnetLinks(url, client)\nif isRateLimited(err) {\n    time.Sleep(cooldownPeriod)\n    magnets, err = p.fetchDetailPageMagnetLinks(url, client)\n}","preventionTips":["Configure MaxRetryOnRateLimit > 0 so 429s are retried","Honor the Retry-After header from 429 responses","Serialize/limit concurrency of detail-page fetches","Respect the plugin's rateLimited flag and pause the source for a cooldown","Space requests with a fixed interval when batch-scraping detail pages"],"tags":["rate-limit","http-429","scraping","go"],"backgroundTag":"rate-limit-exceeded","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"}