Logo FinLensFinLens Docs

Xử lý lỗi finlens: FinLensError và cây ngoại lệ Python

Cách bắt lỗi khi lấy dữ liệu chứng khoán bằng finlens: except FinLensError bắt trọn cây ngoại lệ, rẽ nhánh theo mã FL_*, dùng on_error cho lời gọi nhiều mã.

Mọi lỗi thư viện finlens chủ động sinh ra đều nằm trong một cây thừa kế duy nhất, gốc là finlens.FinLensError. Trang này nói bạn nên bắt ở mức nào, đọc gì từ ngoại lệ bắt được, và phân biệt lỗi (dừng chương trình) với cảnh báo (không dừng).

Một dòng bắt hết

import finlens

client = finlens.client()          # đọc FINLENS_API_KEY

try:
    df = client.eod.stock.ohlcv("HPG", start="2024-01-01", end="2024-06-30")
except finlens.FinLensError as exc:
    print(exc.code, "-", exc.message)

`except finlens.FinLensError` là đủ

Đây là cam kết của bản 1.x và có test khẳng định: mọi lớp lỗi công khai của thư viện đều là con của FinLensError.

Tài liệu của bản 0.1.x nói ngược lại — rằng except FinLensError để lọt CompanyTypeMismatchError và các lỗi bị tầng dòng tiền ném lại thành RuntimeError. Cả hai chỗ đó đã được sửa. Nếu code của bạn còn một nhánh except RuntimeError viết theo hướng dẫn cũ, nhánh đó nay là code chết — bỏ đi được.

Không cần except Exception. Bắt rộng như vậy sẽ nuốt luôn KeyboardInterrupt của bạn, lỗi gõ sai tên cột, và mọi thứ khác không liên quan đến FinLens.

Ngoại lệ mang theo những gì

Mọi lớp trong cây đều có sáu thuộc tính sau:

Thuộc tínhKiểuNội dung
.codestrMã lỗi ổn định, máy đọc được — ví dụ "FL_QUOTA_DAILY"
.messagestrMô tả cho người đọc, tiếng Việt
.request_idstr | NoneMã của request thất bại. Gửi kèm khi báo lỗi
.status_codeint | NoneMã trạng thái HTTP, nếu lỗi đến từ một response
.detailAnyDữ liệu cấu trúc kèm theo, nếu có
.doc_urlstrTrang tài liệu của mã lỗi này

str(exc) gộp ba thứ quan trọng nhất lại:

[FL_QUOTA_DAILY] Đã dùng hết hạn mức 5000 request/ngày. Đặt lại lúc 2026-08-05T00:00:00+07:00. (request_id=req_8f3a1c) -> https://docs.finlens.vn/python-sdk/errors/FL_QUOTA_DAILY

Rẽ nhánh theo `.code`, không bao giờ theo `.message`

code là hợp đồng: nó không đổi, không được dịch. message thì được phép đổi chữ và được phép dịch bất cứ lúc nào.

try:
    df = client.eod.stock.ohlcv("HPG")
except finlens.QuotaError as exc:
    if exc.code == "FL_QUOTA_DAILY":   # đúng
        ...
    if "hạn mức" in exc.message:       # sai — sẽ hỏng khi câu chữ đổi
        ...

Một số lớp mang thêm thuộc tính riêng:

LớpThuộc tính thêm
TierError và các con.account_type (gói hiện tại), .required_tier (gói tối thiểu cần có)
RateLimitError.retry_after — số giây nên chờ
DailyQuotaExceededError.resets_at — thời điểm hạn mức đặt lại, chuỗi kèm offset +07:00
InvalidDateRangeError.max_days — số ngày tối đa server chấp nhận
PartialFetchError.data (phần lấy được) và .failures (mã → ngoại lệ)

Cây ngoại lệ

FinLensError
├── ConfigurationError            cấu hình client sai: thiếu khoá, base URL hỏng
├── UnsupportedOperationError     có trong API nhưng chưa khả dụng ở bản này

├── AuthError                     không xác thực được khoá
│   ├── InvalidApiKeyError        khoá không tồn tại hoặc đã bị thu hồi
│   ├── ApiKeyExpiredError        khoá hết hạn
│   ├── AccountExpiredError       gói dịch vụ hết hạn (khoá vẫn còn hiệu lực)
│   └── AccountInactiveError      tài khoản inactive hoặc banned

