Menu

HTTP request trong Python: dùng thư viện requests (GET, POST, JSON)

Cách gửi HTTP request trong Python với thư viện requests: GET, POST, query parameter, header, body JSON và xử lý lỗi.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Lấy dữ liệu từ Internet chỉ với vài dòng

Phần lớn chương trình Python thực tế đến lúc nào đó đều cần giao tiếp qua mạng: một REST API, một dịch vụ thời tiết, một endpoint của GitHub, một lượt tải tệp. Thư viện chuẩn làm được việc này (qua urllib), nhưng công cụ mặc định trong thế giới Python là một thư viện bên thứ ba tên requests. API của nó thân thiện hơn nhiều, đáng để bạn chạy một lệnh pip install duy nhất.

pip install requests

Nếu bạn chưa làm việc bên trong một môi trường ảo (virtual environment), hãy tạo một cái trước: nó giữ gói cài đặt này trong phạm vi dự án của bạn.

GET request đầu tiên

Ba bước: gọi get, kiểm tra mã trạng thái, xem phần body. response.text là body của response dưới dạng chuỗi. Ở đây nó bị cắt bớt vì toàn bộ JSON khá dài.

Những mã trạng thái nên nhận ra ngay, ở mức cheat sheet:

  • 200: OK, mọi thứ hoạt động.
  • 201: Created (response thường gặp của POST).
  • 301 / 302: chuyển hướng; mặc định requests tự động đi theo.
  • 400: request sai; có gì đó trong dữ liệu bạn gửi bị sai.
  • 401 / 403: chưa xác thực / không có quyền.
  • 404: tài nguyên không tồn tại.
  • 429: bị giới hạn tần suất; hãy chậm lại.
  • 500: lỗi máy chủ.

Phân tích response JSON

Khi endpoint trả về JSON, hãy gọi .json() trên response: nó phân tích body và trả cho bạn một dict (hoặc list):

Về bản chất, .json() giống hệt json.loads(response.text), chỉ là cách viết tắt cho trường hợp phổ biến.

Gửi query parameter

Đừng tự tay ghép ?key=value&... vào URL. Hãy truyền một dict vào params=:

requests tự mã hóa URL cho bạn: dấu cách, ký tự đặc biệt, Unicode đều được xử lý an toàn.

URL thật sự đã được gửi đi nằm trong response.url, rất tiện khi gỡ lỗi.

POST request với body JSON

Để gửi dữ liệu, như tạo tài nguyên, gửi biểu mẫu hay gọi các endpoint làm thay đổi dữ liệu, hãy dùng requests.post:

import requests

payload = {
    "title": "Docs update",
    "body": "Added HTTP requests page.",
    "labels": ["docs"],
}

response = requests.post(
    "https://api.example.com/issues",
    json=payload,
    headers={"Authorization": "Bearer YOUR_TOKEN"},
)

print(response.status_code)
print(response.json())

Đối số json= làm hai việc: chuyển payload thành JSON và đặt Content-Type: application/json. Bạn có thể tự làm cả hai bằng data=json.dumps(payload) cùng một header ghi rõ, nhưng json= là cách viết tắt chuẩn mực.

Với dữ liệu dạng form-encoded (kiểu mà một biểu mẫu HTML cổ điển gửi đi), hãy dùng data=:

requests.post("https://example.com/login", data={"user": "rosa", "password": "..."})

Truyền mọi header tùy chỉnh dưới dạng một dict:

import requests

response = requests.get(
    "https://api.example.com/profile",
    headers={
        "Authorization": "Bearer abc123",
        "User-Agent": "my-tool/1.0",
    },
)

Hầu hết API cần header Authorization để xác thực. Kiểu xác thực cụ thể (Bearer, Basic, Token) được ghi trong tài liệu của họ.

Timeout là bắt buộc

Mặc định, requests sẽ chờ response mãi mãi. Trong chương trình thực tế, điều đó biến một máy chủ chập chờn thành một script bị treo. Luôn truyền timeout:

import requests

try:
    response = requests.get("https://api.example.com/slow", timeout=5)
except requests.Timeout:
    print("Server took too long.")

Con số tính bằng giây. timeout=5 nghĩa là "bỏ cuộc nếu sau 5 giây vẫn chưa có response." Bạn có thể truyền một tuple (connect_timeout, read_timeout) để kiểm soát chi tiết hơn.

Xử lý lỗi

Có hai loại sự cố có thể xảy ra:

  1. Lỗi ở tầng HTTP (4xx, 5xx): máy chủ đã phản hồi, nhưng response là một lỗi. response.status_code cho bạn biết điều đó.
  2. Sự cố ở tầng mạng: timeout, lỗi DNS, không kết nối được tới máy chủ. Những sự cố này ném ra ngoại lệ.

Cách viết chuẩn kết hợp cả hai:

raise_for_status() không làm gì với response 2xx và ném ngoại lệ trong các trường hợp còn lại. RequestException là lớp cơ sở của mọi lỗi mà requests ném ra, chỉ cần bắt một lần cho "bất cứ trục trặc nào khi giao tiếp với endpoint này."

