@@ -60,9 +60,10 @@ func (rl *RateLimiter) Release() {
6060
6161func NewScrapingBeeClient (apiKey string , maxConcurrent int ) * ScrapingBeeClient {
6262 return & ScrapingBeeClient {
63- apiKey : apiKey ,
64- baseURL : scrapingBeeBaseURL ,
65- httpClient : & http.Client {Timeout : 60 * time .Second },
63+ apiKey : apiKey ,
64+ baseURL : scrapingBeeBaseURL ,
65+ // ScrapingBee timeout param is 30s; add 5s margin for network round-trip.
66+ httpClient : & http.Client {Timeout : 35 * time .Second },
6667 rateLimiter : NewRateLimiter (maxConcurrent , 500 * time .Millisecond ),
6768 }
6869}
@@ -114,40 +115,60 @@ func (c *ScrapingBeeClient) doRequest(ctx context.Context, targetURL string, use
114115 }
115116 defer c .rateLimiter .Release ()
116117
117- params := url.Values {}
118- params .Set ("api_key" , c .apiKey )
119- params .Set ("url" , targetURL )
120- // Kickstarter discover pages are SSR — no headless browser needed (1 credit vs 5)
121- params .Set ("render_js" , "false" )
122-
123- if useAI && aiQuery != "" {
124- params .Set ("ai_query" , aiQuery )
125- }
126- if useAI && aiSelector != "" {
127- params .Set ("ai_selector" , aiSelector )
128- }
129- if sessionID > 0 {
130- params .Set ("session_id" , strconv .Itoa (sessionID ))
118+ // buildParams constructs the query string, optionally upgrading to premium proxy.
119+ buildParams := func (premiumProxy bool ) string {
120+ params := url.Values {}
121+ params .Set ("api_key" , c .apiKey )
122+ params .Set ("url" , targetURL )
123+ // Kickstarter discover pages are SSR — render_js=false costs 1 credit (vs 5).
124+ params .Set ("render_js" , "false" )
125+ // Fail fast: 30s is more than enough for an SSR page; default is 140s.
126+ params .Set ("timeout" , "30000" )
127+ // Forward Accept-Language so the request looks like real browser traffic.
128+ params .Set ("forward_headers" , "true" )
129+ if premiumProxy {
130+ // Residential premium proxy (10 credits) as fallback when standard blocked.
131+ params .Set ("premium_proxy" , "true" )
132+ }
133+ if useAI && aiQuery != "" {
134+ params .Set ("ai_query" , aiQuery )
135+ }
136+ if useAI && aiSelector != "" {
137+ params .Set ("ai_selector" , aiSelector )
138+ }
139+ if sessionID > 0 {
140+ params .Set ("session_id" , strconv .Itoa (sessionID ))
141+ }
142+ return fmt .Sprintf ("%s?%s" , c .baseURL , params .Encode ())
131143 }
132144
133- reqURL := fmt .Sprintf ("%s?%s" , c .baseURL , params .Encode ())
134-
135145 var lastErr error
136- for attempt := 0 ; attempt < 3 ; attempt ++ {
146+ premiumProxy := false
147+
148+ for attempt := 0 ; attempt < 4 ; attempt ++ {
137149 if attempt > 0 {
138150 backoff := time .Duration (attempt ) * 2 * time .Second
139- log .Printf ("ScrapingBee retry attempt %d after %v" , attempt + 1 , backoff )
151+ // On the 3rd retry, escalate to premium_proxy (residential IP).
152+ if attempt == 3 && ! premiumProxy {
153+ premiumProxy = true
154+ log .Printf ("ScrapingBee escalating to premium_proxy for %s" , targetURL )
155+ } else {
156+ log .Printf ("ScrapingBee retry attempt %d after %v" , attempt , backoff )
157+ }
140158 select {
141159 case <- time .After (backoff ):
142160 case <- ctx .Done ():
143161 return "" , ctx .Err ()
144162 }
145163 }
146164
165+ reqURL := buildParams (premiumProxy )
147166 req , err := http .NewRequestWithContext (ctx , "GET" , reqURL , nil )
148167 if err != nil {
149168 return "" , fmt .Errorf ("create request: %w" , err )
150169 }
170+ // Forward a realistic Accept-Language header to Kickstarter.
171+ req .Header .Set ("Spb-Accept-Language" , "en-US,en;q=0.9" )
151172
152173 resp , err := c .httpClient .Do (req )
153174 if err != nil {
@@ -175,10 +196,10 @@ func (c *ScrapingBeeClient) doRequest(ctx context.Context, targetURL string, use
175196 }
176197
177198 credits := resp .Header .Get ("Spb-Cost" )
178- log .Printf ("ScrapingBee success: url=%s, credits=%s, useAI=%v" , targetURL , credits , useAI )
199+ log .Printf ("ScrapingBee success: url=%s, credits=%s, useAI=%v, premium=%v " , targetURL , credits , useAI , premiumProxy )
179200
180201 return string (body ), nil
181202 }
182203
183- return "" , fmt .Errorf ("failed after 3 attempts: %w" , lastErr )
204+ return "" , fmt .Errorf ("failed after 4 attempts: %w" , lastErr )
184205}
0 commit comments