HTTP Requests with urllib
Making Basic Requests
Python’s urllib.request module handles HTTP requests without external dependencies. The simplest request fetches a URL and reads the response:
import urllib.request # Fetch a webpage with urllib.request.urlopen('http://httpbin.org/html') as response: html_content = response.read() print(f"Status: {response.status}") print(f"Content length: {len(html_content)} bytes")
The urlopen() function returns a response object that works like a file. Always use with to ensure the connection closes properly.
Response Objects
The response object provides both data and metadata:
with urllib.request.urlopen('http://httpbin.org/json') as response: # Read response body data = response.read() # Returns bytes # Response metadata status_code = response.status # 200, 404, etc. headers = response.headers # Dictionary-like object content_type = response.headers.get('Content-Type') # Convert bytes to string text = data.decode('utf-8')
Key response attributes:
.read()- Get response body as bytes.status- HTTP status code.headers- Response headers.url- Final URL (after redirects)
Handling Timeouts
Network requests can hang indefinitely. Always set a timeout:
import urllib.request import urllib.error url = 'http://httpbin.org/delay/5' try: # Timeout after 3 seconds with urllib.request.urlopen(url, timeout=3) as response: data = response.read() except urllib.error.URLError as e: print(f"Request failed: {e}")
The timeout applies to the connection attempt, not the total download time.
Error Handling
Different errors require different handling:
import urllib.request import urllib.error def fetch_url(url): try: with urllib.request.urlopen(url, timeout=10) as response: return response.read(), response.status except urllib.error.HTTPError as e: # HTTP errors (404, 500, etc.) print(f"HTTP Error {e.code}: {e.reason}") return None, e.code except urllib.error.URLError as e: # Network errors (DNS, connection refused) print(f"URL Error: {e.reason}") return None, None except Exception as e: # Other errors print(f"Unexpected error: {e}") return None, None # Test with various URLs data, status = fetch_url('http://httpbin.org/status/404')
Measuring Response Time
Track how long requests take:
import time import urllib.request def timed_request(url): start_time = time.time() try: with urllib.request.urlopen(url, timeout=10) as response: data = response.read() elapsed_ms = (time.time() - start_time) * 1000 return { 'status': response.status, 'size': len(data), 'time_ms': elapsed_ms } except Exception as e: elapsed_ms = (time.time() - start_time) * 1000 return { 'error': str(e), 'time_ms': elapsed_ms } # Example usage result = timed_request('http://httpbin.org/delay/1') print(f"Response time: {result['time_ms']:.2f} ms")
Working with Headers
Check response headers to determine content type:
def fetch_with_type_check(url): with urllib.request.urlopen(url) as response: content_type = response.headers.get('Content-Type', '') data = response.read() # Check if response is text if 'text' in content_type or 'json' in content_type: text = data.decode('utf-8') return text, 'text' else: return data, 'binary' # Fetch and identify content content, content_format = fetch_with_type_check('http://httpbin.org/json') print(f"Content format: {content_format}")
Processing Multiple URLs
Pattern for fetching multiple URLs with error recovery:
import urllib.request import urllib.error def fetch_multiple(urls, timeout=10): results = [] for url in urls: try: with urllib.request.urlopen(url, timeout=timeout) as response: results.append({ 'url': url, 'status': response.status, 'size': len(response.read()), 'success': True }) except (urllib.error.HTTPError, urllib.error.URLError) as e: results.append({ 'url': url, 'error': str(e), 'success': False }) return results # Process a list of URLs urls = [ 'http://httpbin.org/status/200', 'http://httpbin.org/status/404', 'http://invalid.url.example' ] results = fetch_multiple(urls) for r in results: if r['success']: print(f"✓ {r['url']}: {r['status']}") else: print(f"✗ {r['url']}: {r['error']}")
Testing with httpbin.org
httpbin.org provides endpoints for testing HTTP operations:
# Common test endpoints test_urls = { 'success': 'http://httpbin.org/status/200', 'not_found': 'http://httpbin.org/status/404', 'server_error': 'http://httpbin.org/status/500', 'delay': 'http://httpbin.org/delay/2', 'json': 'http://httpbin.org/json', 'html': 'http://httpbin.org/html', 'redirect': 'http://httpbin.org/redirect/1' } # Test different scenarios for name, url in test_urls.items(): try: with urllib.request.urlopen(url, timeout=5) as response: print(f"{name}: Status {response.status}") except Exception as e: print(f"{name}: Error - {e}")
Complete Example: URL Fetcher
Putting it all together:
import urllib.request import urllib.error import json import time from datetime import datetime, timezone def process_url_list(url_file_path, output_dir): """Fetch URLs and generate report.""" # Read URLs from file with open(url_file_path, 'r') as f: urls = [line.strip() for line in f if line.strip()] results = [] for url in urls: start_time = time.time() try: with urllib.request.urlopen(url, timeout=10) as response: content = response.read() elapsed_ms = (time.time() - start_time) * 1000 # Check if content is text content_type = response.headers.get('Content-Type', '') word_count = None if 'text' in content_type: text = content.decode('utf-8', errors='ignore') words = text.split() word_count = len(words) results.append({ 'url': url, 'status_code': response.status, 'response_time_ms': elapsed_ms, 'content_length': len(content), 'word_count': word_count, 'timestamp': datetime.now(timezone.utc).isoformat(), 'error': None }) except Exception as e: elapsed_ms = (time.time() - start_time) * 1000 results.append({ 'url': url, 'status_code': None, 'response_time_ms': elapsed_ms, 'content_length': 0, 'word_count': None, 'timestamp': datetime.now(timezone.utc).isoformat(), 'error': str(e) }) # Save results output_path = f"{output_dir}/responses.json" with open(output_path, 'w') as f: json.dump(results, f, indent=2) return results
Method Reference
# Basic operations response = urllib.request.urlopen(url) # Open URL data = response.read() # Read bytes text = data.decode('utf-8') # Convert to string response.status # Get status code response.headers['Content-Type'] # Get header # With timeout response = urllib.request.urlopen(url, timeout=10) # Error handling try: response = urllib.request.urlopen(url) except urllib.error.HTTPError as e: # HTTP errors print(f"HTTP {e.code}: {e.reason}") except urllib.error.URLError as e: # Network errors print(f"Network error: {e.reason}")