Skip to main content

Async VMESS proxy rotation manager with automatic subscription updates and connection testing

Project description

Proxy Rotator

An async Python library for managing VMESS proxy rotation with automatic subscription updates, connection testing, and user-agent rotation. Perfect for web scraping projects that require reliable proxy management.

Python 3.14 License: MIT Code style: black


โœจ Features

  • Automatic Proxy Rotation: Seamlessly rotate through a pool of VMESS proxies
  • Subscription Support: Fetch and update proxies from subscription URLs
  • Connection Testing: Automatically test and filter working proxies
  • User-Agent Rotation: Optional automatic user-agent rotation for each proxy
  • Configurable: Extensive configuration options via Pydantic models
  • Pythonic: Clean async/await syntax with context managers
  • Full Logging: Comprehensive logging for debugging and monitoring
  • ate Limiting: Built-in delay with jitter for rate limit handling
  • Thread-Safe: Global lock prevents concurrent proxy sessions

๐Ÿ“‹ Requirements

  • Python 3.11+
  • Xray-core installed and accessible in PATH

Installing Xray-core

Linux/macOS:

bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

Windows: Download from Xray-core releases


๐Ÿš€ Installation

git clone https://github.com/keyhankamyar/proxy_rotator.git
cd proxy-rotator
pip install -e .

๐Ÿ“– Quick Start

Basic Usage

To use the rotation you have two options. Use manual API or context manager. First, manual:

import httpx
from proxy_rotator import ProxyRotator, ProxyRotatorConfig, RotationConfig

# Configure with a subscription URL
config = ProxyRotatorConfig(
    rotation_config=RotationConfig(
        subscription_url="https://your-subscription-url.com/vmess"
    )
)

rotator = ProxyRotator(config)

await rotator.start()

async with httpx.AsyncClient(
    proxy=rotator.proxy_url,
    headers=rotator.headers  # Contains user agent and other fields
) as client:
    response = await client.get("https://httpbin.org/ip")
    print(response.json())

await rotator.stop()  # Make sure to 'stop' before another 'start' to avoid locking

To rotate:

# ...
await rotator.start()

async with httpx.AsyncClient(
    proxy=rotator.proxy_url,
    headers=rotator.headers
) as client:
    response = await client.get("https://httpbin.org/ip")
    print(response.json())

await rotator.stop()

await rotator.start()  # Each new start will rotate the proxy

async with httpx.AsyncClient(
    proxy=rotator.proxy_url,
    headers=rotator.headers
) as client:
    response = await client.get("https://httpbin.org/ip")
    print(response.json())

await rotator.stop()

or:

# ...
await rotator.start()

async with httpx.AsyncClient(
    proxy=rotator.proxy_url,
    headers=rotator.headers
) as client:
    response = await client.get("https://httpbin.org/ip")
    print(response.json())

await rotator.rotate()

async with httpx.AsyncClient(
    proxy=rotator.proxy_url,
    headers=rotator.headers
) as client:
    response = await client.get("https://httpbin.org/ip")
    print(response.json())

await rotator.stop()

Context manager syntax to make your life easier:

async with rotator:
    async with httpx.AsyncClient(
        proxy=rotator.proxy_url,
        headers=rotator.headers
    ) as client:
        response = await client.get("https://httpbin.org/ip")
        print(response.json())

The context manager also have builtin retries for internal errors like port allocation.

To rotate:

async with rotator:
    async with httpx.AsyncClient(
        proxy=rotator.proxy_url,
        headers=rotator.headers
    ) as client:
        response = await client.get("https://httpbin.org/ip")
        print(response.json())

async with rotator:  # Each time this automatically rotates the proxy and optionally the user agents
    async with httpx.AsyncClient(
        proxy=rotator.proxy_url,
        headers=rotator.headers
    ) as client:
        response = await client.get("https://httpbin.org/ip")
        print(response.json())

or:

async with rotator:
    async with httpx.AsyncClient(
        proxy=rotator.proxy_url,
        headers=rotator.headers
    ) as client:
        response = await client.get("https://httpbin.org/ip")
        print(response.json())
    
    await rotator.rotate()

    async with httpx.AsyncClient(
        proxy=rotator.proxy_url,
        headers=rotator.headers
    ) as client:
        response = await client.get("https://httpbin.org/ip")
        print(response.json())

Direct Proxy List

from proxy_rotator import ProxyRotatorConfig, RotationConfig

config = ProxyRotatorConfig(
    rotation_config=RotationConfig(
        proxies=[
            "vmess://eyJhZGQiOiIxMjcuMC4wLjEi...",
            "vmess://eyJhZGQiOiIxOTIuMTY4LjEuMSI...",
        ]
    )
)

Note that "proxies" argument is mutually exclusive with "subscription_url". You should pass only one of them.


โš™๏ธ Configuration

Complete Configuration Example

from datetime import timedelta
from proxy_rotator import (
    ProxyRotatorConfig,
    RotationConfig,
    ConnectionTestConfig,
    HeadersConfig,
    XrayConfig,
    DelayConfig,
)

config = ProxyRotatorConfig(
    # Where to store proxy data and configs
    data_dir=Path(".proxy_rotator"),
    
    # Proxy rotation settings
    rotation_config=RotationConfig(
        subscription_url="https://your-subscription-url.com/vmess",
        subscription_update_interval=timedelta(hours=24),
        enable_shuffling=True,
    ),
    
    # Connection testing
    connection_test=ConnectionTestConfig(
        url="https://httpbin.org/",
        timeout=10.0,
        interval=timedelta(hours=1),
        max_parallel_connections=10,
        retries=2,
    ),
    
    # Headers and User-Agent rotation
    headers=HeadersConfig(
        rotate_user_agent=True,
        user_agents_url="https://cdn.jsdelivr.net/gh/microlinkhq/top-user-agents@master/src/desktop.json",
        default_values={
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Language": "en-US,en;q=0.9",
        }
    ),
    
    # Xray process management
    xray=XrayConfig(
        binary_path="xray",
        port_range_start=10000,
        port_range_end=60000,
        init_wait_seconds=2.0,
        shutdown_timeout=5.0,
    ),
    
    # Rate limiting with jitter
    delay=DelayConfig(
        enabled=True,
        base_delay=1.0,
        jitter=0.2,  # ยฑ20% variation
        min_delay=0.5,
        max_delay=2.0,
    ),
)

Environment Variables

You can also configure using environment variables:

export PROXY_ROTATOR_DATA_DIR=/custom/path
export PROXY_ROTATOR_XRAY__BINARY_PATH=/usr/local/bin/xray
export PROXY_ROTATOR_DELAY__ENABLED=true
export PROXY_ROTATOR_DELAY__BASE_DELAY=2.0

Or use a .env file:

PROXY_ROTATOR_DATA_DIR=/custom/path
PROXY_ROTATOR_XRAY__BINARY_PATH=/usr/local/bin/xray

๐Ÿ“š Advanced Usage

Custom User-Agent List

config = ProxyRotatorConfig(
    rotation_config=RotationConfig(
        subscription_url="https://...",
    ),
    headers=HeadersConfig(
        rotate_user_agent=True,
        user_agents_list=[
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
        ]
    )
)

Disable Proxy Shuffling

config = ProxyRotatorConfig(
    rotation_config=RotationConfig(
        subscription_url="https://...",
        enable_shuffling=False,  # Use proxies in order
    )
)

Force Refresh Subscription

# Force update subscription and re-test all proxies
await rotator.refresh()

Using with Logging

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger("my_scraper")
rotator = ProxyRotator(config, logger=logger)

๐Ÿ—๏ธ Project Structure

