# Client-side rotation

ISP IPs are permanently stable — there's no `session` parameter and no gateway rotation. If you want different IPs across requests, your client picks them. This page documents the common patterns.

## Round-robin

The simplest pattern: cycle through the list in order.

```python
import itertools, requests

with open("isp.txt") as f:
    lines = [l.strip() for l in f if l.strip()]

cycle = itertools.cycle(lines)

def get(url):
    ip, port, user, password = next(cycle).split(":", 3)
    proxy = f"http://{user}:{password}@{ip}:{port}"
    return requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
```

Pros: trivial. Cons: failed IPs keep getting picked.

## Round-robin + health tracking

Track failures and skip dead IPs for a cool-down window:

```python
import time, itertools, requests
from collections import defaultdict

class IPPool:
    def __init__(self, lines, cooldown=300):
        self.lines = lines
        self.cooldown = cooldown
        self.bad_until = defaultdict(float)
        self._iter = itertools.cycle(lines)

    def next_alive(self):
        for _ in range(len(self.lines)):
            line = next(self._iter)
            if self.bad_until[line] < time.time():
                return line
        raise RuntimeError("All IPs are in cool-down")

    def mark_bad(self, line):
        self.bad_until[line] = time.time() + self.cooldown

pool = IPPool(open("isp.txt").read().splitlines())

def get(url):
    line = pool.next_alive()
    ip, port, user, password = line.split(":", 3)
    proxy = f"http://{user}:{password}@{ip}:{port}"
    try:
        r = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
        if r.status_code >= 500:
            pool.mark_bad(line)
        return r
    except requests.RequestException:
        pool.mark_bad(line)
        raise
```

## Sticky-per-task

If your unit of work needs a stable IP (login flow, multi-step purchase), pin one IP per task:

```python
import hashlib

def ip_for_task(task_id, lines):
    h = int(hashlib.sha256(task_id.encode()).hexdigest(), 16)
    return lines[h % len(lines)]
```

Consistent hashing means restarts and re-runs of the same `task_id` hit the same IP.

## Concurrency caps

Each ISP IP can handle high throughput (1 Gbps+), but most target sites will rate-limit you per IP. A reasonable starting point: **max 5 concurrent requests per IP** to a single target site. Sharded across the batch, this scales linearly.

## Coordinating across processes

If multiple workers share one batch, use a shared store (Redis, etcd) to coordinate rotation and health tracking. A simple Redis-backed pool:

```python
import redis, time, json

r = redis.Redis()

def pick(batch_key):
    lines = json.loads(r.get(f"isp:{batch_key}:lines"))
    idx = r.incr(f"isp:{batch_key}:counter") % len(lines)
    return lines[idx]
```

For health tracking, use a sorted set keyed on `cooldown_until` and skip entries above `now()`.

## When **not** to rotate

If your use case is account-bound (logged-in scraping, posting, monitoring a logged-in dashboard), **do not rotate**. Pin one IP per account. The whole reason to use ISP is IP stability — rotating defeats it.

## Tools that handle rotation for you

* **Antidetect browsers** (AdsPower, Multilogin, GoLogin) — assign a different ISP IP per browser profile
* **Proxifier** — round-robin across imported list at the OS level
* **Scrapy + scrapy-rotating-proxies** — middleware reads a list and rotates
* **Apify SDK proxy configuration** — handles rotation automatically when given a list

For most production stacks, your scraper framework's built-in proxy rotator is enough. The code above is for when you need custom logic.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.helodata.com/products/overview-2/rotation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
