OpenAQ API v3 đo chất lượng khí: sensor, đơn vị và múi giờ
OpenAQ API v3 đo chất lượng khí là cách lấy số đo bụi mịn, ozone hay NO2 theo từng trạm và từng cảm biến. Bài này viết cho người dựng bảng dữ liệu cần giữ đúng đơn vị, đúng giờ và đúng nguồn. ProxyVN đã gọi API không khóa, nhận lỗi 401, rồi đọc kho lưu trữ công khai của OpenAQ để kiểm dữ liệu Hà Nội và một ngày đổi giờ tại Mỹ.
OpenAQ API v3 đo chất lượng khí: 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à những việc bài không làm, để anh em biết nên đọc tới đâu.
Bài toán cần giải
Một nhóm làm báo cáo môi trường muốn có bảng PM2.5 theo giờ cho vài trạm, kèm cả ozone và NO2 nếu trạm có đo. Họ tải số về, cộng trung bình theo ngày rồi vẽ biểu đồ. Tới lúc đối chiếu thì số ngày lệch, đơn vị lẫn lộn giữa ppm và µg/m³, còn một số người đọc lại hiểu con số đó là AQI. Các lỗi này không nằm ở API, chúng nằm ở khâu ghi và đọc dữ liệu.
Đầu ra nghiệm thu
Đầu ra là một dataset gồm các cột location, sensor, parameter, unit, datetime và value. Cột thời gian có cả giờ địa phương kèm độ lệch lẫn giờ UTC. Đi kèm là phép kiểm đổi múi giờ, phép đếm giờ thiếu và ghi chú nguồn dữ liệu. Bảng này phải đọc lại được sau vài tháng mà không cần hỏi người làm.
Ranh giới với các bài dữ liệu thời tiết
Bài này chỉ nói về số đo nồng độ chất ô nhiễm từ cảm biến. Nó khác bài dữ liệu thời tiết của NOAA, nơi biến là nhiệt độ hay lượng mưa theo trạm khí tượng. Nó cũng khác bài lấy lịch sử thời tiết theo lưới mô hình. Nếu việc của anh em là tra nhiệt độ, các bài đó hợp hơn.
Giới hạn của lab cần nói trước
Lab không có khóa API của OpenAQ, nên phần response JSON của API v3 trong bài lấy từ tài liệu, không phải từ lần gọi thật. Lab chỉ gọi được tới mức nhận lỗi 401. Số liệu thật trong bài đến từ kho lưu trữ công khai của OpenAQ trên AWS, cũng là dữ liệu của cùng hệ thống. Mọi con số trong bảng là output của lần đọc ngày 05/11/2026.
Ai nên đọc
Bài hợp với người viết nội dung môi trường, kỹ sư dữ liệu dựng dashboard, sinh viên làm đề tài về không khí. Người đã quen OpenAQ có thể nhảy thẳng tới phần múi giờ và phần AQI. Người mới nên đọc theo thứ tự vì các khái niệm location, sensor, parameter nối vào nhau.
Những gì bài không làm
Bài không đánh giá chất lượng không khí của Hà Nội hay bất kỳ thành phố nào. Bài không đưa khuyến cáo sức khỏe. Ba ngày dữ liệu của lab chỉ dùng để minh họa cách xử lý, không đại diện cho cả mùa hay cả năm.
Thuật ngữ dùng trong bài
Location là một điểm đo, có tọa độ và tên. Sensor là một cảm biến tại điểm đó, đo một parameter. Parameter là chất được đo như pm25, o3, no2. Measurement là một giá trị do nguồn gốc báo về. Aggregate là giá trị đã được tính lại theo giờ, ngày hay năm. Kết luận phần này gọn thôi: một location có nhiều sensor, và khóa dữ liệu đúng nằm ở sensor.
Thu measurement theo location, sensor và đơn vị với OpenAQ API v3 đo chất lượng khí
Phần này đi từ lời gọi đầu tiên tới cách tổ chức khóa dữ liệu, vì đây là chỗ quyết định bảng về sau có ghép được hay không.
Nguồn tài liệu đã đọc
Lab đọc trang Measurements của OpenAQ cho các resource giá trị, trang API key cho cách xác thực và trang rate limits cho giới hạn gọi. Phần kho lưu trữ dựa trên tài liệu Open Data on AWS của OpenAQ. Bốn trang này đều trả HTTP 200 trong ngày lab chạy.
Gọi không khóa thì nhận gì
Lab gọi /v3/locations/2178 không có header. API trả HTTP 401 với thông điệp yêu cầu khóa hợp lệ trong header X-API-Key. Lab gọi lại với một chuỗi khóa bịa, API vẫn trả 401 nhưng thân lỗi đổi thành Invalid credentials. Hai thông điệp khác nhau này có ích khi chẩn đoán: một bên là quên header, một bên là khóa sai.
import requests
s = requests.Session()
s.headers["User-Agent"] = "ProxyVN-docs-lab/1.0 (+https://proxy.vn/)"
u = "https://api.openaq.org/v3/locations/2178"
r = s.get(u, timeout=60)
print(r.status_code, r.text[:90])
r = s.get(u, headers={"X-API-Key": "khoa-bia-cho-lab"}, timeout=60)
print(r.status_code, r.text[:90])
Output thật của đoạn trên là hai dòng 401. Dòng đầu nhắc header X-API-Key, dòng sau là detail Invalid credentials. Đoạn này cũng là phép thử nhanh khi anh em nghi biến môi trường chưa nạp đúng khóa.
Cách lấy khóa và giữ khóa
Tài liệu cho biết khóa được cấp sau khi đăng ký ở OpenAQ Explorer và quản lý trong trang tài khoản. Tài liệu dặn giữ khóa như mật khẩu. Trong code, anh em đọc khóa từ biến môi trường, không ghi vào tệp script hay notebook đem chia sẻ.
import os, requests
key = os.environ["OPENAQ_API_KEY"]
r = requests.get(
"https://api.openaq.org/v3/sensors/21632/measurements",
headers={"X-API-Key": key},
params={"limit": 100, "page": 1},
timeout=60,
)
r.raise_for_status()
Đoạn trên là ví dụ triển khai theo tài liệu, lab chưa chạy được vì không có khóa. Sensor 21632 là cảm biến PM2.5 mà lab thấy trong tệp lưu trữ của điểm đo Hanoi-7441. Khi có khóa, anh em chạy thử đoạn này trước để xác nhận đường dẫn và tham số ở phiên bản hiện hành.
Giới hạn gọi của gói chung
Trang rate limits ghi mức dùng chung là 60 lượt mỗi phút và 2000 lượt mỗi giờ. Vượt ngưỡng thì API trả 429, và tài liệu khuyên chờ tới kỳ giới hạn kế tiếp. Vi phạm lặp lại có thể dẫn tới khóa quyền truy cập. Vì giới hạn gắn với khóa, script nên gọi tuần tự và đọc các header x-ratelimit trong response.
Vì sao khóa dữ liệu là sensor
Lab đọc ba ngày của điểm đo 2178 tại New Mexico và thấy tám sensor. Mỗi sensor đo một parameter riêng, có id riêng. Nếu chỉ giữ location và thời gian, các dòng của tám chất sẽ dính vào nhau. Khóa đúng của một dòng là sensors_id cộng thời điểm.
Cấu trúc tệp lưu trữ
Mỗi tệp csv.gz của kho AWS có chín cột: location_id, sensors_id, location, datetime, lat, lon, parameter, units, value. Dòng đầu của lab là location 2178, sensor 3916, tên Del Norte-2178, parameter no2, đơn vị ppm, giá trị 0.0022. Dữ liệu này gọn và đủ để dựng bảng nghiệm thu mà không cần API.
Đường dẫn trong kho
Đường dẫn có dạng records/csv.gz/locationid=2178/year=2024/month=03/location-2178-20240301.csv.gz. Liệt kê theo tiền tố bằng list-type=2 của S3 trả 31 khóa cho tháng 3/2024. Vì vậy script biết trước có tệp nào rồi mới tải, thay vì đoán ngày và nhận lỗi.
import gzip, csv, io, re, requests
B = "https://openaq-data-archive.s3.amazonaws.com"
pre = "records/csv.gz/locationid=2178/year=2024/month=03/"
r = requests.get(B + "/", params={"list-type": 2, "prefix": pre, "max-keys": 40}, timeout=60)
keys = re.findall(r"<Key>([^<]+)", r.text)
print(len(keys))
raw = requests.get(B + "/" + keys[8], timeout=60).content
rows = list(csv.DictReader(io.StringIO(gzip.decompress(raw).decode())))
print(rows[0])
Một tệp ngày của điểm đo này chỉ khoảng 900 byte sau nén. Tải vài tháng cho vài trạm là việc nhẹ, không cần chạy song song. Lab nghỉ hai giây giữa các lần tải dù kho không ghi giới hạn cụ thể.
Chọn location, sensor và parameter trong OpenAQ API v3 đo chất lượng khí
Trước khi tải số, anh em cần chốt điểm đo và chất cần đo. Chọn sai ở bước này thì mọi phép tính sau đều sai theo.
Một điểm đo có những gì
Điểm đo 2178 có tám chất trong ba ngày lab đọc. Bảng dưới ghi số dòng thật, đơn vị và khoảng giá trị. Tổng là 477 dòng, không có dòng trùng theo sensor và thời điểm, không có giá trị âm.
Parameter |
Sensor |
Đơn vị |
Số dòng / 72 giờ |
Nhỏ nhất |
Lớn nhất |
|---|---|---|---|---|---|
pm25 |
3920 |
µg/m³ |
70 |
1.5 |
10.8 |
pm10 |
3919 |
µg/m³ |
70 |
4.0 |
44.0 |
co |
25227 |
ppm |
68 |
0.1 |
0.7 |
no2 |
3916 |
ppm |
65 |
0.0014 |
0.033 |
nox |
4272103 |
ppm |
65 |
0.001 |
0.1 |
no |
4272226 |
ppm |
65 |
0.0 |
0.068 |
o3 |
3917 |
ppm |
51 |
0.002 |
0.051 |
so2 |
3918 |
ppm |
23 |
0.0 |
0.0 |
Đọc bảng này thế nào
Cùng một trạm, cùng ba ngày, số dòng mỗi chất khác nhau rất xa. PM2.5 có 70 dòng còn SO2 chỉ 23 dòng. Vậy một trạm "có dữ liệu" không có nghĩa là mọi chất đều đủ giờ. Bảng so sánh giữa các chất phải ghi rõ số giờ có mặt, không chỉ ghi giá trị trung bình.
Không chọn trạm theo tên
Tên location không phải khóa bền. Tên trong tệp lưu trữ đã kèm id, ví dụ Del Norte-2178 hay Hanoi-7441, và tên có thể đổi theo nguồn. Lab giữ location_id và sensors_id ở dạng chuỗi, còn tên chỉ để đọc. Khi hai bảng cần ghép, anh em ghép bằng id.
Điểm đo tại Hà Nội trong lab
Lab mở điểm đo 7441 trong kho. Tệp cho thấy tên Hanoi-7441, tọa độ 21.021939, 105.818806, một sensor 21632 đo pm25 đơn vị µg/m³. Điểm đo này chỉ có một parameter trong các ngày đọc. Anh em đừng giả định trạm Việt Nam nào cũng có ozone hay NO2 như trạm ở Mỹ.
Độ phủ theo năm của điểm đo
Liệt kê kho cho điểm 7441 cho thấy năm 2024 có 365 tệp dù đó là năm nhuận, tức thiếu ít nhất một ngày. Năm 2025 chỉ có 99 tệp, tệp cuối là ngày 09/04/2025. Năm 2026 không có tệp nào tại thời điểm lab đọc. Điểm đo có trong danh mục không đồng nghĩa nó vẫn đang báo số, nên bước đầu tiên luôn là kiểm tệp mới nhất.
Cách kiểm trạm còn hoạt động
Với API, anh em xem trường thời điểm cuối của location hoặc sensor theo tài liệu. Với kho lưu trữ, liệt kê tiền tố theo năm rồi xem khóa cuối. Lab dùng cách thứ hai vì không có khóa. Kết quả này nên ghi vào cột ghi chú của bảng trạm, kèm ngày kiểm.
Phân biệt parameter gần nghĩa
Tên pm25 và pm10 là hai cỡ hạt khác nhau, không cộng với nhau. Nox và no2 cũng không phải một chất, nox là nhóm oxit nitơ. Đặt tên cột theo đúng parameter trả về, không dịch sang một tên chung như "bụi" hay "khí thải". Tên gốc giúp truy lại nguồn khi có người hỏi.
Giấy phép và ghi nguồn
Trang rate limits nhắc việc tuân theo điều khoản, trong đó có ghi nguồn. Dữ liệu OpenAQ gom từ nhiều nhà cung cấp nên giấy phép có thể khác theo nguồn, và tài liệu có resource Licenses riêng. Bảng công bố nên ghi OpenAQ, tên nguồn gốc nếu biết và ngày truy cập. Trước khi dùng thương mại, anh em đọc giấy phép của đúng location đó.
Đọc measurement có unit và datetime UTC/local bằng OpenAQ API v3 đo chất lượng khí
Thời gian là phần gây lỗi nhiều nhất. Mục này dùng dữ liệu thật để cho thấy lỗi xuất hiện ở đâu.
Hai dạng giờ trong tài liệu