ProxyRotator/
โ”œโ”€โ”€ proxy_rotator/
โ”‚   โ”œโ”€โ”€ __init__.py         # Main exports
โ”‚   โ”œโ”€โ”€ config.py           # Configuration models
โ”‚   โ”œโ”€โ”€ errors.py           # Custom exceptions
โ”‚   โ”œโ”€โ”€ models.py           # Data models (VmessProxy, XrayConfig)
โ”‚   โ”œโ”€โ”€ process.py          # Xray process management
โ”‚   โ”œโ”€โ”€ py.typed
โ”‚   โ”œโ”€โ”€ rotator.py          # Main ProxyRotator class
โ”‚   โ”œโ”€โ”€ subscription.py     # Subscription fetching/parsing
โ”‚   โ”œโ”€โ”€ user_agents.py      # User-Agent management
โ”‚   โ”œโ”€โ”€ utils.py            # Utility functions
โ”‚   โ””โ”€โ”€ protocols/
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ””โ”€โ”€ vmess.py        # VMESS protocol parser
โ”œโ”€โ”€ .gitignore
โ”œโ”€โ”€ CHANGELOG.md
โ”œโ”€โ”€ LICENSE
โ”œโ”€โ”€ MANIFEST.in
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ pyproject.toml
โ””โ”€โ”€ requirements.txt

๐Ÿ” Error Handling

The library provides specific exceptions for different error cases:

from proxy_rotator.errors import (
    ProxyRotatorError,      # Base exception
    NetworkError,           # Network operation failures
    ProcessError,           # Xray process errors
    PortAllocationError,    # Port allocation failures
    ValidationError,        # Configuration/data validation
    SubscriptionError,      # Subscription fetch/parse errors
    ProtocolError,          # Protocol parsing errors
)

try:
    async with rotator:
        # Your code here
        pass
except PortAllocationError:
    print("Could not allocate a free port")
except NetworkError:
    print("No working proxies available")
except SubscriptionError:
    print("Failed to fetch subscription")

๐Ÿ› ๏ธ Development

Setup Development Environment

# Clone repository
git clone https://github.com/keyhankamyar/proxy_rotator.git
cd proxy-rotator

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install
pip install -e .

# Or install with development dependencies
# pip install -e ".[dev]"

๐Ÿ“ Roadmap

Future features planned for upcoming releases:

  • Full test suite with pytest
  • Context manager yielding httpx client directly
  • Parallel connection testing
  • VLESS protocol support
  • aiohttp client support
  • SOCKS proxy support
  • PyPI package distribution
  • Comprehensive documentation
  • Lock timeout configuration

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Please make sure to update tests as appropriate and follow the code style guidelines.


๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


๐Ÿ™ Acknowledgments


โš ๏ธ Disclaimer

This tool is for educational and legitimate use cases only. Users are responsible for complying with all applicable laws and terms of service of websites they interact with. The author is not responsible for any misuse of this software.


Made with โค๏ธ by Keyhan Kamyar

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xray_proxy_rotator-0.1.0.tar.gz (26.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

xray_proxy_rotator-0.1.0-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

Details for the file xray_proxy_rotator-0.1.0.tar.gz.

File metadata

  • Download URL: xray_proxy_rotator-0.1.0.tar.gz
  • Upload date:
  • Size: 26.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for xray_proxy_rotator-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5f1c53b3cffa9c836d788ab014e7cf03d308654e92b0aa643d5f8e655ebd0129
MD5 e232a789f40ab21212bd5e4461f7db0e
BLAKE2b-256 fa709c91f3847349603358fc1514d11dd1ad07c3cbc01cabc3b5b06b824135ad

See more details on using hashes here.

File details

Details for the file xray_proxy_rotator-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for xray_proxy_rotator-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b43766365190b3010874f6fe9080f41938f6781a96b5fcc10edefde4b46590c3
MD5 795494fa0f3ac03df28c6d8618b9e061
BLAKE2b-256 2bb6c06d3175589d9a6b2ed54924779bc2caf946886479743fa4a7406be1df4b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page