├── TierError                     gói hiện tại không cho phép thao tác này
│   ├── DatasetNotInTierError     bộ dữ liệu ngoài gói
│   └── SymbolNotInTierError      mã chứng khoán ngoài gói

├── QuotaError                    chạm một giới hạn sử dụng
│   ├── RateLimitError            vượt giới hạn tần suất
│   └── DailyQuotaExceededError   hết hạn mức request trong ngày

├── ValidationError               tham số truyền vào không hợp lệ
│   ├── InvalidSymbolError        mã sai định dạng hoặc không tồn tại
│   ├── InvalidDateRangeError     khoảng thời gian sai hoặc quá dài
│   ├── InvalidIntervalError      interval không hợp lệ hoặc nhập nhằng
│   ├── CompanyTypeMismatchError  các mã thuộc nhiều loại hình doanh nghiệp
│   └── UnitMismatchError         ghép các bảng khác đơn vị

├── TransportError                lỗi mạng, trước khi có response hợp lệ
│   ├── ConnectionFailedError     không kết nối được
│   │   └── TlsVerificationError  không xác thực được chứng chỉ TLS
│   ├── RequestTimeoutError       hết thời gian chờ
│   └── ServerError               backend trả 5xx sau khi đã thử lại

├── DataError                     có response nhưng nội dung không đúng hợp đồng
│   ├── DecodingError             không giải mã được payload
│   ├── SchemaMismatchError       server trả schema client không hiểu
│   ├── NoDataError               tài nguyên không tồn tại
│   └── PartialFetchError         một phần các mã thất bại

├── UpgradeRequiredError          bản client này không còn được hỗ trợ
└── IncompatibleApiVersionError   client và backend khác major của hợp đồng

AuthError, TierError, QuotaError, ValidationError, TransportErrorDataError vừa là lớp cha vừa là lớp bắt được: bắt lớp cha là bắt trọn nhánh, kể cả những lớp con được thêm về sau. TlsVerificationError là ví dụ — nó là lớp con của ConnectionFailedError, nên code chỉ bắt lớp cha vẫn nhận được nó mà không phải sửa gì.

Nên bắt ở mức nào

Quy tắc ba dòng

Bắt lớp cha khi bạn chỉ cần biết "có lỗi". Bắt lớp lá khi mỗi loại lỗi dẫn tới một hành động khác nhau. Phân biệt bằng .code khi cùng một lớp mà hai mã cần hai cách xử lý.

Bạn đang viết gìKhuyến nghị
Notebook, script phân tích chạy một lầnMột khối except finlens.FinLensError, in exc ra rồi dừng. Đủ dùng
Job chạy định kỳ, không có người trựcTách ba nhóm: thử lại được, cần người can thiệp, lỗi trong code của bạn
Ứng dụng phục vụ người khácBắt FinLensError ở biên, hiển thị exc.message, ghi log exc.codeexc.request_id
Lời gọi nhiều mãĐừng bọc try. Dùng tham số on_error — xem mục dưới

Bảng phân loại cho nhóm job định kỳ:

NhómLớpNên làm gì
Thử lại đượcRateLimitError, RequestTimeoutError, ServerError, ConnectionFailedErrorChờ rồi chạy lại. Với RateLimitError thì chờ đúng .retry_after giây
Chờ vô íchTlsVerificationError, AuthError, TierError, UpgradeRequiredErrorBáo cho người. Nguyên nhân nằm ở máy bạn, ở tài khoản, hoặc ở phiên bản — thử lại không đổi được gì
Hết hạn mứcDailyQuotaExceededErrorChạy lại sau .resets_at, không sớm hơn
Lỗi trong code của bạnValidationError và các conSửa code. Chạy lại y nguyên sẽ ra đúng lỗi đó

Hai dòng đầu giao nhau ở một chỗ: TlsVerificationError một ConnectionFailedError. Nếu bạn bắt lớp cha để thử lại thì phải đặt nhánh TlsVerificationError lên trước, không thì một lỗi vĩnh viễn sẽ bị thử lại mãi mà không bao giờ khác đi.

import time
import finlens

client = finlens.client()

try:
    df = client.eod.stock.investor.flow("HPG", start="2024-01-01")
