
requests trong Python: gọi API và đọc JSON
AIDev TeamTham vấn chuyên môn: Nguyễn Việt Chung
Đăng 9 phút đọc
Mục lục · 20
requests là library giúp Python gửi request tới một API và nhận dữ liệu JSON chỉ với vài dòng code. Mình gọi API cửa hàng mẫu Tiệm Mây trên Python 3.15.1: lọc, phân trang, gửi khóa API, tái hiện lỗi 401, timeout, JSONDecodeError, rồi đo thấy Session nhanh gấp đôi khi gọi 30 lần.
Muốn gửi request từ Python tới một API (giao diện để chương trình này lấy dữ liệu của chương trình khác qua Internet), cách quen thuộc nhất là dùng library requests: cài bằng pip install requests, gọi requests.get(url, timeout=10) rồi đọc kết quả bằng .json(). JSON là dạng chữ ghi dữ liệu theo cặp khóa và giá trị, rất giống dict. Ba dòng là có dữ liệu dạng dict của Python. Phần còn lại của bài là những chỗ người mới hay vấp khi viết request Python: truyền tham số, gửi khóa API, đọc mã lỗi và đặt timeout.
Mình chạy mọi ví dụ ngày 26/10/2026 trên một máy AIDev Cloud chạy Ubuntu 24.04, Python 3.15.1 và requests 2.34.2, gọi tới API thực hành của AIDev Lab. Đây là API công khai với dữ liệu cửa hàng mẫu Tiệm Mây, ai cũng gọi được mà không cần đăng ký. Mấy điều mình thấy sau khi chạy:
Server trả mã lỗi 401 hay 404 thì
requestskhông tự báo lỗi. Bạn phải gọiraise_for_status().Không đặt
timeoutthì một server treo có thể làm chương trình của bạn chờ mãi.Gọi 30 lần liên tiếp qua
requests.Sessionmất 1,96 giây, gọi bằngrequests.gettừng lần mất 4,24 giây.
Cài requests và gửi request Python đầu tiên thế nào?
requests không có sẵn trong Python, nên bạn cài vào môi trường ảo của project trước:
pip install requests
Endpoint (một địa chỉ cụ thể của API) /k2/api/san-pham trả danh sách sản phẩm. File b1_get.py:
import requests
URL = "https://lab.aidev.edu.vn/k2/api/san-pham"
r = requests.get(URL, timeout=10)
print(r.status_code)
print(r.headers["Content-Type"])
du_lieu = r.json()
print(type(du_lieu), list(du_lieu.keys()))
print("Tổng sản phẩm:", du_lieu["tong"], "| số trang:", du_lieu["so_trang"])
print(du_lieu["du_lieu"][0])
requests.get trả về một object Response. status_code là mã trạng thái HTTP: 200 nghĩa là thành công. Header (phần thông tin đi kèm phản hồi) Content-Type cho biết server gửi về JSON, và r.json() đổi chuỗi JSON đó thành dict:

API có 120 sản phẩm nhưng mỗi lần chỉ trả một trang, mặc định 12 sản phẩm, nên có 10 trang. Nhiều API công khai phân trang như vậy để mỗi lần trả về không quá nặng.
Truyền tham số cho request bằng params
Bạn có thể tự ghép ?danh_muc=...&q=... vào cuối URL, nhưng chữ có dấu và khoảng trắng phải mã hóa đúng thì server mới hiểu. Đưa tham số vào params dưới dạng dict thì requests tự làm việc này:
tham_so = {"danh_muc": "Thời trang", "q": "áo khoác", "so_dong": 5}
r = requests.get(URL, params=tham_so, timeout=10)
print(r.url)
Dòng print(r.url) cho thấy URL thật đã gửi đi, trong đó "Thời trang" thành Th%E1%BB%9Di+trang; file b2_params.py in thêm các sản phẩm khớp:

Lấy hết dữ liệu khi API phân trang
Để lấy đủ 120 sản phẩm, mình gọi lần lượt từng trang với so_dong lớn nhất API cho phép (50), dừng khi tới trang cuối mà API báo trong trường so_trang:
tat_ca, trang = [], 1
while True:
r = requests.get(URL, params={"trang": trang, "so_dong": 50}, timeout=10)
r.raise_for_status()
goi = r.json()
tat_ca.extend(goi["du_lieu"])
if trang >= goi["so_trang"]:
break
trang += 1
File b3_phan_trang.py của mình in thêm số sản phẩm nhận được ở mỗi trang và tổng thời gian:

Ba trang mất 0,38 giây. Tài liệu API của Lab đề nghị nghỉ 0,5 đến 1 giây giữa các trang. Mình chỉ gọi ba trang nên bỏ qua, nhưng khi lấy hàng chục trang trở lên, bạn thêm time.sleep(0.5) trước dòng trang += 1 (nhớ import time ở đầu file) để không dồn tải lên server.
Gửi khóa API trong header ra sao?
Nhiều API yêu cầu khóa (API key) để biết ai đang gọi. Endpoint đơn hàng /k2/api/don-hang của Lab cần khóa trong header X-API-Key, và tài liệu API cho sẵn một khóa thực hành công khai là aidev-k2-demo:
URL_DON_HANG = "https://lab.aidev.edu.vn/k2/api/don-hang"
r = requests.get(URL_DON_HANG, params={"so_dong": 2, "kenh": "Website"},
headers={"X-API-Key": "aidev-k2-demo"}, timeout=10)
Mình gọi hai lần, lần đầu không gửi khóa, lần sau có khóa và lọc đơn của kênh Website:

Khóa demo chỉ để học. Với khóa thật của bạn (khóa OpenAI, khóa Google…), đừng ghi thẳng vào code mà đọc từ biến môi trường bằng os.environ["TEN_BIEN"], để khóa không lọt lên GitHub cùng code.
Request Python trả 401, 404 thì hiểu thế nào?
Một điểm khiến người mới bối rối: lần gọi thiếu khóa ở mục khóa API trả 401 mà Python không báo lỗi gì. requests coi việc server trả lời là đã thành công, kể cả khi câu trả lời là "không cho phép". Gọi r.raise_for_status() thì mã 4xx và 5xx biến thành exception (lỗi Python dừng chương trình) HTTPError. Mình gom thêm ba lỗi khác hay gặp: tham số sai (400), dùng POST trong khi API chỉ nhận GET (405), và gõ sai tên endpoint (404):

Mã | Nghĩa | Lần gặp trong bài | Cách sửa |
|---|---|---|---|
200 | thành công | mọi lần gọi đúng | |
400 | tham số sai |
| đọc tài liệu API về giới hạn tham số |
401 | chưa xác thực | gọi đơn hàng không gửi khóa | thêm khóa vào header |
404 | không có địa chỉ này | gõ sai tên endpoint | kiểm tra lại URL |
405 | sai phương thức |
| đổi sang |
API của Lab trả lý do lỗi bằng tiếng Việt trong trường loi, nên in r.json() khi gặp lỗi giúp bạn sửa nhanh. API khác có thể trả HTML hoặc không trả gì, khi đó bạn xem r.text.
Vì sao luôn phải đặt timeout?
Mặc định requests không có giới hạn thời gian chờ. Theo tài liệu requests, gần như mọi request trong code chạy thật nên có timeout, nếu không chương trình có thể treo không biết bao lâu. Mình cố tình đặt các mức timeout rất nhỏ để xem requests báo gì:

Kết quả có một chỗ bất ngờ. Ở mức 0,001 và 0,01 giây, lỗi không phải Timeout mà là ConnectionError với [Errno 101] Network is unreachable. Mình đoán là khi bản IPv4 không kịp kết nối, library thử tiếp địa chỉ IPv6, mà máy Cloud không có mạng IPv6. Ở mức 0,05 giây mới thấy ReadTimeout đúng nghĩa, còn từ 0,2 giây trở lên request chạy bình thường. Bài học rút ra: lỗi mạng có nhiều dạng, nên khi bắt lỗi bạn dùng lớp chung requests.RequestException như hàm ở mục "Gom lại" phía dưới.
Lỗi thường gặp khi gọi API bằng Python
JSONDecodeError khi server trả HTML
r.json() chỉ chạy khi nội dung là JSON. Gọi nhầm một trang web thường như /k2/tiem-may/ thì mã vẫn là 200 nhưng nội dung là HTML, và dòng r.json() báo:

requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
"line 1 column 1" nghĩa là ngay ký tự đầu tiên đã không phải JSON. Trước khi gọi .json(), bạn kiểm tra r.headers["Content-Type"] có chứa application/json không, hoặc in r.text[:200] để xem server thật sự trả gì.
403 Forbidden khi dùng urllib, requests thì chạy
urllib, module có sẵn khi cài Python (thuộc standard library), cũng gửi được request. Trang Tiệm Mây chặn User-Agent (chuỗi giới thiệu chương trình đang gọi) mặc định của urllib. requests gửi User-Agent python-requests/2.34.2 nên vẫn qua:
urllib: 403 403 Forbidden: hãy gửi User-Agent riêng của bạn (bài 5.4).
requests: 200 | User-Agent: python-requests/2.34.2
Nhiều trang khác cũng chặn kiểu này. Cách lịch sự là tự đặt User-Agent có tên chương trình và cách liên hệ, ví dụ headers={"User-Agent": "bao-cao-tiem-may/1.0 (email@cua-ban.vn)"}.

Gọi API nhiều lần: dùng Session cho nhanh
Mỗi lần requests.get mở một kết nối HTTPS mới, tốn thời gian bắt tay mã hóa với server. requests.Session giữ kết nối đó để dùng lại cho các lần sau. Mình gọi 30 lần liên tiếp theo hai cách, năm vòng xen kẽ:
with requests.Session() as s:
for i in range(30):
s.get(URL, params={"trang": i % 10 + 1}, timeout=10).raise_for_status()

Cách gọi | 30 lần (trung vị) | Mỗi lần | Vòng nhanh nhất / chậm nhất |
|---|---|---|---|
| 4,24 giây | 141 ms | 4,02 / 4,41 giây |
| 1,96 giây | 65 ms | 1,02 / 2,17 giây |
Session nhanh hơn khoảng 2,2 lần trong lần đo này. Con số cụ thể phụ thuộc khoảng cách mạng tới server, nhưng phần tiết kiệm được nằm ở bước mở kết nối. Gọi một hai lần thì requests.get là đủ, còn khi viết request Python trong vòng lặp gọi hàng chục lần trở lên thì nên dùng Session.
Gom lại: một hàm gọi API an toàn
Ghép các phần trên lại, mình có một hàm vừa có timeout, vừa kiểm tra mã lỗi, vừa không làm chương trình dừng khi mạng có sự cố:
import requests
def lay_san_pham(tu_khoa):
try:
r = requests.get("https://lab.aidev.edu.vn/k2/api/san-pham",
params={"q": tu_khoa}, timeout=10)
r.raise_for_status()
return r.json()["du_lieu"]
except requests.RequestException as loi:
print("Gọi API lỗi:", loi)
return []
print(len(lay_san_pham("tai nghe")), "sản phẩm")
Chạy thật thì hàm in 3 sản phẩm. requests.RequestException là lớp cha của HTTPError, ConnectionError, Timeout và cả JSONDecodeError của requests, nên một khối except bắt được mọi lỗi đã gặp trong bài.
Khi nào không cần requests?
Nếu API có library Python chính thức, như openai hay google-genai, bạn dùng library đó, vì library chính thức thường đã lo sẵn phần xác thực và thử lại khi lỗi. Khi cần gọi hàng trăm request cùng lúc, requests gửi lần lượt từng request nên sẽ chậm, lúc đó bạn cần một library chạy bất đồng bộ (gửi nhiều request mà không chờ nhau). Còn nếu chỉ tải một file CSV công khai về pandas, pd.read_csv(url) đọc thẳng từ link, không cần viết request riêng.
Câu hỏi thường gặp
request và requests trong Python khác nhau thế nào?
"Request" là một yêu cầu gửi tới server. requests (có chữ s) là tên library Python dùng để gửi yêu cầu đó, nên bạn viết import requests. Gõ import request sẽ báo ModuleNotFoundError.
requests có sẵn trong Python không?
Không. Bạn cài bằng pip install requests. Standard library có urllib.request làm được việc tương tự, nhưng code dài hơn và, như mục lỗi 403 Forbidden, User-Agent mặc định dễ bị chặn hơn.
Gửi dữ liệu lên API bằng POST thế nào?
Dùng requests.post(url, json=du_lieu, timeout=10), trong đó du_lieu là dict. requests tự đổi dict thành JSON và gắn header Content-Type: application/json. API của Lab chỉ nhận GET nên mình không chạy thử POST trong bài, gọi POST vào đó sẽ nhận lỗi 405 như bảng mã lỗi.
Làm sao đưa dữ liệu API vào pandas?
Danh sách dict trong trường du_lieu đưa thẳng vào pd.DataFrame(tat_ca) là thành bảng, mỗi khóa thành một cột. Bài pandas là gì có 10 dòng code đầu với chính dữ liệu Tiệm Mây.
Đọc tiếp
Học Python từ số 0: lộ trình tám chặng, trong đó chặng module và library là lúc bạn bắt đầu cài library ngoài như requests.
Class và OOP trong Python:
ResponsevàSessioncủa requests đều là class, đọc bài này để hiểur.json()hays.get()gọi cái gì.pandas là gì: bước tiếp theo sau khi đã lấy được dữ liệu.
Gọi API, đọc JSON rồi đưa vào bảng là một phần của khóa Phân tích dữ liệu với Python trong lộ trình AI Engineer từ số 0. API Tiệm Mây trong bài này chính là API thực hành của các bài lấy dữ liệu từ web trong khóa đó, có bài chấm tự động đi kèm.
Mình chạy ngày 26/10/2026 trên máy AIDev Cloud: Ubuntu 24.04.4 LTS, Intel Xeon E5-2696 v4, Python 3.15.1 trong môi trường ảo venv, requests 2.34.2, gọi tới lab.aidev.edu.vn/k2/api.
Chuyên mục
Học PythonTham vấn chuyên môn
Nhà sáng lập AIDev và VietPlatform · Thạc sĩ AI MSE
- Tốt nghiệp thủ khoa xuất sắc Thạc sĩ AI MSE.
- Đồng sáng lập, CTO E-University, SVOnline: số hóa 10+ đại học, 40.000+ sinh viên.
- Xây hệ thống AI cho doanh nghiệp tại VietPlatform.