Tải một tệp

Với các tệp nhị phân lớn, hãy tải theo dạng luồng (stream) để response không nằm hết trong bộ nhớ:

import requests

url = "https://example.com/large.zip"

with requests.get(url, stream=True, timeout=30) as r:
    r.raise_for_status()
    with open("large.zip", "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)

stream=True bảo requests đừng tải trước toàn bộ body. iter_content(chunk_size=...) trả về body theo từng khúc, và bạn ghi thẳng từng khúc xuống ổ đĩa.

Session: dùng lại kết nối và giá trị mặc định

Nếu bạn sẽ gửi nhiều request tới cùng một dịch vụ, hãy dùng Session. Nó dùng lại kết nối TCP bên dưới (nhanh hơn) và cho phép bạn đặt các giá trị mặc định một lần:

import requests

session = requests.Session()
session.headers.update({"Authorization": "Bearer abc123"})

# Every request through this session carries the header.
a = session.get("https://api.example.com/users/1")
b = session.get("https://api.example.com/users/2")
c = session.post("https://api.example.com/users", json={"name": "Rosa"})

Với các script gọi cùng một API hàng chục lần, session giúp tăng tốc đáng kể.

Một ví dụ thực tế: client GitHub nhỏ

Lấy bản phát hành mới nhất của một repository:

import requests

def latest_release(owner, repo):
    url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    data = response.json()
    return {
        "tag": data["tag_name"],
        "name": data["name"],
        "published": data["published_at"],
        "url": data["html_url"],
    }

release = latest_release("python", "cpython")
print(release)

Chưa tới mười lăm dòng: tạo URL, gửi request, kiểm tra lỗi, lấy ra các trường bạn cần. Đó là hình dạng của phần lớn các API client mà bạn sẽ viết.

Còn urllib thì sao?

urllib.request của thư viện chuẩn làm được mọi thứ requests làm được, nhưng tốn nhiều dòng hơn và kém tiện hơn. Nếu bạn tuyệt đối không thể thêm thư viện phụ thuộc, nó vẫn ở đó:

import json
import urllib.request

with urllib.request.urlopen("https://api.github.com/repos/python/cpython") as r:
    data = json.loads(r.read().decode("utf-8"))

print(data["name"])

Với bất cứ thứ gì hơn một script nhanh, requests (hoặc httpx nếu bạn cần async) đáng để cài.

Vài thói quen nên có

  • Luôn đặt timeout. Không có ngoại lệ.
  • Dùng raise_for_status() trong các script mong đợi thành công: nó biến một response xấu thành một ngoại lệ rõ ràng.
  • Truyền dict vào params= và json=, đừng tự ghép chuỗi.
  • Gom các lời gọi liên quan vào một Session khi bạn gọi cùng một API nhiều lần.
  • Ghi log response.status_code và response.text khi gỡ lỗi: phần body thường cho biết chính xác máy chủ không hài lòng ở điểm nào.

Tiếp theo: ngày và giờ

Với requests trong bộ công cụ, bạn có thể giao tiếp với bất kỳ web API hiện đại nào, tải tệp và xây dựng những tích hợp nhỏ giữa các dịch vụ. Kết hợp với các trang về JSON và CSV trước đó, giờ bạn đã có trọn vòng "lấy dữ liệu, đọc nó, xử lý nó, ghi kết quả ra": hình dạng của rất nhiều script Python thực tế. Tuy nhiên, phần lớn dữ liệu đó đều có dấu thời gian, và trang tiếp theo sẽ nói về cách Python biểu diễn ngày, giờ và những cái bẫy múi giờ nên tránh.

Câu hỏi thường gặp

Làm sao để gửi một HTTP request trong Python?

Cài thư viện requests bằng pip install requests, rồi gọi requests.get(url) cho GET hoặc requests.post(url, json=...) cho POST. Đối tượng response có .status_code, .text, .json() và .headers. Ví dụ: r = requests.get('https://api.example.com/users/1').

Nên dùng requests hay urllib?

Dùng requests cho mọi thứ bạn tự viết, vì API của nó thân thiện hơn hẳn. urllib có sẵn và đủ dùng khi không thể thêm thư viện phụ thuộc, nhưng cần nhiều code hơn cho những việc như body JSON và session. Trong môi trường production, nhiều nhóm còn dùng httpx (một thư viện tương thích với requests và hỗ trợ async).

Làm sao để gửi JSON trong một POST request bằng Python?

Truyền json={'key': 'value'} vào requests.post(...). requests sẽ chuyển dict thành JSON và tự đặt Content-Type: application/json cho bạn. Đừng truyền cả data= lẫn json=, hãy chọn một.

Làm sao để xử lý lỗi với thư viện requests?

Kiểm tra response.status_code (200 nghĩa là thành công), hoặc gọi response.raise_for_status() để ném ngoại lệ khi gặp mã 4xx/5xx. Các sự cố ở tầng mạng (timeout, lỗi DNS) sẽ ném ra các lớp con của requests.RequestException; bắt lớp này để xử lý cả hai trường hợp.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu