Tại sao lỗi này xảy ra
Nếu bạn từng dành thời gian cào dữ liệu web hoặc gọi API bằng Python, có lẽ bạn đã gặp phải trở ngại này. Lỗi này không chỉ đơn thuần là một vấn đề; nó là tín hiệu cho thấy lệnh gọi requests của bạn đã thất bại trong việc thiết lập handshake với máy chủ sau nhiều lần thử.
Theo mặc định, thư viện sẽ cố gắng kết nối ngay lập tức. Nếu mạng chập chờn hoặc máy chủ bận dù chỉ trong 100 mili giây, kết nối sẽ thất bại. Khi những thất bại này chạm đến một giới hạn xác định trước—"Max retries"—Python sẽ dừng lại và ném ra ngoại lệ này để ngăn script của bạn lặp lại vô hạn.
Các nguyên nhân gốc rễ phổ biến
- Giới hạn tốc độ (Rate Limiting) quá mức: Các API như GitHub hoặc Twitter thường giới hạn người dùng ở mức 5.000 yêu cầu mỗi giờ. Nếu bạn vượt quá mức này, máy chủ có thể ngắt kết nối của bạn hoàn toàn.
- Lỗi phân giải DNS: Máy cục bộ của bạn không thể dịch tên miền thành địa chỉ IP, thường do tệp
/etc/hostsbị cấu hình sai hoặc nhà cung cấp DNS chậm như 8.8.8.8. - Mạng không ổn định: Tỷ lệ mất gói tin (packet loss) cao trên Wi-Fi công cộng hoặc kết nối VPN chập chờn có thể làm gián đoạn quá trình TCP handshake giữa chừng.
- Chặn bởi WAF: Các lớp bảo mật như Cloudflare có thể nhận diện header mặc định của Python và gắn cờ lưu lượng của bạn là bot.
- Cạn kiệt Socket: Việc mở 1.000 kết nối riêng lẻ trong một vòng lặp
formà không đóng chúng sẽ khiến hệ điều hành của bạn hết cổng (port) khả dụng.
Cách khắc phục 1: Sử dụng Exponential Backoff
Cách khắc phục thông minh nhất là yêu cầu Python kiên nhẫn hơn. Thay vì thất bại ngay lập tức, hãy sử dụng HTTPAdapter để thử lại yêu cầu với thời gian chờ tăng dần. Cách tiếp cận này giúp xử lý các lỗi 502 hoặc 504 một cách mượt mà.
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def fetch_data(url):
session = requests.Session()
# Thiết lập total=5 và backoff_factor=1 nghĩa là Python sẽ chờ
# 1s, 2s, 4s, 8s, sau đó là 16s giữa các lần thử.
retry_strategy = Retry(
total=5,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["HEAD", "GET", "OPTIONS"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
session.mount("http://", adapter)
try:
response = session.get(url, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.ConnectionError as ce:
print(f"Connection failed: {ce}")
except requests.exceptions.Timeout:
print("The server took too long to respond.")
except requests.exceptions.RequestException as e:
print(f"A general error occurred: {e}")
# Ví dụ sử dụng
data = fetch_data("https://api.example.com/v1/resource")
Cách khắc phục 2: Giả lập trình duyệt thực
Nhiều máy chủ tự động từ chối các yêu cầu tự nhận dạng là python-requests/2.28.1. Bạn có thể vượt qua các bộ lọc cơ bản này bằng cách thêm header User-Agent trông giống như trình duyệt Chrome hoặc Firefox tiêu chuẩn.
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
}
# Điều này làm cho yêu cầu trông như đến từ một máy tính để bàn chạy Windows 10
response = requests.get("https://api.example.com/data", headers=headers)
Cách khắc phục 3: Tái sử dụng kết nối với Session
Việc tạo một kết nối mới cho mỗi yêu cầu đơn lẻ rất tốn kém và dễ gây lỗi. Hãy sử dụng đối tượng requests.Session(). Điều này giúp giữ cho kết nối TCP bên dưới luôn mở cho nhiều yêu cầu đến cùng một máy chủ, giúp giảm đáng kể khả năng xảy ra lỗi handshake.
# Sử dụng một context manager để xử lý việc đóng session tự động
with requests.Session() as session:
for item_id in range(1, 101):
try:
# Tái sử dụng cùng một connection pool cho tất cả 100 yêu cầu
resp = session.get(f"https://api.example.com/items/{item_id}", timeout=5)
process_data(resp.json())
except requests.exceptions.ConnectionError:
print(f"Skipping item {item_id} due to connection failure.")
continue
Cách khắc phục 4: Cấu hình cài đặt Proxy
Trong môi trường doanh nghiệp, lưu lượng của bạn có thể bị chặn bởi tường lửa trừ khi bạn định tuyến nó qua một proxy cụ thể. Bạn có thể xác định các cài đặt này trên toàn bộ script để tránh bị ngắt kết nối.
proxies = {
'http': 'http://proxy.company.com:8080',
'https': 'http://proxy.company.com:1080',
}
requests.get("https://api.example.com", proxies=proxies)
Mẹo nhỏ: Kiểm tra Subnet của bạn
Đôi khi vấn đề không nằm ở mã nguồn của bạn mà là do xung đột định tuyến. Khi tôi khắc phục sự cố mất kết nối liên tục trong môi trường phát triển cục bộ, tôi thường sử dụng Công cụ tính Subnet. Điều này giúp xác minh xem endpoint của API có nằm trên một subnet bị hạn chế, yêu cầu một gateway hoặc lộ trình VPN cụ thể để truy cập hay không.
Cách xác minh việc khắc phục
Đừng chỉ hy vọng nó hoạt động; hãy xem nhật ký (logs). Bạn có thể bật chế độ gỡ lỗi chuyên sâu để xem mọi nỗ lực kết nối và thử lại trong thời gian thực.
import logging
import http.client as http_client
# Bật gỡ lỗi chi tiết cho thư viện urllib3 bên dưới
http_client.HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
Quan sát kết quả đầu ra. Nếu bạn thấy "Retrying (Retry(total=4...))", chiến lược backoff của bạn đang hoạt động tốt. Nếu script thành công ở lần thử thứ ba, bạn đã giảm thiểu thành công một sự cố mạng tạm thời.
Danh sách kiểm tra tóm tắt
- Thiết lập timeout: Luôn sử dụng
timeout=10(hoặc tương tự) để ngăn script bị treo trong nhiều phút. - Đóng các session: Sử dụng context manager (
with) để giải phóng tài nguyên hệ thống. - Tôn trọng máy chủ: Nếu bạn gặp lỗi 429, hãy tăng
backoff_factorlên 2 hoặc 3 để máy chủ có thêm không gian thở.

