Basic usage
Set a concurrency limit
Pass
WithMaxConcurrentRequests when creating the client to cap how many requests run at the same time:Set
WithMaxConcurrentRequests to match your ZenRows plan’s concurrent request limit. The default is 5. Each client instance enforces its own limit. Two scripts (or two client instances) don’t share it, so running both at once can still trigger 429 Too Many Requests errors. See Concurrency for how plan limits work.Scrape a list of URLs concurrently
Launch a goroutine per URL and use a Each goroutine that acquires a slot from the internal semaphore runs its request, then releases the slot, so no more than
sync.WaitGroup to track completion:WithMaxConcurrentRequests requests from this client are in flight at once, no matter how many goroutines you’ve launched.When to think about concurrency
You don’t need to do anything special to run requests concurrently. Launching goroutines already benefits from the internal semaphore. The default limit is 5. SetWithMaxConcurrentRequests explicitly when you want to match a higher plan limit, or lower it if you’re seeing 429 errors.
POST and PUT in parallel
The same pattern works withclient.Post() and client.Put():
- POST in parallel
- PUT in parallel
How it works under the hood
WithMaxConcurrentRequests(n) creates a buffered channel of size n inside the client. Every call to Get, Post, Put, or Scrape acquires a slot from that channel before sending the request, and releases it once the response comes back. This applies uniformly across methods, there’s no separate limit for GET versus POST/PUT. Retry behavior configured via WithMaxRetryCount applies the same way regardless of how many requests run in parallel. See Error Handling and Retries for details.Troubleshooting
429 Too Many Requestseven with aWithMaxConcurrentRequestsvalue that matches your plan: Another script or process is using the same API key at the same time. Concurrency limits are per plan, not per client instance, so simultaneous scripts share the same real ceiling even though each*scraperapi.Clientonly tracks its own local limit.- Goroutines seem to run one at a time: Confirm you’re not accidentally blocking inside the goroutine before calling
wg.Done(), and that you’re not reusing a singlecontext.Contextwith an already-expired deadline. - Program exits before all goroutines finish: Make sure
wg.Wait()runs beforemain()returns; a goroutine that hasn’t calledDone()yet is silently dropped when the program exits. - Cancelling a request doesn’t free a concurrency slot right away: ZenRows keeps the slot occupied for up to a few minutes after a client-side cancellation while it finishes processing server-side.
Pricing
Requests are billed identically whether they run one at a time or concurrently. Cost depends on the parameters you send per request, not on how many run in parallel:| Configuration | Cost multiplier vs. basic |
|---|---|
| Basic request | 1x |
JSRender: true | 5x |
UsePremiumProxies: true | 10x |
JSRender: true + UsePremiumProxies: true | 25x |
FAQ (Frequently Asked Questions)
What's the default concurrency limit if I don't set one?
What's the default concurrency limit if I don't set one?
The default is 5. Even without passing
WithMaxConcurrentRequests explicitly, the SDK creates an internal semaphore capped at 5. Set it explicitly if your plan allows more, or lower it if you’re seeing 429 Too Many Requests errors.Do I need a worker pool library to do this?
Do I need a worker pool library to do this?
No. A
sync.WaitGroup plus goroutines, as shown above, is enough. Reach for a worker pool library only if you need more advanced scheduling, like rate limiting independent of the SDK’s own semaphore.