except finlens.RateLimitError as exc:
    time.sleep(exc.retry_after or 60)
except finlens.DailyQuotaExceededError as exc:
    print("Hạn mức đặt lại lúc", exc.resets_at)
except finlens.AuthError as exc:
    print("Kiểm tra lại khoá API:", exc.message)
except finlens.FinLensError as exc:
    print(exc.code, exc.message, exc.request_id)

Thứ tự except đi từ hẹp tới rộng. Đặt FinLensError lên trên cùng thì mọi nhánh phía dưới không bao giờ chạy.

ValidationError vẫn là ValueError

ValidationError kế thừa cả FinLensError ValueError, và đó là chủ đích: code cũ viết except ValueError quanh lời gọi FinLens vẫn tiếp tục chạy sau khi nâng lên 1.x.

try:
    df = client.eod.stock.ohlcv("HPG", interval="1M")
except ValueError as exc:          # vẫn bắt được
    print(exc)

Nhưng khi viết mới thì bắt finlens.ValidationError: nó chỉ bắt lỗi tham số của FinLens, không nuốt luôn ValueError từ code khác của bạn.

Ba lỗi tham số hay gặp nhất:

  • InvalidIntervalErrorinterval chỉ nhận 1d, 1w, 1mo, 3mo, 6mo, 1y. 1M1m bị từ chối thẳng thay vì đoán ý: trong từ vựng cũ hai token đó chỉ khác nhau ở chữ hoa nhưng lệch nhau 43.200 lần.
  • InvalidDateRangeError — khoảng thời gian vượt giới hạn. Giới hạn thật do server quyết định, đọc bằng client.limits(), không phải hằng số trong thư viện.
  • CompanyTypeMismatchError — các mã thuộc nhiều loại hình doanh nghiệp khác nhau (phi tài chính, ngân hàng, chứng khoán, bảo hiểm). Cây chỉ tiêu báo cáo tài chính của chúng khác nhau nên không gộp vào một bảng được.

Nhiều mã trong một lời gọi: on_error

Khi bạn xin 50 mã và 1 mã hỏng, 49 mã còn lại không được mất theo. Đó là việc của tham số on_error, có ở mọi hàm nhận nhiều mã.

Giá trịHành vi
"warn" (mặc định)Giữ phần thành công, phát PartialDataWarning. Chương trình chạy tiếp
"raise"Ném PartialFetchErrormang theo phần đã lấy được ở .data
"ignore"Giữ phần thành công, không nói gì cả

Cả ba trường hợp đều ghi lại chi tiết vào df.attrs["finlens"]:

df = client.eod.stock.ohlcv(ma_list, start="2024-01-01")   # on_error="warn"

if df.attrs["finlens"]["partial"]:
    for symbol, ly_do in df.attrs["finlens"]["failed"].items():
        print(symbol, ly_do["code"], ly_do["message"])

Với on_error="raise", dữ liệu đi kèm ngoại lệ nên bạn không phải gọi lại từ đầu:

try:
    df = client.eod.stock.ohlcv(ma_list, on_error="raise")
except finlens.PartialFetchError as exc:
    df = exc.data                    # 49 mã thành công, vẫn dùng được
    print(exc.failures)              # {"XYZ": InvalidSymbolError(...)}

exc.failures ánh xạ mã chứng khoán → ngoại lệ tương ứng, nên mỗi mã hỏng vẫn giữ đủ .code.message của riêng nó.

`df.attrs` không sống sót qua `merge` và `concat`

pandas không chuyển attrs qua các phép ghép bảng. Đọc partial, failed, units trước khi ghép, hoặc lưu lại giá trị bạn cần. Đây là thông tin đi kèm dữ liệu, không phải một cơ chế bảo vệ.

Kết quả không có dòng nào không phải lỗi. Một khoảng ngày nghỉ, hay một mã mới niêm yết, trả về DataFrame rỗng với đúng cột và đúng dtype — không cảnh báo, không ngoại lệ. NoDataError chỉ dành cho trường hợp server báo rõ rằng tài nguyên không tồn tại.

TlsVerificationError: máy bạn nằm sau proxy kiểm tra TLS

Rất phổ biến trong mạng doanh nghiệp: proxy kiểm tra TLS hoặc phần mềm diệt virus chèn vào giữa kết nối bằng một CA riêng, mà bundle chứng chỉ mặc định của Python không có CA đó.

