REST Countries API chuẩn hóa quốc gia: mã, tiền tệ và bản cũ
REST Countries API chuẩn hóa quốc gia là cách đưa tên nước về mã alpha2, alpha3 và tiền tệ để ghép bảng cho đúng. Bài này viết cho người có cột quốc gia lộn xộn. ProxyVN gọi API thật ngày 05/11/2026 và thấy v3.1 quen thuộc không còn trả dữ liệu, dù HTTP vẫn là 200. Lab chuyển sang bộ dữ liệu mở 250 nước để kiểm phần chuẩn hóa.
REST Countries API chuẩn hóa quốc gia: mục tiêu và phạm vi không lặp
Phần này chốt bài toán, đầu ra và điều đã thay đổi so với các hướng dẫn cũ trên mạng.
Bài toán cần giải
Một bảng đơn hàng có cột quốc gia do nhiều hệ thống đổ về: "Vietnam", "Viet Nam", "VN", "Korea", "Czech Republic". Nhóm phân tích muốn đếm theo nước và đổi theo tiền tệ. Dùng tên làm khóa thì một nước thành nhiều dòng, còn "Korea" không rõ là nước nào. Khóa phải là mã, và tên chỉ để hiển thị.
Đầu ra nghiệm thu
Đầu ra là bảng alpha2, alpha3, mã số, tên chuẩn, các tên thay thế và danh sách tiền tệ. Bảng phải xử lý được nước có nhiều tiền tệ, nơi không có tiền tệ và vùng lãnh thổ không có mã số. Đi kèm là phép kiểm phát hiện API đổi cấu trúc trước khi dữ liệu xấu vào bảng.
Ranh giới với bài IP và bài mua proxy theo nước
Bài này không suy quốc gia từ IP và không nói chuyện chọn proxy theo nước. Nó chỉ xử lý dữ liệu tham chiếu về quốc gia. Việc xác định vị trí từ IP nằm ở bài IP geolocation. Hai bài chỉ gặp nhau ở chỗ cùng dùng mã alpha2 làm khóa.
Giới hạn của lab cần nói ngay
Mọi đường dẫn v2 và v3.1 lab gọi đều không trả dữ liệu. Bản v5 cần tài khoản và khóa, lab không đăng ký tài khoản. Vì vậy các đoạn gọi v5 trong bài là ví dụ theo tài liệu, chưa chạy thành công. Số liệu chuẩn hóa đến từ bộ dữ liệu mở mledoze/countries trên GitHub, là tệp JSON công khai, không phải response của REST Countries v5.
Ai nên đọc
Bài hợp với người đang có code gọi restcountries.com/v3.1 và đột nhiên hỏng, cũng hợp với kỹ sư dữ liệu cần bảng tham chiếu. Ai mới bắt đầu nên đọc phần chọn nguồn trước khi quyết định gọi API hay lưu tệp.
Những gì bài không làm
Bài không bàn về tranh chấp lãnh thổ hay tên gọi chính trị. Bài không đánh giá gói trả phí của REST Countries. Bài cũng không viết lại toàn bộ tài liệu v5, vì tài liệu đó có thể đổi và phần lớn cần khóa để kiểm.
Thuật ngữ dùng trong bài
Alpha2 là mã ISO 3166-1 hai chữ, như VN. Alpha3 là mã ba chữ, như VNM. Mã số là mã ba số, như 704. Cca2, cca3, ccn3 là tên các trường đó trong dữ liệu. Currency là mã tiền tệ ba chữ theo ISO 4217, như VND.
Chuẩn hóa alpha code, tên và currency từ REST Countries có chọn fields với REST Countries API chuẩn hóa quốc gia
Phần này ghi lại chính xác điều lab thấy khi gọi các đường dẫn quen thuộc, vì đây là lý do nhiều script đang chết.
Nguồn tài liệu đã đọc
Lab đọc trang chủ REST Countries, tài liệu tổng quan và trang Legacy API Deprecation. Với dữ liệu thay thế, lab dùng kho mledoze/countries, commit gần nhất ngày 03/11/2026 theo GitHub API.
Gọi đường dẫn cũ
Lab gọi /v3.1/alpha/vn, /v3.1/name/vietnam, /v3.1/all và /v2/alpha/vn, mỗi lượt không theo redirect. Tất cả đều trả HTTP 301 tới files-03.restcountries.com/countries.00/legacy.json, giữ nguyên tham số fields nếu có. Tên quốc gia hay mã trong đường dẫn cũ không ảnh hưởng tới đích đến.
import requests
s = requests.Session()
s.headers["User-Agent"] = "ProxyVN-docs-lab/1.0 (+https://proxy.vn/)"
u = "https://restcountries.com/v3.1/all?fields=cca2,cca3,name,currencies"
r = s.get(u, timeout=60)
print(r.status_code, r.url, [h.status_code for h in r.history], len(r.content))
print(r.json()["success"], r.json()["data"], r.json()["errors"][0]["message"][:60])
Output thật: status 200, URL cuối là tệp legacy.json, history có một 301, thân dài 261 byte. Trường success là False, data là None, và errors báo phiên bản đã ngừng, yêu cầu chuyển sang v5.
Vì sao đây là lỗi im lặng
Requests tự theo redirect, nên r.status_code là 200 và r.ok là True. Code cũ thường viết r.raise_for_status() rồi lặp qua r.json(). Giờ r.json() là một object có ba khóa thay vì danh sách nước. Một pipeline không kiểm cấu trúc sẽ đọc ba khóa như ba nước hoặc ghi đè bảng tham chiếu bằng rỗng. Đó là kịch bản xấu nhất của dữ liệu tham chiếu.
Tài liệu ngừng bản cũ nói gì
Trang deprecation ghi mọi request tới các bản cũ trả cùng một response và không có dữ liệu phía sau. Trang hướng dẫn tạo tài khoản, đổi /v1 tới /v4 trong URL thành /v5, gửi khóa trong header Authorization Bearer, rồi sửa phần đọc response vì tên trường và cấu trúc đã đổi. Trang nói mọi trường cũ vẫn tồn tại ở v5 dưới cấu trúc mới, và cam kết v5 là bản giữ lâu dài.
Gọi v5 không có khóa
Lab gọi https://api.restcountries.com/v5/countries/vn và nhận HTTP 401 với mã authKeyMissing, thông điệp Authorization key required. Nếu gọi nhầm tên miền restcountries.com/v5/countries/vn, lab lại nhận HTTP 200 kèm JSON deprecation. Vậy một lỗi gõ tên miền cũng cho ra cùng dạng lỗi im lặng. Base URL của v5 là api.restcountries.com theo lần gọi này, anh em đối chiếu lại tài liệu khi tích hợp.
Ví dụ gọi v5 theo tài liệu
Khóa đọc từ biến môi trường. Tài liệu ghi xác thực bằng một bearer token, có quota tháng và giới hạn nhịp dùng chung cho mọi API của họ.
import os, requests
k = os.environ["RESTCOUNTRIES_KEY"]
r = requests.get(
"https://api.restcountries.com/v5/countries/vn",
headers={"Authorization": f"Bearer {k}"},
timeout=60,
)
r.raise_for_status()
print(r.json())
Đoạn này chưa chạy thành công vì lab không có khóa. Lab không biết cấu trúc response của v5, nên bài không viết mã đọc trường của v5. Anh em xem mục Field reference trong tài liệu sau khi đăng nhập.
Hàm đọc phải kiểm cấu trúc
Dù dùng phiên bản nào, hàm nạp dữ liệu tham chiếu phải từ chối response lạ. Dưới đây là phép kiểm tối thiểu cho dữ liệu dạng danh sách nước. Với dữ liệu nước, số dòng có khoảng khá hẹp, nên ngưỡng 200 là một lưới an toàn đơn giản.
def check_countries(j):
if isinstance(j, dict) and j.get("success") is False:
raise RuntimeError(j["errors"][0]["message"])
if not isinstance(j, list) or len(j) < 200:
raise RuntimeError(f"cau truc la: {type(j).__name__}")
need = {"cca2", "cca3", "name", "currencies"}
bad = [c for c in j if not need <= set(c)]
if bad:
raise RuntimeError(f"thieu truong: {len(bad)} dong")
return j
Hàm này chạy đúng trên response legacy, báo lỗi ngay ở nhánh đầu. Nó cũng qua được bộ dữ liệu mledoze 250 dòng. Ngưỡng 200 là tự chọn, không phải con số của nhà cung cấp.
Chọn phiên bản endpoint và fields thực sự cần cho REST Countries API chuẩn hóa quốc gia
Dữ liệu quốc gia đổi chậm, nên câu hỏi đầu tiên là có cần gọi API lúc chạy hay không.
Ba lựa chọn nguồn

Chọn nguồn dữ liệu
Nguồn |
Cần khóa |
Điểm mạnh |
Điểm cần cân nhắc |
|---|---|---|---|
REST Countries v5 |
Có |
Nhiều trường, cập nhật theo tài liệu |
Quota, phụ thuộc mạng |
Tệp mledoze/countries |
Không |
Tải một lần, ODbL |
Tự cập nhật, cấu trúc gần v3 |
Bảng ISO tự giữ |
Tùy |
Ít cột, kiểm soát cao |
Tự bảo trì |
Vì sao không nên gọi API cho mỗi dòng
Bảng tham chiếu chỉ có khoảng 250 dòng. Gọi API mỗi lần ghép đơn hàng là thừa, thêm điểm hỏng và tốn quota. Lần ngừng bản cũ cho thấy rủi ro này rất thật: dịch vụ gọi trực tiếp lúc chạy sẽ hỏng ngay, còn dịch vụ dùng tệp đã lưu vẫn chạy bình thường.
Tải một lần, dùng nhiều lần
Cách lab đề xuất là tải bảng tham chiếu vào kho của dự án, ghi ngày tải, kiểm cấu trúc, rồi các job dùng bản đã lưu. Cập nhật theo lịch, ví dụ mỗi quý, có người duyệt thay đổi. Dữ liệu tham chiếu thay đổi ít, nên một lần duyệt rất nhanh.
Trường nào thực sự cần
Bộ mledoze có các trường name, tld, cca2, ccn3, cca3, cioc, independent, status, unMember, currencies, idd, capital, altSpellings, region, subregion, languages, translations, latlng, landlocked, borders, area, flag, demonyms. Bài toán ghép đơn hàng chỉ cần cca2, cca3, ccn3, name, altSpellings, currencies. Bảng càng ít cột thì lần duyệt càng dễ.
Dữ liệu Việt Nam trong bộ mở
Dòng VN có tên thường Vietnam, tên chính thức Socialist Republic of Vietnam, cca3 VNM, ccn3 704, tiền tệ VND với tên Vietnamese đồng và ký hiệu ₫. Trường altSpellings gồm VN, Socialist Republic of Vietnam, Cộng hòa Xã hội chủ nghĩa Việt Nam, Viet Nam.
import json
C = json.load(open("countries.json", encoding="utf-8"))
vn = next(c for c in C if c["cca2"] == "VN")
print(len(C), vn["name"]["common"], vn["cca3"], vn["ccn3"], vn["currencies"])
print(vn["altSpellings"])
Output thật là 250 dòng và các giá trị nêu trên. Tệp lab tải nặng khoảng 1.4 MB, đủ nhỏ để đặt trong kho dự án.
Giấy phép
Tệp LICENSE của kho mledoze/countries là ODC Open Database License, nên dùng lại cần ghi nguồn và có điều kiện với cơ sở dữ liệu phái sinh công khai. REST Countries v5 có điều khoản riêng trên trang của họ. Bảng nội bộ và sản phẩm công khai có nghĩa vụ khác nhau, nên anh em đọc kỹ khi phát hành.
Quota và gói
Tài liệu nói một tài khoản, một quota tháng dùng cho mọi API của họ, có giới hạn nhịp và trạng thái tài khoản. Trang deprecation nhắc khách cũ lưu lượng lớn có thể xin gói miễn phí nâng cao trong thời gian chuyển đổi. Lab không kiểm các con số gói, vì giá và hạn mức có thể đổi.
Khi vẫn nên dùng v5
Nếu dự án cần dữ liệu hơn 90 trường theo tài liệu, như lãnh đạo chính trị hay trường cập nhật thường xuyên, API có lý do tồn tại. Lúc đó vẫn nên đồng bộ về bảng cục bộ theo lịch, thay vì gọi trong đường xử lý chính.
Map alpha2, alpha3 với tên đa ngôn ngữ trong REST Countries API chuẩn hóa quốc gia
Phần này làm phần việc chính: đưa chuỗi người dùng gõ về đúng mã.
Tên không phải khóa bất biến

Tên đổi, mã giữ nguyên
Tên quốc gia đổi theo thời gian và theo ngôn ngữ. Bộ dữ liệu của lab ghi Türkiye là tên thường, Czechia là tên thường. Tìm "Turkey" trong trường name không ra gì. Mã TR và CZ thì giữ nguyên. Vì vậy mọi bảng lâu dài nên dùng alpha2 hoặc alpha3 làm khóa, tên chỉ là nhãn.
Kết quả tìm tên trong lab
Lab tìm một số chuỗi trong name.common, name.official và altSpellings. Bảng dưới là output thật, mô tả những gì phép tìm đơn giản bắt được.
Chuỗi |
Khớp trong tên |
Khớp altSpellings |
Nhận xét |
|---|---|---|---|
Vietnam |
VN |
không |
Ổn |
Viet Nam |
không |
VN |
Cần altSpellings |
Việt Nam |
không |
không |
Không tìm được |
Korea |
KR, KP |
không |
Mơ hồ |
Congo |
CD, CG |
CG |
Mơ hồ |
Guinea |
GN, GW, GQ, PG |
không |
Khớp chuỗi con sai |
Turkey |
không |
không |
Tên đã đổi |
Türkiye |
TR |
không |
Ổn |
Czech Republic |
CZ |
không |
Nằm ở tên chính thức |
Macedonia |
MK |
MK |
Ổn |
Bản dịch không có tiếng Việt
Trường translations của Việt Nam có 24 ngôn ngữ, và lab đếm cả 250 dòng đều không có khóa vie. Bản tiếng Pháp có "Viêt Nam", nhưng không có "Việt Nam". Vì vậy bộ dữ liệu này không đủ để ghép tên tiếng Việt như "Hàn Quốc" hay "Nhật Bản". Dự án Việt Nam cần tự giữ một bảng tên tiếng Việt.
Bảng bí danh của dự án
Cách làm là một tệp CSV hai cột: chuỗi đã chuẩn hóa và mã alpha2. Tệp này do người giữ, nhận mọi biến thể đã gặp trong dữ liệu thật. Chuỗi "Hàn Quốc" trỏ tới KR, "Triều Tiên" trỏ tới KP, "Korea" để trống vì mơ hồ. Bảng tăng dần theo dữ liệu gặp phải.
import unicodedata, csv
def key(s):
s = s.replace("đ", "d").replace("Đ", "D")
t = unicodedata.normalize("NFD", s)
t = "".join(x for x in t if not unicodedata.combining(x))
return " ".join(t.lower().replace(".", " ").split())
ALIAS = {key(a): c for a, c in [("Việt Nam", "VN"), ("Hàn Quốc", "KR"), ("Turkey", "TR")]}
idx = {}
for c in C:
for n in [c["name"]["common"], c["name"]["official"], c["cca2"], c["cca3"], *c["altSpellings"]]:
idx.setdefault(key(n), set()).add(c["cca2"])
def to_alpha2(s):
k = key(s)
if k in ALIAS:
return ALIAS[k], "alias"
m = idx.get(k, set())
if len(m) == 1:
return next(iter(m)), "exact"
return None, "mo_ho" if m else "khong_co"
Hàm dùng khớp nguyên chuỗi, không dùng chuỗi con, nên "Guinea" không kéo theo Papua New Guinea. Bảng ALIAS ưu tiên trước vì do người xác nhận.
Kết quả chạy hàm to_alpha2
Lab chạy hàm trên 24 chuỗi đầu vào. Chỉ mục có 1316 khóa từ 250 dòng, và không khóa nào trỏ về hai nước, nên nhánh mo_ho không xảy ra với bộ dữ liệu này. Bảng dưới gom các kết quả đáng chú ý.
Đầu vào |
Kết quả |
Cách khớp |
Nhận xét |
|---|---|---|---|
Vietnam, vn, VNM |
VN |
exact |
Đúng |
Viet Nam, VIET NAM, Việt Nam |
VN |
alias |
Bỏ dấu nên chung một khóa |
Hàn Quốc |
KR |
alias |
Cần bảng riêng |
Nhật Bản |
không có |
khong_co |
Chưa có trong alias |
Korea |
không có |
khong_co |
Đúng ý, không tự gán |
South Korea, Republic of Korea |
KR |
exact |
Đúng |
Congo |
CG |
exact |
Cần xem lại |
Guinea |
GN |
exact |
Đúng tên nhưng người gõ có thể nhầm |
UK, USA, Holland |
GB, US, NL |
exact |
Nhờ altSpellings |
U.K. |
không có |
khong_co |
Thêm vào alias |
Ivory Coast, Côte d'Ivoire |
CI |
exact |
Đúng |
Khớp đúng chưa chắc là đúng ý
Chuỗi "Congo" khớp nguyên văn tên thường của CG, tức Cộng hòa Congo. Người gõ có thể đang nói về DR Congo, nước lớn hơn nhiều. Hàm không có cách nào biết điều đó. Với Congo, Guinea và các tên dễ nhầm, dự án nên thêm chúng vào danh sách bắt buộc duyệt, dù máy khớp được.
Danh sách tên bắt buộc duyệt
Danh sách này ngắn và viết tay được: Korea, Congo, Guinea, Georgia, Sudan, Dominica, Niger, Samoa. Chuỗi trùng danh sách đi vào hàng duyệt kèm ngữ cảnh của dòng gốc, như tiền tệ, thành phố hay mã điện thoại. Georgia còn trùng tên một bang của Mỹ, nên cột thành phố đi kèm rất hữu ích.
Đếm kết quả trước khi tin
Sau mỗi lần chạy, anh em đếm theo cách khớp: alias, exact, mo_ho, khong_co, tính cả số dòng gốc. Tỷ lệ khong_co cao là dấu hiệu nguồn mới có định dạng lạ. Dòng khong_co xếp theo tần suất, người giữ bảng thêm bí danh từ trên xuống. Vài chục bí danh đầu thường xử lý được phần lớn dữ liệu.
Không dùng chuỗi con
Bảng trên cho thấy tìm chuỗi con "Guinea" ra bốn nước. Fuzzy matching còn nguy hiểm hơn với tên nước, vì "Niger" và "Nigeria", "Dominica" và "Dominican Republic" rất gần nhau. Với chỉ khoảng 250 thực thể, một bảng bí danh viết tay đáng tin hơn thuật toán.
Nhập đã là mã sẵn
Chuỗi "VN" hay "VNM" đi thẳng vào chỉ mục qua cca2, cca3, và hàm key đã bỏ khác biệt chữ hoa thường. Chuỗi "UK" khớp GB qua altSpellings, còn "U.K." có dấu chấm thì trả khong_co vì hàm key đổi dấu chấm thành khoảng trắng. Bảng ALIAS là nơi xử lý ca này, không viết rẽ nhánh trong code.
Tên chính thức dài
Trường official hợp cho văn bản pháp lý, trường common hợp cho giao diện. Bảng lưu cả hai. Hiển thị cho người Việt thì dùng cột tên tiếng Việt của dự án, không dùng bản tiếng Anh rồi để người đọc tự hiểu.
Mã alpha3 ở các nguồn khác
Nhiều bộ dữ liệu thống kê dùng alpha3, như nguồn của Ngân hàng Thế giới. Lưu cả ba mã giúp ghép được với các bài World Bank API và Eurostat API. Các mã nhóm khu vực của các nguồn đó không có trong bảng nước và phải xử lý riêng.
Giữ currency object và trường không có giá trị trong REST Countries API chuẩn hóa quốc gia
Tiền tệ là phần mà giả định "một nước một đồng tiền" sẽ sai.
Trường currencies là object
Trong bộ mở, currencies là object, khóa là mã tiền tệ, giá trị gồm name và symbol. Đây là cấu trúc đúng vì một nơi có thể có nhiều đồng tiền. Lấy phần tử đầu tiên rồi coi đó là đồng tiền của nước là phép đơn giản hóa gây lỗi.
Nước có nhiều tiền tệ
Lab đếm 19 dòng có từ hai tiền tệ trở lên. Ví dụ Bahamas có BSD và USD, Campuchia có KHR và USD, Brunei có BND và SGD, Bhutan có BTN và INR. Tây Sahara có ba mã DZD, MAD, MRU. Thứ tự trong object không cho biết đồng nào là chính, nên chọn đồng đầu tiên là đoán.
Nơi không có tiền tệ
Bốn dòng có currencies rỗng: AQ Nam Cực, BV đảo Bouvet, HM đảo Heard và McDonald, và FM Micronesia. Ba nơi đầu là vùng không có dân cư thường trú, nên rỗng khá dễ hiểu. Micronesia là quốc gia có người ở, nên ô rỗng đáng kiểm lại với nguồn khác. Rỗng trong bảng không có nghĩa là không dùng tiền.
Đồng tiền dùng chung
Đếm theo tiền tệ, EUR xuất hiện ở 38 dòng, USD 20 dòng, XCD và XOF mỗi đồng 8 dòng. Bảng phải cho phép một tiền tệ gắn với nhiều nước. Thiết kế chuẩn là bảng quan hệ nhiều nhiều giữa nước và tiền tệ, thay vì một cột.
rows = []
for c in C:
if not c["currencies"]:
rows.append({"cca2": c["cca2"], "currency": None, "state": "rong"})
for code, v in c["currencies"].items():
rows.append({"cca2": c["cca2"], "currency": code,
"name": v.get("name"), "symbol": v.get("symbol"),
"state": "mot" if len(c["currencies"]) == 1 else "nhieu"})
print(len(rows))
Bảng dài này giữ đủ cả nước nhiều tiền tệ lẫn dòng rỗng. Cột state giúp lọc nhanh các dòng cần người xác nhận khi đổi tỷ giá.
Kết quả bảng dài
Đoạn trên sinh 278 dòng từ 250 nước và vùng lãnh thổ: 227 dòng một tiền tệ, 47 dòng thuộc nơi nhiều tiền tệ, 4 dòng rỗng. Có 160 mã tiền tệ khác nhau. Con số 278 khác 250 chính là lý do không thể lưu tiền tệ trong một cột đơn.
Ghép với tỷ giá
Khi ghép với bảng tỷ giá, anh em dùng mã tiền tệ của giao dịch, không suy từ quốc gia. Đơn hàng ở Campuchia có thể thanh toán USD. Bảng quốc gia chỉ cho biết các đồng tiền có thể gặp. Tỷ giá theo ngày có ở bài Frankfurter trong cùng cụm này.
Ký hiệu tiền tệ không phải khóa
Trường symbol không duy nhất, vì nhiều đồng dùng ký hiệu đô la. Chuỗi "$" trong dữ liệu đơn hàng không đổi được sang mã tiền tệ nếu thiếu ngữ cảnh quốc gia. Khóa luôn là mã ba chữ. Ký hiệu chỉ để hiển thị.
Vùng không có mã số
Dòng XK Kosovo có ccn3 rỗng, cca3 là UNK, status là user-assigned, khác 249 dòng còn lại mang status officially-assigned. XK không phải mã được ISO gán chính thức. Script ép ccn3 sang số sẽ lỗi ở đúng dòng này. Cột mã số phải cho phép rỗng và phải là chuỗi để giữ số 0 đầu như 004.
Phân biệt nước với vùng lãnh thổ
Bộ dữ liệu có 56 dòng không phải độc lập theo trường independent. Đó là các vùng lãnh thổ có mã ISO riêng. Câu "có bao nhiêu quốc gia" không trả lời được bằng cách đếm dòng. Báo cáo nên nói "quốc gia và vùng lãnh thổ theo ISO 3166", tránh gây tranh cãi không cần thiết.
Trường boolean có thể thiếu
Khi đọc independent hay unMember, anh em dùng c.get để không lỗi với dòng thiếu. Giá trị thiếu giữ là None, không đổi thành False. Đây là cùng nguyên tắc với mọi bảng dữ liệu: thiếu khác với sai.
Cache dataset tham chiếu và kiểm thay đổi schema trước cập nhật trong REST Countries API chuẩn hóa quốc gia
Chính sự cố v3.1 là lý do phần này tồn tại.
Quy trình cập nhật an toàn

Nhiều tiền tệ một nước
Quy trình có năm bước. Tải bản mới vào thư mục tạm. Chạy check_countries. So với bản đang dùng theo mã. Người duyệt danh sách thay đổi. Chỉ khi đó mới thay bản chính. Không có bước nào ghi đè trực tiếp.
Phép so hai phiên bản
Hàm so dưới đây cho biết nước nào thêm, bớt hay đổi trường đã chọn. Với dữ liệu tham chiếu, một lần so thường chỉ ra vài dòng.
def diff(old, new, cols=("cca3", "ccn3", "currencies")):
a = {c["cca2"]: c for c in old}
b = {c["cca2"]: c for c in new}
print("them", sorted(set(b) - set(a)), "bot", sorted(set(a) - set(b)))
for k in sorted(set(a) & set(b)):
for col in cols:
if a[k].get(col) != b[k].get(col):
print(k, col, a[k].get(col), "->", b[k].get(col))
Lab chạy hàm trên hai bản giống nhau và không in dòng thay đổi nào. Sau đó lab tự xóa dòng XK và đổi tiền tệ của VN trong bản sao, hàm báo đúng bot XK và dòng VN currencies đổi sang XXX. Thay đổi đó do lab đặt để thử hàm, không phải dữ liệu thật.
Cảnh báo khi nguồn chết
Job đồng bộ phải báo động khi tải thất bại nhưng không làm hỏng job ghép đơn. Vì thế hai việc phải tách rời. Job ghép đọc tệp đã duyệt, không biết gì về mạng. Trong sự cố legacy, job đồng bộ sẽ đỏ, còn báo cáo hằng ngày vẫn chạy đúng.
Lưu kèm metadata
Bên cạnh tệp dữ liệu nên có tệp nhỏ ghi nguồn, URL, ngày tải, hash, số dòng. Khi có người hỏi "mã này lấy từ đâu", câu trả lời có sẵn. Hash còn giúp phát hiện tệp bị sửa tay ngoài quy trình.
import hashlib, json, datetime as D, pathlib
raw = pathlib.Path("countries.json").read_bytes()
meta = {"source": "mledoze/countries", "fetched": D.date.today().isoformat(),
"sha256": hashlib.sha256(raw).hexdigest(), "rows": len(json.loads(raw))}
pathlib.Path("countries.meta.json").write_text(json.dumps(meta, indent=1))
Lab dùng đúng cấu trúc này cho tệp mledoze. Con số rows là 250.
Viết test cho bảng tham chiếu
Bảng tham chiếu cũng cần test như code. Vài assert đơn giản đủ bắt lỗi lớn: alpha2 có đúng hai ký tự, alpha3 đúng ba ký tự, không có mã trùng, VN phải trỏ tới VNM và VND. Lab kiểm trên tệp mledoze thấy 250 alpha2 khác nhau, 250 alpha3 khác nhau, và không có mã alpha2 nào sai độ dài. Test chạy trong vài mili giây nên đặt ngay trong bước nạp.
Giữ số 0 đầu trong mã số
Mã số như 004 của Afghanistan hay 024 của Angola có số 0 đầu. Đọc bằng Excel hoặc pandas không khai báo kiểu thì thành 4 và 24. Khi ghép với nguồn thống kê dùng mã số, các dòng này mất khớp mà không báo lỗi. Cột ccn3 phải khai báo là chuỗi ngay khi đọc tệp.
Quản lý bảng bí danh theo nhóm
Bảng bí danh không nên nằm trong code. Tệp CSV có cột chuỗi, mã, người thêm, ngày thêm giúp xem lại quyết định về sau. Khi nhiều người cùng thêm, kiểm trùng khóa là việc đầu tiên. Một chuỗi trỏ về hai mã nghĩa là cần họp lại, không phải lấy dòng sau cùng.
Proxy có cần không
Bảng tham chiếu tải vài lần một năm, không cần proxy. Quota v5 gắn với khóa, nên đổi IP không tăng thêm gì. Nếu mạng doanh nghiệp bắt buộc đi qua cổng ra, anh em khai báo ở tầng session, chuỗi theo định dạng proxy. Vai trò proxy trong thu thập dữ liệu có ở bài Web Scraping proxy là gì.
Tác vụ khác cần IP theo nước thì anh em có thể mua proxy tại trang chủ ProxyVN.
Khóa phiên bản
Lab chạy Python 3.12.10, requests 2.34.2. Tệp mledoze ở nhánh master ngày 05/11/2026. Để tái lập, anh em ghim theo commit thay vì nhánh. REST Countries v5 có phiên bản trong URL, cấu hình nên ghi rõ.
Đầu ra nghiệm thu bảng alpha2, alpha3, currency và tên đa ngôn ngữ của REST Countries API chuẩn hóa quốc gia
Mục này chốt bảng và danh sách kiểm.
Schema của bảng

Nghiệm thu bảng tham chiếu
Cột |
Ví dụ thật |
Ghi chú |
|---|---|---|
cca2 |
VN |
Khóa chính |
cca3 |
VNM |
Chuỗi |
ccn3 |
704 |
Chuỗi, có thể rỗng |
name_common |
Vietnam |
Nhãn tiếng Anh |
name_official |
Socialist Republic of Vietnam |
Văn bản |
name_vi |
Việt Nam |
Bảng riêng của dự án |
independent |
True |
Có thể thiếu |
currency |
VND |
Bảng quan hệ riêng |
source_sha256 |
giá trị hash |
Từ tệp meta |
Danh sách kiểm
- Cấu trúc: response có kiểu danh sách, đủ trường, trên 200 dòng.
- Khóa: alpha2 duy nhất, không dùng tên làm khóa.
- Mơ hồ: Korea, Congo không tự gán mã.
- Tiền tệ: giữ mọi mã, cho phép rỗng và nhiều mã.
- Mã số: chuỗi, cho phép rỗng ở XK.
- Nguồn: có giấy phép, ngày tải, hash.
Câu kết luận được phép viết
Từ lab có thể viết rằng v2 và v3.1 không còn trả dữ liệu tại ngày 05/11/2026. Lỗi này đi cùng HTTP 200 sau redirect. Bộ dữ liệu mở có 19 dòng nhiều tiền tệ, không có bản dịch tiếng Việt. Không nên viết gì về cấu trúc response của v5, vì lab chưa thấy response đó.
Những gì lab chưa chạy
Lab chưa nhận được response thành công từ v5, chưa kiểm quota, chưa so v5 với mledoze. Lab chưa kiểm dữ liệu currencies của Micronesia với nguồn khác. Hàm diff mới chạy trên một thay đổi do lab tự đặt, chưa gặp hai phiên bản lịch sử thật.
Dữ liệu tham chiếu nên nằm trong kho của dự án, có hash và ngày tải. Dịch vụ bên ngoài đổi cấu trúc thì job đồng bộ đỏ, còn báo cáo vẫn chạy.
REST Countries API chuẩn hóa quốc gia: câu hỏi thường gặp
Các câu dưới đây gom những điểm hay bị hỏi.
Vì sao code gọi v3.1 của tôi trả rỗng?
Vì bản cũ đã ngừng. Request được chuyển tới một tệp JSON báo lỗi, HTTP vẫn 200, không có dữ liệu.
Sang v5 chỉ đổi URL là xong?
Không. Cần khóa trong header Authorization và cần sửa phần đọc vì cấu trúc đã đổi theo tài liệu.
Có cách nào không cần khóa?
Dùng tệp dữ liệu mở như mledoze/countries, chấp nhận tự cập nhật và tuân theo giấy phép ODbL.
Nên dùng alpha2 hay alpha3?
Chọn một làm khóa chính, thường là alpha2, và lưu cả hai để ghép nguồn khác.
Tìm tên tiếng Việt thế nào?
Bộ lab dùng không có bản dịch tiếng Việt. Hãy giữ bảng bí danh của dự án.
Vì sao Kosovo có mã lạ?
XK là mã do người dùng gán, không phải mã ISO chính thức, nên ccn3 rỗng.
Một nước có nhiều tiền tệ thì chọn đồng nào?
Không chọn theo bảng quốc gia. Dùng mã tiền tệ của chính giao dịch.
Có cần gọi API mỗi khi xử lý dữ liệu không?
Không. Bảng tham chiếu nên lưu cục bộ và đồng bộ theo lịch.
REST Countries API chuẩn hóa quốc gia và những điều cần nhớ
REST Countries API chuẩn hóa quốc gia cần được dùng qua lớp kiểm cấu trúc, vì bản cũ chết trong im lặng với HTTP 200. Khóa bằng mã, giữ mọi tiền tệ, lưu bảng cục bộ có hash. Proxy.vn - Nhà cung cấp dịch vụ proxy chất lượng hàng đầu Việt Nam khuyên anh em duyệt thay đổi trước khi cập nhật REST Countries API chuẩn hóa quốc gia.