Integrating Proxies into Apache Airflow Workflows for Scheduled Data Extraction
July 12, 2026
Why Use Proxies in Airflow Workflows
Apache Airflow excels at orchestrating repetitive tasks such as nightly data pulls, weekly market scans, or continuous SEO monitoring. When those tasks involve hitting external websites or APIs, you quickly run into rate limits, IP bans, or geo‑restrictions. Adding a proxy layer lets you:
- Distribute requests across many IP addresses to stay under per‑IP limits.
- Appear as traffic from different geographic regions for localized data.
- Hide the origin of your Airflow workers, reducing the chance of being flagged as a bot.
- Rotate IPs automatically when a site returns a 429 or CAPTCHA response.
A quality proxy provider like RoProxy supplies residential, datacenter, or mobile IPs with built‑in rotation and authentication, making it straightforward to plug into Airflow without managing your own proxy infrastructure.
Architecture Overview
At a high level, an Airflow DAG that uses proxies follows this flow:
- Task Instance – A PythonOperator (or custom operator) prepares a request.
- Proxy Configuration – The operator reads proxy credentials from Airflow Connections or Variables.
- Request Execution – The request is sent through the chosen proxy using libraries such as
requests,httpx, oraiohttp. - Response Handling – Based on status codes or content, the operator decides whether to retry with a different IP.
- Logging & Monitoring – Proxy IP, latency, and outcome are logged for debugging and metrics.
This design keeps the proxy logic encapsulated, making DAGs easy to read and maintain.
Setting Up Proxy Credentials in Airflow
Airflow recommends storing secrets in Connections (encrypted in the metadata database) or using the Secrets Backend (AWS Secrets Manager, HashiCorp Vault, etc.). For this guide we’ll use a simple HTTP proxy Connection.
Create a Connection
- Go to Admin → Connections → Create.
- Conn Id:
my_proxy - Conn Type:
HTTP - Host:
proxy.roproxy.com - Port:
8000 - Login:
your_username - Password:
your_password - Extra (JSON):
{"proxy_type": "residential"}
Reference the Connection in Code
from airflow.hooks.base import BaseHook def get_proxy_dict(): conn = BaseHook.get_connection('my_proxy') proxy_url = f"http://{conn.login}:{conn.password}@{conn.host}:{conn.port}" return {"http": proxy_url, "https": proxy_url}
Building a Custom Proxy Operator
While you could call get_proxy_dict() inside each task, a reusable operator keeps the DAG clean. Below is a ProxyHttpOperator that extends BaseOperator and handles rotation automatically.
# proxy_http_operator.py
from airflow.models import BaseOperator
from airflow.hooks.base import BaseHook
import requests
import time
class ProxyHttpOperator(BaseOperator):
"""
Executes an HTTP request through a configurable proxy.
:param endpoint: URL to call.
:param method: HTTP method (default GET).
:param params: Query string parameters.
:param headers: Request headers.
:param proxy_conn_id: Airflow Connection ID for the proxy.
:param max_retries: How many times to retry with a new IP on failure.
:param retry_delay: Seconds to wait between retries.
"""
template_fields = ('endpoint', 'params', 'headers')
def __init__(self, *, endpoint, method='GET', params=None, headers=None,
proxy_conn_id='my_proxy', max_retries=3, retry_delay=2, **kwargs):
super().__init__(**kwargs)
self.endpoint = endpoint
self.method = method.upper()
self.params = params or {}
self.headers = headers or {}
self.proxy_conn_id = proxy_conn_id
self.max_retries = max_retries
self.retry_delay = retry_delay
def _get_proxy(self):
conn = BaseHook.get_connection(self.proxy_conn_id)
proxy_url = f"http://{conn.login}:{conn.password}@{conn.host}:{conn.port}"
return {"http": proxy_url, "https": proxy_url}
def execute(self, context):
attempt = 0
while attempt <= self.max_retries:
proxies = self._get_proxy()
try:
self.log.info(f"Attempt {attempt+1} – calling {self.endpoint} via {proxies['http']}" )
resp = requests.request(
method=self.method,
url=self.endpoint,
params=self.params,
headers=self.headers,
proxies=proxies,
timeout=30
)
# Treat 2xx as success, 429/403/503 as proxy‑related failures
if resp.status_code < 300:
self.log.info(f"Success – status {resp.status_code}" )
return resp.text
if resp.status_code in (429, 403, 503):
raise requests.HTTPError(f"Status {resp.status_code}" )
# For other errors (e.g., 404) we do not rotate IP
resp.raise_for_status()
except Exception as e:
attempt += 1
self.log.warning(f"Request failed: {e}" )
if attempt > self.max_retries:
self.log.error("Max retries exceeded. Raising." )
raise
self.log.info(f"Waiting {self.retry_delay}s before retry…" )
time.sleep(self.retry_delay)
# Should never reach here
raise RuntimeError("Unexpected exit from retry loop" )
The operator does three important things:
- Retrieves proxy credentials from the Connection each attempt, so if your provider rotates credentials (e.g., time‑based token) you get the latest.
- Treats HTTP 429 (Too Many Requests), 403 (Forbidden – often a block), and 503 (Service Unavailable) as signals to switch IP and retry.
- Logs each attempt, making it easy to see which IP succeeded.
Using the Operator in a DAG
Here’s a sample DAG that scrapes a product listing page every six hours, rotating proxies automatically when needed.
# dag_proxy_scrape.py
from datetime import datetime, timedelta
from airflow import DAG
from proxy_http_operator import ProxyHttpOperator
default_args = {
"owner": "data-eng",
"depends_on_past": False,
"email_on_failure": False,
"retries": 0, # retries handled by the operator
"retry_delay": timedelta(minutes=5),
}
with DAG(
dag_id="proxy_product_scrape",
default_args=default_args,
description="Scrape product prices using rotating proxies",
schedule_interval="0 */6 * * *", # every 6 hours
start_date=datetime(2024, 1, 1),
catchup=False,
tags=["scraping", "proxy"],
) as dag:
scrape_task = ProxyHttpOperator(
task_id="scrape_product_page",
endpoint="https://example-shop.com/products?category=electronics",
method="GET",
headers={
"User-Agent": "Mozilla/5.0 (compatible; AirflowScraper/1.0)" ,
"Accept": "text/html,application/xhtml+xml" ,
},
proxy_conn_id="my_proxy",
max_retries=5,
retry_delay=3,
)
scrape_task
When the DAG runs, Airflow logs will show something like:
[2024-09-25 10:00:00,123] {base_task_runner.py:115} INFO - Attempt 1 – calling https://example-shop.com/products?category=electronics via http://user:[email protected]:8000
[2024-09-25 10:00:02,456] {proxy_http_operator.py:45} WARNING - Request failed: 429 Client Error: Too Many Requests for url: https://example-shop.com/products?category=electronics
[2024-09-25 10:00:05,470] {proxy_http_operator.py:45} INFO - Attempt 2 – calling https://example-shop.com/products?category=electronics via http://user:[email protected]:8000
[2024-09-25 10:00:07,890] {proxy_http_operator.py:45} INFO - Success – status 200
Handling Authentication and Session Persistence
Some sites require login or maintain session cookies across multiple requests. In those cases you’ll want to preserve cookies between attempts while still rotating the underlying IP. You can achieve this by using a requests.Session object inside the operator.
import requests
class ProxyHttpOperator(BaseOperator):
# … (same init as before) …
def execute(self, context):
session = requests.Session()
attempt = 0
while attempt <= self.max_retries:
proxies = self._get_proxy()
session.proxies.update(proxies)
try:
resp = session.get(self.endpoint, headers=self.headers, timeout=30)
if resp.status_code < 300:
return resp.text
if resp.status_code in (429, 403, 503):
raise requests.HTTPError(f"Status {resp.status_code}" )
resp.raise_for_status()
except Exception as e:
attempt += 1
self.log.warning(f"Request failed: {e}" )
if attempt > self.max_retries:
raise
self.log.info(f"Waiting {self.retry_delay}s before retry…" )
time.sleep(self.retry_delay)
The session retains cookies, while session.proxies is refreshed each loop, giving you a new IP but the same logged‑in state.
Monitoring Proxy Performance
To ensure your proxy layer isn’t becoming a bottleneck, collect basic metrics:
- Latency: Record
resp.elapsed.total_seconds()for each attempt. - Success Rate: Ratio of successful attempts to total attempts.
- IP Usage: Log the proxy host (or the IP returned by a service like
https://api.ipify.org) to see distribution.
You can push these metrics to Airflow’s StatsD integration or to a monitoring system like Prometheus via a simple PostExecute hook.
from airflow.utils.stats import Stats
# Inside execute, after a successful request:
Stats.timing('proxy.latency', resp.elapsed.total_seconds())
Stats.incr('proxy.success')
# After a failed attempt:
Stats.incr('proxy.failure')
Graphing latency over time helps you spot slow proxy nodes; a rising failure rate may indicate you need to upgrade your proxy plan or adjust rotation frequency.
Best Practices
- Never hard‑code credentials – always use Airflow Connections or a secrets backend.
- Respect target site’s terms – proxy rotation does not give you carte blanche to scrape aggressively; keep request rates reasonable and add
User‑Agentstrings that identify your bot. - Use sticky sessions when needed – if a multi‑step flow (login → search → download) requires the same IP for the duration, set
max_retries=0for those steps and handle rotation only between distinct flows. - Test with a small subset – run the DAG against a test endpoint first to verify proxy auth and error handling.
- Handle CAPTCHAs gracefully – if you encounter a CAPTCHA page, treat it as a failure, rotate IP, and consider adding a manual solve step or switching to a provider that offers CAPTCHA‑resistant residential IPs.
- Limit concurrent workers – too many parallel tasks using the same proxy pool can exhaust the pool; adjust
poolsettings in your Airflow config or use separate proxy Connections per worker group.
Conclusion
Integrating proxies into Apache Airflow transforms fragile, IP‑bound scraping jobs into resilient, scalable data pipelines. By encapsulating proxy logic in a custom operator, you gain:
- Automatic IP rotation on throttling or blocking.
- Centralized credential management via Airflow Connections.
- Transparent session handling for sites that require authentication.
- Built‑in logging and metrics for performance tuning.
With a reliable provider like RoProxy supplying a diverse pool of residential, datacenter, or mobile IPs, you can focus on the business logic of your DAGs while the proxy layer handles the noisy reality of the web. Start small, monitor the metrics, and iteratively tune retry delays, pool sizes, and request rates to achieve the optimal balance between speed and stealth.
Happy airflow‑powered scraping!