Đây là lỗi vĩnh viễn, khác hẳn một lỗi kết nối thường: thử lại không bao giờ cứu được, và cách sửa nằm ở máy bạn chứ không ở phía FinLens. Vì vậy thư viện không thử lại nó.

client = finlens.client(ca_bundle="/duong/dan/toi/ca-bundle.pem")

Hoặc đặt biến môi trường một lần cho cả máy:

# Linux / macOS
export FINLENS_CA_BUNDLE="/duong/dan/toi/ca-bundle.pem"

# Windows PowerShell
$env:FINLENS_CA_BUNDLE = "C:\certs\ca-bundle.pem"

File CA bundle do bộ phận IT của bạn cấp — đó là chứng chỉ gốc của chính proxy đang đứng giữa.

Khác với 0.1.x

Ở bản 0.1.x, lỗi chứng chỉ bị báo cáo thành "backend đang không phản hồi", gửi người đọc đi kiểm đúng chỗ không có vấn đề gì. Nếu bạn đang gặp thông báo đó trong khi curl cùng lúc vẫn chạy, hãy nâng cấp bằng pip install -U finlens rồi đọc lại thông báo mới.

TlsVerificationError kế thừa ConnectionFailedError, nên code đang bắt lớp cha không phải sửa gì.

SchemaMismatchError: response sai hình dạng

Ném ra khi server trả về một cấu trúc mà phiên bản client này không hiểu. Hai nguyên nhân, theo thứ tự khả năng:

  1. FINLENS_BASE_URL đang trỏ nhầm chỗ — thường là một endpoint cũ hoặc một backend cục bộ đang chạy phiên bản khác. Kiểm biến môi trường trước tiên.
  2. Client quá cũ so với backend. Nâng cấp bằng pip install -U finlens.

Đây là thay đổi hành vi so với 0.1.x

Ở bản 0.1.x, một response sai hình dạng trả về bảng rỗng trong im lặng. Người dùng đọc "khối ngoại mua ròng 0 đồng" trong khi thật ra client không hiểu được response — không ngoại lệ, không cảnh báo.

Nay nó ném lỗi. Một traceback to rõ ràng tốt hơn một con số sai trông rất thật.

Bảng tra mã lỗi

Ánh xạ là nhiều-đối-một: nhiều mã có thể trỏ về cùng một lớp. Bắt theo lớp, phân biệt theo mã.

Lớp ngoại lệThông báo bạn thấy
FL_CONFIGConfigurationErrorCấu hình client không hợp lệ
FL_AUTH_INVALID_KEYInvalidApiKeyErrorAPI key không hợp lệ hoặc đã bị thu hồi
FL_AUTH_KEY_EXPIREDApiKeyExpiredErrorAPI key đã hết hạn vào ...
FL_AUTH_ACCOUNT_EXPIREDAccountExpiredErrorGói FinLens của bạn đã hết hạn vào ... Vui lòng gia hạn để tiếp tục
FL_AUTH_ACCOUNT_INACTIVEAccountInactiveErrorTài khoản đang ở trạng thái ...
FL_TIER_DATASETDatasetNotInTierErrorBộ dữ liệu ... cần gói ... (bạn đang dùng ...)
FL_TIER_SYMBOLSymbolNotInTierErrorMã ... cần gói ... (bạn đang dùng ...)
FL_QUOTA_RATERateLimitErrorVượt giới hạn tần suất. Thử lại sau ... giây
FL_QUOTA_DAILYDailyQuotaExceededErrorĐã dùng hết hạn mức ... request/ngày. Đặt lại lúc ...
FL_VALIDATION_SYMBOLInvalidSymbolErrorMã ... không tồn tại
FL_VALIDATION_DATE_RANGEInvalidDateRangeErrorKhoảng thời gian quá lớn: yêu cầu ... ngày, tối đa ...
FL_VALIDATION_INTERVALInvalidIntervalErrorInterval ... không hợp lệ. Giá trị hợp lệ: ...
FL_VALIDATION_INTERVAL_AMBIGUOUSInvalidIntervalErrorInterval ... nhập nhằng: có thể là một phút hoặc một tháng
FL_VALIDATION_COMPANY_TYPECompanyTypeMismatchErrorCác mã thuộc nhiều loại hình doanh nghiệp ...
FL_VALIDATION_UNIT_MISMATCHUnitMismatchErrorKhông thể ghép các bảng khác đơn vị: ... và ...
FL_DATA_EMPTYNoDataErrorKhông có dữ liệu cho ...
FL_DATA_SCHEMASchemaMismatchErrorServer trả về schema mà phiên bản client này không hiểu
FL_DATA_PARTIALPartialFetchError.../... mã thất bại
FL_TRANSPORT_CONNECTConnectionFailedErrorKhông kết nối được tới ...
FL_TRANSPORT_TLSTlsVerificationErrorXác thực chứng chỉ TLS thất bại khi nối tới ...
FL_TRANSPORT_TIMEOUTRequestTimeoutErrorRequest hết thời gian chờ sau ... giây
FL_SERVERServerErrorLỗi phía server
FL_VERSION_UPGRADE_REQUIREDUpgradeRequiredErrorPhiên bản client này không còn được hỗ trợ. Tối thiểu là ...
FL_VERSION_API_INCOMPATIBLEIncompatibleApiVersionErrorClient dùng contract major ..., server dùng ...

Dấu ... là chỗ thông báo thật điền giá trị cụ thể của lời gọi.

Một mã không có trong bảng này vẫn được ánh xạ về một lớp trong cây theo nhóm của nó — không bao giờ thành một RuntimeError trần. Đó là lý do except finlens.FinLensError giữ được lời hứa của mình kể cả khi backend thêm mã mới.

Cảnh báo khác lỗi

Cảnh báo không dừng chương trình. Chúng đi qua module warnings của Python, nên bạn tắt hay bật từng loại một cách chọn lọc.

FinLensWarning (UserWarning)
├── UpgradeAvailableWarning   có bản client mới hơn trên PyPI
├── QuotaLowWarning           hạn mức sắp cạn. Chỉ phát một lần mỗi client
├── PartialDataWarning        một phần các mã thất bại, với on_error="warn"
├── DataQualityWarning        dữ liệu trả về có vấn đề đã biết
└── TierLimitWarning          yêu cầu đã bị cắt bớt cho vừa gói dịch vụ

FinLensDeprecationWarning (DeprecationWarning)
    một phần của API công khai sắp bị gỡ bỏ

Tắt một loại, giữ nguyên các loại còn lại:

import warnings
import finlens

warnings.filterwarnings("ignore", category=finlens.QuotaLowWarning)

Hoặc làm ngược lại — biến cảnh báo thành lỗi để nó không thể bị bỏ qua. Hữu ích trong job chạy tự động, nơi không ai đọc stderr:

warnings.filterwarnings("error", category=finlens.PartialDataWarning)

Lúc đó PartialDataWarning sẽ được ném ra như một ngoại lệ và dừng chương trình — khác với on_error="raise" ở chỗ bạn không nhận được .data.

Tắt toàn bộ cảnh báo của FinLens mà không đụng tới cảnh báo của thư viện khác:

warnings.filterwarnings("ignore", category=finlens.FinLensWarning)

Thư viện không còn tắt cảnh báo của bạn

Bản 0.1.x gọi warnings.filterwarnings("ignore") ngay lúc import, tức là tắt mọi cảnh báo trong toàn bộ tiến trình của bạn — kể cả cảnh báo của pandas, numpy và code của chính bạn. Bản 1.x không làm vậy nữa.

FinLensDeprecationWarning kế thừa DeprecationWarning chứ không kế thừa FinLensWarning, theo đúng quy ước Python: mặc định ẩn với người dùng cuối, hiện ra khi chạy python -W default.

Khi cần báo lỗi

Gửi kèm ba thứ, chúng cắt ngắn phần lớn cuộc trao đổi qua lại:

import finlens

print(finlens.__version__)
print(finlens.build_info())     # commit và thời điểm build của binary bạn đang chạy

Cộng thêm exc.request_id của lời gọi hỏng — đó là thứ cho phép tra lại đúng request đó ở phía FinLens.

Danh sách thay đổi từng phiên bản: Nhật ký thay đổi.

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

Bắt lỗi của thư viện finlens bằng cách nào?

Dùng `except finlens.FinLensError`. Ở bản 1.x mọi lớp lỗi công khai của thư viện Python finlens đều kế thừa `FinLensError`, nên một khối `except` là đủ và không cần `except Exception`. Ngoại lệ bắt được mang `.code` (mã ổn định như `FL_QUOTA_DAILY`), `.message` bằng tiếng Việt, `.request_id`, `.status_code`, `.detail` và `.doc_url`; `str(exc)` gộp sẵn ba thứ quan trọng nhất. Luôn rẽ nhánh theo `.code`, đừng so khớp `.message` — nội dung thông báo được phép đổi chữ, còn `code` thì không.

Vượt hạn mức khi gọi API dữ liệu chứng khoán bằng Python thì bắt lỗi nào?

Với thư viện Python finlens, cả hai trường hợp đều nằm dưới `finlens.QuotaError`. Vượt giới hạn tần suất là `RateLimitError` (mã `FL_QUOTA_RATE`), mang thêm `.retry_after` là số giây nên chờ. Hết hạn mức trong ngày là `DailyQuotaExceededError` (mã `FL_QUOTA_DAILY`), mang `.resets_at` là thời điểm hạn mức được đặt lại kèm offset `+07:00` — chạy lại sau mốc đó, không sớm hơn. Trước khi cạn, thư viện phát `QuotaLowWarning` một lần cho mỗi client.

Một mã lỗi FL_ có tương ứng một lớp ngoại lệ riêng không?

Không, ánh xạ là nhiều-đối-một: nhiều mã có thể trỏ về cùng một lớp ngoại lệ của thư viện Python finlens. Ví dụ `FL_VALIDATION_INTERVAL` và `FL_VALIDATION_INTERVAL_AMBIGUOUS` cùng cho `InvalidIntervalError`. Vì vậy nên bắt theo lớp rồi phân biệt theo `.code`. Một mã không có trong bảng tra vẫn được ánh xạ về một lớp trong cây theo nhóm của nó, không bao giờ thành `RuntimeError` trần — đó là lý do `except finlens.FinLensError` vẫn đúng khi máy chủ thêm mã mới.

Gọi nhiều mã cùng lúc mà một mã hỏng thì chuyện gì xảy ra?

Tham số `on_error` quyết định, và mặc định là `"warn"`: thư viện Python finlens giữ phần lấy được, phát `PartialDataWarning`, rồi ghi chi tiết vào `df.attrs["finlens"]["partial"]` và `df.attrs["finlens"]["failed"]` (mã chứng khoán ánh xạ sang `code` và `message`). Đặt `on_error="raise"` thì nhận `PartialFetchError`, ngoại lệ này mang theo phần đã lấy được ở `.data` và ánh xạ mã sang ngoại lệ ở `.failures`, nên không phải gọi lại từ đầu. `on_error="ignore"` giữ dữ liệu và không cảnh báo gì.

Lỗi xác thực chứng chỉ TLS khi kết nối tới FinLens sửa thế nào?

Đó là `finlens.TlsVerificationError`, mã `FL_TRANSPORT_TLS`, thường gặp khi máy bạn nằm sau proxy kiểm tra TLS hoặc phần mềm diệt virus dùng CA riêng. Cách sửa là trỏ thư viện tới CA bundle do bộ phận IT cấp: `finlens.client(ca_bundle="/duong/dan/ca-bundle.pem")`, hoặc đặt biến môi trường `FINLENS_CA_BUNDLE`. Đây là lỗi vĩnh viễn nên thư viện không thử lại và nó cũng không làm mở circuit breaker. Lớp này kế thừa `ConnectionFailedError`, nên code đang bắt lớp cha không phải sửa gì.

Code cũ viết except ValueError quanh lời gọi finlens có còn chạy không?

Còn. `finlens.ValidationError` kế thừa cả `FinLensError` lẫn `ValueError`, và đó là chủ đích để code viết cho bản cũ không gãy khi nâng lên 1.x: các lỗi tham số như `InvalidSymbolError`, `InvalidDateRangeError`, `InvalidIntervalError`, `CompanyTypeMismatchError` và `UnitMismatchError` vẫn lọt vào `except ValueError`. Khi viết mới thì nên bắt `finlens.ValidationError` — nó chỉ bắt lỗi tham số của finlens chứ không nuốt luôn `ValueError` từ code khác của bạn.

Cập nhật lần cuối

Nội dung trang