Hai dạng giờ
Tài liệu API trả thời gian theo cặp utc và local, ví dụ 2024-01-01T10:00:00Z đi với 2024-01-01T03:00:00-07:00. Kho lưu trữ chỉ có một cột datetime dạng giờ địa phương kèm độ lệch. Độ lệch đó đủ để đổi về UTC, miễn là anh em không cắt bỏ phần cuối chuỗi.
Mốc thời gian là cuối kỳ
Tệp ngày 01/01/2023 của Hanoi-7441 có 24 dòng, dòng đầu là 01:00 ngày 01/01, dòng cuối là 00:00 ngày 02/01. Nhìn vào đó có thể thấy mốc giờ đánh dấu cuối khung một giờ theo cách kho này sắp xếp. Vì vậy dòng 00:00 ngày 02/01 thuộc về ngày 01/01 của tệp. Tài liệu API mô tả days là trung bình từ 01:00 tới 0:00 giờ địa phương, khớp với cách chia đó.
Đổi về UTC bằng Python
Hàm fromisoformat đọc được chuỗi có độ lệch, sau đó astimezone đổi sang UTC. Không dùng phép cắt chuỗi hay cộng trừ giờ tay. Ba dòng dưới là output thật của lab, mỗi dòng gồm giờ gốc, giờ UTC và giá trị PM2.5.
import datetime as D
for x in rows[:2] + rows[-1:]:
a = D.datetime.fromisoformat(x["datetime"])
print(x["datetime"], a.astimezone(D.timezone.utc).isoformat(), x["value"])
# 2023-01-01T01:00:00+07:00 2022-12-31T18:00:00+00:00 71.0
# 2023-01-01T02:00:00+07:00 2022-12-31T19:00:00+00:00 77.0
# 2023-01-04T00:00:00+07:00 2023-01-03T17:00:00+00:00 86.0
Dòng đầu của tệp 01/01/2023 rơi vào ngày 31/12/2022 theo UTC. Đó là lý do chia ngày theo UTC cho kết quả khác chia theo giờ địa phương.
Trung bình ngày đổi theo cách chia
Lab tính trung bình PM2.5 cho ba ngày của Hanoi-7441 theo hai cách. Cách một chia ngày địa phương theo khung 01:00 tới 00:00. Cách hai chia theo ngày UTC. Bảng dưới cho thấy cùng 72 giá trị nhưng con số trung bình lệch nhau rõ.
Ngày |
Giờ địa phương: số giờ, trung bình |
Ngày UTC: số giờ, trung bình |
|---|---|---|
31/12/2022 |
không có |
6 giờ, 73.3 |
01/01/2023 |
24 giờ, 74.7 |
24 giờ, 88.0 |
02/01/2023 |
24 giờ, 86.8 |
24 giờ, 73.8 |
03/01/2023 |
24 giờ, 68.1 |
18 giờ, 65.9 |
Câu kết luận rút ra
Ngày 01/01 có trung bình 74.7 hoặc 88.0 tùy cách chia, cùng đơn vị µg/m³. Không cách nào sai, nhưng hai con số không so được với nhau. Bảng trung bình ngày phải ghi rõ chia theo múi giờ nào. Báo cáo cho người đọc tại Việt Nam nên chia theo giờ địa phương và ghi vào chú thích.
Ngày đổi giờ tại Mỹ
Điểm đo 2178 cho một phép thử mà dữ liệu Việt Nam không có. Ngày 10/03/2024 có các mốc 00:00-07:00, 01:00-07:00 rồi nhảy sang 03:00-06:00. Mốc 02:00 không tồn tại. Tệp đó có 147 dòng, ít hơn hai tệp hai bên là 170 và 160 dòng.
Lỗi nếu bỏ độ lệch
Trong ba ngày đó, 174 dòng có độ lệch -07:00 và 303 dòng có -06:00. Nếu lưu cột thời gian mà bỏ phần độ lệch, khoảng cách giữa hai dòng liên tiếp sẽ thành hai giờ dù thực tế là một giờ. Phép đếm giờ thiếu khi đó báo sai một giờ ở mỗi sensor. Lưu cả UTC lẫn chuỗi gốc là cách đơn giản nhất.
Kiểm đơn vị trước khi gộp
Trong điểm đo 2178, PM dùng µg/m³ còn các khí dùng ppm. Dữ liệu từ nguồn khác có thể báo khí theo µg/m³. Đổi giữa ppm và µg/m³ cần khối lượng phân tử, nhiệt độ và áp suất, nên không có một hệ số cố định dùng cho mọi nơi. Lab không đổi, chỉ giữ nguyên unit theo dòng và từ chối gộp hai đơn vị vào một cột.
from collections import defaultdict
units = defaultdict(set)
for x in rows:
units[x["parameter"]].add(x["units"])
for p, u in units.items():
assert len(u) == 1, (p, u)
print(dict(units))
Phép kiểm này chạy nhanh và bắt được lỗi trộn nguồn ngay khi nạp. Nếu assert hỏng, anh em tách bảng theo đơn vị thay vì đổi vội.
Phân biệt dữ liệu gốc với aggregate và coverage trong OpenAQ API v3 đo chất lượng khí
OpenAQ cho cả số gốc lẫn số tính lại. Dùng nhầm hai loại thì so sánh mất ý nghĩa.
Các resource giá trị
Tài liệu liệt kê /v3/sensors/{sensors_id}/measurements cho giá trị gốc do nguồn báo. Resource hours là trung bình theo giờ, days là trung bình ngày tính từ giá trị giờ, years là trung bình năm. Khi nguồn vốn báo mỗi giờ một lần, tài liệu nói measurements và hours tương đương. Với cảm biến báo nhiều lần trong một giờ, hai resource khác nhau.
Nhóm tổng hợp khác
Ngoài các resource trên, tài liệu còn nhóm theo giờ trong ngày, ngày trong tuần và tháng trong năm. Các nhóm này hữu ích cho câu hỏi kiểu "giờ nào trong ngày thường cao nhất". Chúng không thay thế chuỗi thời gian. Lab chưa gọi các nhóm này vì không có khóa, nên bài chỉ nêu theo tài liệu.
Đối tượng coverage
Mỗi kết quả có khối coverage với expectedCount, observedCount, percentComplete và percentCoverage. Ví dụ trong tài liệu ghi 273 giờ kỳ vọng, 266 giờ quan sát, 97 phần trăm. Với measurement đơn lẻ, tài liệu ghi các số này là 1, 1 và 100. Khối coverage là thứ khiến một số trung bình có thể tin được.
Đối tượng summary
Khối summary chứa min, q02, q25, median, q75, q98, max, avg, sd. Với measurement gốc, tài liệu cho biết summary có thể rỗng vì không có gì để tổng hợp. Script đọc phải chấp nhận null ở khối này. Đừng lấy avg của summary làm cột giá trị khi value đã có sẵn.
Tự tính coverage từ tệp lưu trữ
Kho lưu trữ không có khối coverage, nên lab tự đếm. Với mỗi sensor, số giờ quan sát chia cho số giờ của cửa sổ. Ba ngày của điểm 2178 có 71 giờ thực vì mất một giờ do đổi giờ mùa xuân, nhưng cửa sổ tính theo tệp vẫn là một phép xấp xỉ. Bảng trên ghi 72 để dễ đọc, và anh em tính chính xác bằng giờ UTC khi cần.
import datetime as D
from collections import defaultdict
seen = defaultdict(set)
for x in rows:
t = D.datetime.fromisoformat(x["datetime"]).astimezone(D.timezone.utc)
seen[(x["sensors_id"], x["parameter"])].add(t)
for k, v in sorted(seen.items()):
span = (max(v) - min(v)).total_seconds() / 3600 + 1
print(k, len(v), int(span), round(len(v) / span * 100, 1))
Đoạn này tính độ phủ từ giờ đầu tới giờ cuối của sensor, nên không bị đánh lừa bởi ngày đổi giờ. Con số phần trăm đi kèm mỗi sensor trong bảng nghiệm thu.
Đặt ngưỡng độ phủ
Trung bình ngày tính từ 6 giờ dữ liệu không có cùng giá trị với trung bình từ 24 giờ. Dòng 31/12/2022 ở bảng trước là ví dụ, chỉ có 6 giờ. Nhóm dự án nên tự chốt ngưỡng, ví dụ tối thiểu 18 giờ mới công bố trung bình ngày. Ngưỡng này là quy ước của dự án, không phải quy định của OpenAQ.
Giá trị 0 và giá trị thiếu
SO2 của điểm 2178 có 23 dòng đều bằng 0.0. Đó là giá trị được báo, khác với giờ không có dòng nào. Giờ thiếu không điền bằng 0. Một cột có toàn số 0 còn gợi ý ngưỡng phát hiện của thiết bị, nên anh em ghi nhận thay vì kết luận rằng không khí không có SO2.
Dữ liệu có thể được vá sau
Tài liệu AWS cho biết tệp được ghi sau 72 giờ kể từ cuối ngày và có thể được vá lại khi dữ liệu thay đổi. Như vậy bảng tải tháng trước có thể khác bảng tải hôm nay. Cột ngày tải giúp giải thích khác biệt đó.
Đối soát timestamp và đơn vị trong OpenAQ API v3 đo chất lượng khí mà không tự đổi sang AQI
Phần này trả lời câu hỏi hay gặp nhất: vì sao không nên ghi một số AQI bên cạnh mọi giá trị nồng độ.
Nồng độ không phải AQI

