Overview
Request interception allows you to intercept network requests before they’re sent and responses before they reach the page. This is useful for:
Modifying request headers or payloads
Blocking unwanted resources (ads, trackers, images)
Mocking API responses
Reading response bodies for testing
Implementing custom caching logic
Basic interception
Zendriver provides a high-level intercept() method for common interception scenarios:
import asyncio
import zendriver as zd
from zendriver.cdp.fetch import RequestStage
from zendriver.cdp.network import ResourceType
async def main ():
browser = await zd.start()
page = await browser.get( "about:blank" )
# Intercept all image requests
async with page.intercept(
url_pattern = "*" ,
request_stage = RequestStage. REQUEST ,
resource_type = ResourceType. IMAGE
) as interception:
# Navigate to a page
await page.get( "https://example.com" )
# Block the request
await interception.fail_request(
error_reason = zd.cdp.network.ErrorReason. BLOCKED_BY_CLIENT
)
await browser.stop()
if __name__ == "__main__" :
asyncio.run(main())
Request stages
You can intercept at two stages:
Request stage Intercept before the request is sent to the server. Use this to modify or block requests.
Response stage Intercept after the response is received (but before the body is loaded). Use this to read or modify responses.
from zendriver.cdp.fetch import RequestStage
# Intercept requests before they're sent
RequestStage. REQUEST
# Intercept responses after they're received
RequestStage. RESPONSE
Resource types
Filter interceptions by resource type:
from zendriver.cdp.network import ResourceType
ResourceType. DOCUMENT # HTML pages
ResourceType. STYLESHEET # CSS files
ResourceType. IMAGE # Images (PNG, JPG, etc.)
ResourceType. SCRIPT # JavaScript files
ResourceType. XHR # XMLHttpRequest
ResourceType. FETCH # Fetch API requests
ResourceType. FONT # Web fonts
ResourceType. MEDIA # Audio/video
ResourceType. WEBSOCKET # WebSocket connections
Blocking requests
Block unwanted resources to speed up page loads:
import asyncio
import zendriver as zd
from zendriver.cdp.fetch import RequestStage
from zendriver.cdp.network import ResourceType, ErrorReason
async def main ():
browser = await zd.start()
page = await browser.get( "about:blank" )
# Block all images and stylesheets
async with page.intercept(
url_pattern = "*" ,
request_stage = RequestStage. REQUEST ,
resource_type = ResourceType. IMAGE
) as img_interception:
async with page.intercept(
url_pattern = "*" ,
request_stage = RequestStage. REQUEST ,
resource_type = ResourceType. STYLESHEET
) as css_interception:
await page.get( "https://example.com" )
# Block both types of requests
await img_interception.fail_request(ErrorReason. BLOCKED_BY_CLIENT )
await css_interception.fail_request(ErrorReason. BLOCKED_BY_CLIENT )
await browser.stop()
if __name__ == "__main__" :
asyncio.run(main())
Modifying requests
Modify request headers, method, or body:
import asyncio
import zendriver as zd
from zendriver.cdp.fetch import RequestStage, HeaderEntry
from zendriver.cdp.network import ResourceType
async def main ():
browser = await zd.start()
page = await browser.get( "about:blank" )
async with page.intercept(
url_pattern = "*api.example.com*" ,
request_stage = RequestStage. REQUEST ,
resource_type = ResourceType. FETCH
) as interception:
await page.get( "https://example.com" )
# Get the original request
request = await interception.request
# Add custom headers
custom_headers = [
HeaderEntry( name = "Authorization" , value = "Bearer custom-token" ),
HeaderEntry( name = "X-Custom-Header" , value = "custom-value" )
]
# Continue with modified headers
await interception.continue_request(
headers = custom_headers
)
await browser.stop()
if __name__ == "__main__" :
asyncio.run(main())
Reading responses
Capture and read API responses:
import asyncio
import json
import zendriver as zd
from zendriver.cdp.fetch import RequestStage
from zendriver.cdp.network import ResourceType
async def main ():
browser = await zd.start()
page = await browser.get( "about:blank" )
async with page.intercept(
url_pattern = "*user-data.json" ,
request_stage = RequestStage. RESPONSE ,
resource_type = ResourceType. FETCH
) as interception:
await page.get( "https://cdpdriver.github.io/examples/api-request.html" )
# Get response body
body, is_base64 = await interception.response_body
# Parse JSON response
data = json.loads(body)
print ( f "User: { data[ 'name' ] } " )
print (json.dumps(data, indent = 2 ))
# Continue with the original response
await interception.continue_response()
await browser.stop()
if __name__ == "__main__" :
asyncio.run(main())
When intercepting responses, you must call continue_response() or fulfill_request() to allow the page to continue loading.
Mocking responses
Return custom responses without hitting the server:
import asyncio
import json
import zendriver as zd
from zendriver.cdp.fetch import RequestStage, HeaderEntry
from zendriver.cdp.network import ResourceType
async def main ():
browser = await zd.start()
page = await browser.get( "about:blank" )
async with page.intercept(
url_pattern = "*api.example.com/user*" ,
request_stage = RequestStage. REQUEST ,
resource_type = ResourceType. FETCH
) as interception:
await page.get( "https://example.com" )
# Create a mock response
mock_data = {
"id" : 123 ,
"name" : "Test User" ,
"email" : "[email protected] "
}
# Fulfill with mock data
await interception.fulfill_request(
response_code = 200 ,
response_headers = [
HeaderEntry( name = "Content-Type" , value = "application/json" )
],
body = json.dumps(mock_data)
)
await browser.stop()
if __name__ == "__main__" :
asyncio.run(main())
URL patterns
The url_pattern parameter supports wildcards:
* matches zero or more characters
? matches exactly one character
Use \ to escape wildcards
# Match all requests
url_pattern = "*"
# Match specific domain
url_pattern = "*example.com*"
# Match specific path
url_pattern = "*api.example.com/v1/*"
# Match file extension
url_pattern = "*.png"
# Match exact URL
url_pattern = "https://example.com/api/user"
Using expect_response
For simpler cases where you just want to wait for and read a response, use expect_response():
import asyncio
import json
import zendriver as zd
from zendriver.cdp.network import get_response_body
async def main ():
browser = await zd.start()
page = browser.tabs[ 0 ]
# Wait for a specific response
async with page.expect_response( ".*/user-data.json" ) as response_expectation:
await page.get( "https://cdpdriver.github.io/examples/api-request.html" )
response = await response_expectation.value
# Read the response body
request_id = response.request_id
body, _ = await page.send(get_response_body( request_id = request_id))
user_data = json.loads(body)
print ( f "User: { user_data[ 'name' ] } " )
await browser.stop()
if __name__ == "__main__" :
asyncio.run(main())
Use expect_response() when you just need to read a response. Use intercept() when you need to modify, block, or fulfill requests.
Advanced: Multiple interceptions
You can set up multiple interceptions simultaneously:
import asyncio
import zendriver as zd
from zendriver.cdp.fetch import RequestStage
from zendriver.cdp.network import ResourceType, ErrorReason
async def main ():
browser = await zd.start()
page = await browser.get( "about:blank" )
# Set up multiple interceptions
img_intercept = page.intercept(
url_pattern = "*" ,
request_stage = RequestStage. REQUEST ,
resource_type = ResourceType. IMAGE
)
css_intercept = page.intercept(
url_pattern = "*" ,
request_stage = RequestStage. REQUEST ,
resource_type = ResourceType. STYLESHEET
)
async with img_intercept as img_int:
async with css_intercept as css_int:
await page.get( "https://example.com" )
# Handle both interceptions
await img_int.fail_request(ErrorReason. BLOCKED_BY_CLIENT )
await css_int.fail_request(ErrorReason. BLOCKED_BY_CLIENT )
await browser.stop()
if __name__ == "__main__" :
asyncio.run(main())
Best practices
Use specific URL patterns
Narrow down your patterns to avoid intercepting unnecessary requests. This improves performance.
Always continue or fulfill
When intercepting, always call continue_request(), continue_response(), fulfill_request(), or fail_request() to prevent the page from hanging.
Clean up interceptions
Use context managers (async with) to automatically clean up interceptions when done.
Handle errors gracefully
Interception operations can fail. Wrap them in try-except blocks.
Troubleshooting
Page hangs during interception
Make sure you’re calling one of: continue_request(), continue_response(), fulfill_request(), or fail_request(). The browser waits for your decision.
Interception not matching requests
Check your URL pattern. Use * liberally for testing, then narrow down. Also verify the resource_type matches.
Ensure you’re intercepting at RequestStage.RESPONSE, not RequestStage.REQUEST.
Next steps
CDP commands Learn more about using CDP directly
Cloudflare bypass Handle Cloudflare challenges automatically