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.
โจ 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.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - 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
- Xray-core for the excellent proxy core
- httpx for the async HTTP client
- Pydantic for data validation
- microlinkhq for providing top user agents
โ ๏ธ 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f1c53b3cffa9c836d788ab014e7cf03d308654e92b0aa643d5f8e655ebd0129
|
|
| MD5 |
e232a789f40ab21212bd5e4461f7db0e
|
|
| BLAKE2b-256 |
fa709c91f3847349603358fc1514d11dd1ad07c3cbc01cabc3b5b06b824135ad
|
File details
Details for the file xray_proxy_rotator-0.1.0-py3-none-any.whl.
File metadata
- Download URL: xray_proxy_rotator-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b43766365190b3010874f6fe9080f41938f6781a96b5fcc10edefde4b46590c3
|
|
| MD5 |
795494fa0f3ac03df28c6d8618b9e061
|
|
| BLAKE2b-256 |
2bb6c06d3175589d9a6b2ed54924779bc2caf946886479743fa4a7406be1df4b
|