Nồng độ khác AQI
Giá trị trong OpenAQ là nồng độ theo đơn vị đo. AQI là một chỉ số do cơ quan từng nước định nghĩa, với bảng ngưỡng, kỳ trung bình và cách làm tròn riêng. Giá trị 86 µg/m³ PM2.5 của một giờ không phải AQI 86. Đặt hai thứ cùng một cột sẽ khiến người đọc hiểu sai mức độ.
Vì sao không có công thức chung
AQI của Mỹ, Việt Nam hay châu Âu dùng ngưỡng khác nhau, và mỗi chất có kỳ trung bình riêng. Có chất tính theo 24 giờ, có chất theo 8 giờ, có chất theo một giờ. Một script đổi mọi concentration bằng một phép nhân là sai từ gốc. Lab không tính AQI ở bất kỳ dòng nào.
Nếu dự án buộc phải có AQI
Anh em chọn một chuẩn duy nhất, ghi tên văn bản và phiên bản. Tính theo đúng kỳ trung bình của chuẩn đó, từ dữ liệu đủ độ phủ. Cột AQI đặt riêng, ghi rõ chuẩn và chất quyết định. Với dữ liệu thiếu giờ, để trống thay vì ước lượng.
Ghi nhãn trong biểu đồ
Trục của biểu đồ nên ghi "PM2.5 trung bình giờ, µg/m³". Chú thích nên có múi giờ và tên sensor. Tránh các nhãn "tốt, xấu, nguy hại" nếu không tính theo một chuẩn cụ thể. Nhãn đúng rẻ hơn nhiều so với việc đính chính sau khi đăng.
Đối soát với nguồn gốc
Điểm đo trong OpenAQ thường lấy từ một nhà cung cấp dữ liệu khác. Khi con số trông lạ, anh em đối chiếu với trang của nguồn gốc cho cùng giờ. Lúc so phải chú ý múi giờ và mốc cuối kỳ, vì lệch một giờ dễ bị nhầm thành lệch số. Lab chỉ đọc một nguồn nên không kết luận về độ chính xác.
Phát hiện dòng đáng ngờ
Một quy tắc đơn giản là gắn cờ giá trị âm, giá trị đứng yên nhiều giờ và bước nhảy lớn. Lab không thấy giá trị âm trong 477 dòng. Cờ chỉ để người xem lại, không tự xóa dữ liệu. Xóa dòng mà không ghi lý do sẽ làm bảng mất khả năng kiểm chứng.
def flags(a):
out = []
for i, x in enumerate(a):
v = float(x["value"])
if v < 0:
out.append((x["datetime"], "am"))
if i >= 6 and len({y["value"] for y in a[i - 6:i + 1]}) == 1 and v > 0:
out.append((x["datetime"], "dung_yen_7_gio"))
return out
Hàm này là ví dụ của lab, chạy trên từng sensor đã sắp theo thời gian. Ngưỡng bảy giờ là tự đặt để minh họa, dự án có thể chọn ngưỡng khác theo thiết bị.
Tọa độ và làm tròn
Cột lat, lon nên giữ nguyên độ chính xác gốc. Làm tròn tọa độ để tiết kiệm chỗ có thể khiến hai điểm đo gần nhau trùng khóa. Khóa vẫn là id, tọa độ chỉ để vẽ bản đồ.
Đường tải dữ liệu và vai trò proxy với OpenAQ API v3 đo chất lượng khí
Phần này tách phần tải mạng khỏi phần xử lý, vì hai việc có nhu cầu rất khác nhau.
Khi nào dùng API, khi nào dùng kho lưu trữ

API hay kho lưu trữ
Nhu cầu |
Nguồn hợp |
Lý do |
Điểm cần giữ |
|---|---|---|---|
Số mới vài ngày gần đây |
API v3 |
Kho ghi sau 72 giờ |
Khóa, rate limit |
Lịch sử nhiều năm của ít trạm |
Kho AWS |
Tệp tĩnh, không cần khóa |
Ngày tải, tệp vá |
Trung bình có sẵn coverage |
API v3 |
Có khối coverage |
Ghi rõ resource |
Tải lại cho kiểm toán |
Kho AWS |
Đường dẫn ổn định |
Giữ danh sách khóa |
Script tải an toàn
Script nên ghi lại khóa đã tải, kích thước và ngày tải vào một tệp manifest. Lần chạy sau chỉ tải tệp mới hoặc tệp đổi kích thước. Khi mạng đứt giữa chừng, anh em chạy lại mà không lặp việc. Với vài trạm, cách này đủ dùng và dễ đọc hơn mọi framework.
Proxy có cần không
API OpenAQ gọi thẳng là cách đúng. Giới hạn gắn với khóa nên đổi IP không tăng quota, và cố vượt giới hạn có thể làm mất quyền truy cập. Proxy chỉ có lý do khi mạng nội bộ của doanh nghiệp buộc đi qua một cổng ra cố định. Khi đó anh em đặt proxy ở tầng tải, phần phân tích vẫn chạy offline. Cách đặt nhịp gọi có ở bài rate limit API khi dùng proxy.
Khai báo proxy trong requests
Nếu buộc đi qua cổng doanh nghiệp, cấu hình đặt trong biến môi trường hoặc session, không viết trực tiếp vào hàm tải. Chuỗi proxy theo định dạng proxy mà nhóm hạ tầng cấp. Lab không dùng proxy trong lần chạy này, nên bài không khẳng định gì về tốc độ hay tương thích.
Nếu tác vụ khác của anh em cần IP Việt Nam ổn định, có thể mua proxy tại trang chủ ProxyVN.
Không gộp vào crawler web
Dữ liệu OpenAQ có API và tệp công khai, nên không cần thu thập từ trang Explorer. Phần khác nhau giữa lấy API và cào HTML có ở bài Web Scraping proxy là gì. Với nguồn có sẵn API, ưu tiên API vì schema rõ và đúng ý nhà cung cấp.
Lưu trữ cục bộ
Lab lưu CSV đã giải nén kèm cột ngày tải. Với dữ liệu lớn hơn, Parquet theo năm và location giúp đọc nhanh. Cấu trúc nên giữ giống đường dẫn kho, để dò ngược về nguồn dễ dàng. Không đổi tên cột gốc trong lớp lưu thô.
Khóa phiên bản
Lab chạy Python 3.12.10, requests 2.34.2. API v3 có tiền tố phiên bản trong đường dẫn. Ghi phiên bản đó vào cấu hình và giữ vài tệp mẫu làm test đọc. Khi OpenAQ đổi cột, test sẽ báo trước khi bảng báo cáo sai.
OpenAQ API v3 đo chất lượng khí: lỗi hay gặp và cách xử lý
Bảng dưới gom các lỗi lab gặp hoặc suy ra được từ tài liệu, kèm cách nhận biết.
Bảng lỗi nhanh
Triệu chứng |
Nguyên nhân |
Cách xử lý |
|---|---|---|
401 nhắc X-API-Key |
Thiếu header |
Kiểm biến môi trường |
401 Invalid credentials |
Khóa sai hoặc đã đổi |
Lấy khóa mới ở Explorer |
429 |
Vượt 60 lượt/phút hoặc 2000 lượt/giờ |
Chờ kỳ mới, giảm nhịp |
Thiếu mốc 02:00 |
Ngày đổi giờ |
Dùng UTC để đếm |
Trung bình ngày lệch |
Chia ngày khác múi giờ |
Ghi rõ cách chia |
Hai đơn vị một chất |
Trộn nguồn |
Tách bảng theo unit |
Lỗi cắt chuỗi ngày
Nhiều người lấy mười ký tự đầu của datetime làm ngày. Với dòng 2023-01-02T00:00:00+07:00, cách đó đưa dòng sang ngày 02/01 trong khi tệp xếp nó vào 01/01. Lab trừ một giờ trước khi lấy ngày để khớp với khung 01:00 tới 00:00. Quy ước này cần viết vào tài liệu của dự án.
Lỗi đếm năm nhuận
Script đếm 365 tệp cho năm 2024 và báo đủ sẽ bỏ sót ngày thiếu. Cách đúng là sinh danh sách ngày theo lịch rồi so với danh sách khóa. Phép so này chỉ mất vài dòng code và bắt đúng trường hợp của điểm Hanoi-7441.
import datetime as D, re
have = {re.search(r"-(\d{8})\.csv", k).group(1) for k in keys}
d, end, miss = D.date(2024, 1, 1), D.date(2024, 12, 31), []
while d <= end:
if d.strftime("%Y%m%d") not in have:
miss.append(d.isoformat())
d += D.timedelta(days=1)
print(len(have), miss)
Biến keys ở đây là danh sách khóa của cả năm, lấy từ bước liệt kê theo tiền tố. Kết quả cho biết chính xác ngày nào thiếu, để ghi vào báo cáo độ phủ.
Lỗi hiểu trạm đã ngừng
Điểm Hanoi-7441 không có tệp sau tháng 4/2025 trong kho tại ngày lab đọc. Một dashboard vẫn hiện số cũ mà không báo sẽ làm người xem tưởng đó là số mới. Giao diện nên hiện thời điểm của giá trị cuối cùng ngay cạnh con số.
Khóa dữ liệu là sensor và thời điểm, đơn vị đi theo từng dòng, thời gian lưu cả UTC lẫn chuỗi gốc. Ba điều đó giữ bảng không lệch, còn AQI là một phép tính riêng theo chuẩn riêng.
Đầu ra nghiệm thu dataset sensor, parameter, unit, datetime, value của OpenAQ API v3 đo chất lượng khí
Mục này chốt bảng cuối và danh sách kiểm trước khi dùng số.
Schema của bảng

Nghiệm thu dataset
Cột |
Ví dụ thật |
Ghi chú |
|---|---|---|
location_id |
7441 |
Chuỗi, khóa trạm |
sensors_id |
21632 |
Chuỗi, khóa cảm biến |
parameter |
pm25 |
Giữ tên gốc |
unit |
µg/m³ |
Theo từng dòng |
datetime_local |
2023-01-01T01:00:00+07:00 |
Chuỗi gốc |
datetime_utc |
2022-12-31T18:00:00+00:00 |
Tính từ chuỗi gốc |
value |
71.0 |
Không làm tròn |
fetched_at |
2026-11-05 |
Ngày tải |
Danh sách kiểm
- Khóa dòng: sensors_id cộng datetime_utc không trùng.
- Đơn vị: mỗi parameter chỉ có một unit trong bảng.
- Múi giờ: phép đổi UTC giữ đúng độ lệch ở ngày đổi giờ.
- Độ phủ: mỗi sensor có số giờ quan sát và phần trăm.
- Nguồn: có tên nguồn, đường dẫn tệp hoặc resource, ngày tải.
- AQI: không có cột AQI nếu chưa chốt chuẩn.
Câu kết luận được phép viết
Từ lab, anh em có thể viết rằng một điểm đo có số giờ khác nhau giữa các chất. Trung bình ngày đổi theo cách chia múi giờ. Điểm đo có trong kho không có nghĩa vẫn đang báo số. Không nên viết rằng ba ngày tháng 1 đại diện cho chất lượng không khí Hà Nội.
Những gì lab chưa chạy
Lab chưa gọi được bất kỳ resource dữ liệu nào của API v3 vì không có khóa. Các trường coverage và summary lấy từ ví dụ tài liệu. Lab chưa thử phân trang, bộ lọc thời gian của API, resource days và years. Anh em có khóa nên chạy lại các phần này trước khi dựa vào tên tham số trong bài.
Việc nên làm sau khi có khóa
Gọi measurements cho sensor 21632 trong đúng ba ngày lab đã đọc. So value và mốc giờ với tệp lưu trữ. Nếu khớp, hai nguồn có thể dùng thay nhau cho phần lịch sử. Nếu lệch, ghi lại lệch ở đâu rồi mới quyết định nguồn chính.
Duy trì lâu dài
Mỗi tháng, script kiểm tệp mới nhất của từng trạm và cập nhật cột trạng thái. Trạm im lặng quá 30 ngày được gắn cờ. Con số 30 ngày là gợi ý, dự án chỉnh theo nhu cầu. Một bảng trạng thái ngắn tiết kiệm nhiều thời gian khi có người hỏi vì sao biểu đồ dừng lại.
OpenAQ API v3 đo chất lượng khí: câu hỏi thường gặp
Các câu dưới đây gom những điểm hay bị hỏi khi bắt đầu.
Không có khóa thì có dùng API được không?
Không. Lab nhận 401 ở mọi lời gọi không khóa. Muốn dữ liệu không cần khóa thì đọc kho lưu trữ trên AWS, chấp nhận độ trễ khoảng 72 giờ.
Measurements và hours khác nhau chỗ nào?
Measurements là giá trị gốc, hours là trung bình theo giờ. Nguồn báo mỗi giờ một lần thì hai resource tương đương theo tài liệu.
Vì sao tệp ngày kết thúc lúc 00:00 hôm sau?
Vì tệp xếp theo khung 01:00 tới 00:00 giờ địa phương. Dòng 00:00 thuộc về ngày trước đó trong cách chia này.
Có nên đổi tất cả sang µg/m³ không?
Không nên làm mặc định. Phép đổi cần điều kiện nhiệt độ và áp suất. Giữ đơn vị gốc, chỉ đổi khi có công thức và điều kiện ghi rõ.
Có tính AQI từ giá trị giờ được không?
Chỉ khi chuẩn AQI anh em chọn cho phép kỳ trung bình đó. Nhiều chỉ số cần trung bình 24 giờ hoặc 8 giờ, nên giá trị một giờ chưa đủ.
Dữ liệu trong kho có đổi sau khi đăng không?
Có thể. Tài liệu ghi tệp có thể được vá lại. Giữ ngày tải và kích thước tệp để nhận ra thay đổi.
Proxy có giúp tăng giới hạn 60 lượt mỗi phút không?
Không. Giới hạn gắn với khóa. Cần nhiều hơn thì liên hệ OpenAQ theo mức dùng riêng mà tài liệu nêu.
Vì sao điểm đo Hà Nội chỉ có PM2.5?
Trong các ngày lab đọc, điểm 7441 chỉ có một sensor pm25. Các điểm khác có thể đo thêm chất khác, cần kiểm từng điểm.
Nên lưu CSV hay Parquet?
Ít trạm thì CSV đủ dùng và dễ kiểm. Nhiều năm, nhiều trạm thì Parquet theo năm và location đọc nhanh hơn.
OpenAQ API v3 đo chất lượng khí và những điều cần nhớ
OpenAQ API v3 đo chất lượng khí cho bảng đáng tin khi anh em khóa theo sensor, giữ đơn vị từng dòng, lưu cả UTC lẫn giờ địa phương và ghi cách chia ngày. Đừng đổi nồng độ thành AQI khi chưa chốt chuẩn. Proxy.vn - Nhà cung cấp dịch vụ proxy chất lượng hàng đầu Việt Nam khuyên anh em kiểm độ phủ trước khi dùng OpenAQ API v3 đo chất lượng khí.