Skip to content

Commit fb96773

Browse files
committed
feat(#26): add timeout=30000, forward_headers, premium_proxy auto-escalation
1 parent 34f550b commit fb96773

1 file changed

Lines changed: 44 additions & 23 deletions

File tree

‎backend/internal/service/scrapingbee_client.go‎

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ func (rl *RateLimiter) Release() {
6060

6161
func 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

Comments
 (0)