Khảo sát thị trường POS Việt Nam
Khảo sát 5 nhà cung cấp POS phổ biến tại Việt Nam: crawl nguồn công khai bằng Playwright, kiểm định bằng Pandas rồi dựng biểu đồ so sánh.
- Python
- Playwright
- Pandas
- Jupyter
Trước khi khuyên một cửa hàng chọn phần mềm bán hàng, tôi muốn có số liệu thay vì cảm tính. Dự án này khảo sát năm nhà cung cấp POS phổ biến ở Việt Nam — KiotViet, Sapo, MISA CukCuk, iPOS.vn và PosApp — từ nguồn công khai, chụp tại ngày 23/07/2026.
Dữ liệu công khai không phải dữ liệu sạch#
Trang giá của các hãng được viết để bán hàng, không phải để so sánh. Sapo niêm yết gói Start Up 249.000đ/tháng nhưng mức 170.000đ chỉ có khi cam kết từ 2 năm. iPOS.vn công bố FABiBox theo giá khuyến mại còn hạn đến 30/09/2026, còn FABi đầy đủ không đăng giá. Trang thiết bị của PosApp vẫn ghi chú một đợt khuyến mại đã hết hạn từ 30/09/2025. Vì vậy phần lớn công sức nằm ở chỗ ghi lại điều kiện đi kèm từng con số, chứ không phải ở chỗ lấy con số.
Crawl có để lại dấu vết#
crawl_data.py chạy Playwright với Chromium, đọc danh sách từ data/sources.csv
(17 nguồn chính thức), kiểm tra robots.txt trước mỗi domain và mặc định chỉ mở hai
trang cùng lúc. Mỗi URL để lại bốn artifact: JSON máy đọc được (metadata, heading, bảng,
link, JSON-LD), Markdown cho tôi đọc lại, snapshot HTML hoặc PDF thô, và một gói prompt.
def save_record(record, output_dir, prompt, prompt_max_chars, save_html):
source_id = record["source"]["source_id"]
raw_html = record.pop("raw_html", None)
# Checksum của phần text: crawl lại lần sau là thấy ngay trang nào đã đổi.
record["content_sha256"] = hashlib.sha256(
record.get("text", "").encode("utf-8")
).hexdigest()
json_path.write_text(json.dumps(record, ensure_ascii=False, indent=2), encoding="utf-8")
markdown_path.write_text(render_markdown(record), encoding="utf-8")
prompt_path.write_text(
render_prompt_packet(record, prompt, prompt_max_chars), encoding="utf-8"
)Crawler còn tự flag những snapshot đáng ngờ: nội dung dưới 300 ký tự, chạm trần
--max-chars nên có thể bị cắt, hoặc trang có dấu hiệu CAPTCHA. Flag này đi thẳng vào
crawl_manifest.csv và vào gói prompt, để tôi không đọc nhầm một trang lỗi thành trang
thật.
LLM dùng để đọc, không dùng để quyết#
Thư mục prompts/ tồn tại vì bước trích xuất được giao cho một LLM: trang giá POS ở Việt
Nam là bảng HTML lộn xộn, parse bằng luật rất mau gãy. Prompt ràng buộc chặt — không suy
đoán ngoài nguồn, mỗi dữ kiện phải kèm trích đoạn bằng chứng và URL, giữ nguyên giá gốc,
phân biệt giá niêm yết với giá khuyến mại và giá quy đổi theo tháng, thiếu thì để null,
mọi field đều mang nhãn confidence.
Kiểm định trước khi vẽ#
app.py chạy validate_data trước mọi biểu đồ và ném lỗi thay vì vẽ ra một hình sai:
đúng năm nhà cung cấp với key duy nhất, mọi bảng phân tích phủ đủ cả năm, giá đầu vào và
giá thiết bị đều dương, ma trận năng lực đúng rubric đã công bố, mọi nguồn là HTTPS, nhãn
độ tin cậy giá chỉ nhận đúng hai giá trị.
def validate_data(tables: dict[str, pd.DataFrame]) -> list[str]:
errors: list[str] = []
# Rubric độ phủ chỉ chấp nhận 5 mức đã ghi trong docs/methodology.md.
feature_values = tables["features"][list(FEATURE_LABELS)].to_numpy(dtype=float)
if not np.isin(feature_values, [0.0, 0.5, 0.75, 0.8, 1.0]).all():
errors.append("Feature coverage values fall outside the documented rubric")
sources = tables["sources"]
invalid_urls = sources.loc[
~sources["url"].astype(str).str.startswith("https://"), "url"
].tolist()
if invalid_urls:
errors.append(f"All source URLs must use HTTPS: {invalid_urls}")
if set(tables["vendors"]["price_confidence"]) - {"Cao", "Trung bình"}:
errors.append("Unexpected price confidence label")
if errors:
raise ValueError("Data validation failed:\n- " + "\n- ".join(errors))
return checksHai notebook đóng nốt vòng lặp: 01_validate_data.ipynb chạy lại toàn bộ điều kiện,
còn 02_compare_prices.ipynb chốt cứng trung vị và trung bình bằng assert. Sửa một ô
giá mà quên cập nhật báo cáo thì notebook đỏ ngay.
Biểu đồ và cách chuẩn hóa#
app.py sinh ba biểu đồ: giá phần mềm đầu vào, mức đầu tư bộ phần cứng khởi điểm tham
chiếu, và ma trận độ phủ năng lực theo tám nhóm chức năng. Ma trận cố ý không cộng
thành một điểm tổng, vì trọng số phụ thuộc mô hình kinh doanh của từng cửa hàng; nó đo
mức độ thông tin được công khai chứ không phải chất lượng sản phẩm.
Kết quả#
- Tại ngày 23/07/2026, giá phần mềm đầu vào công khai trải từ 129.000đ đến 270.000đ/tháng, trung vị 199.000đ và trung bình 197.600đ
- Bộ phần cứng khởi điểm tham chiếu nằm trong khoảng 7,00–9,14 triệu đồng, trung vị 7,99 triệu, nhưng cấu hình không tương đương: combo Starter của CukCuk đã gồm một năm phần mềm
- Hai mức giá bị hạ độ tin cậy xuống "Trung bình" vì nguồn còn khuyến mại quá hạn hoặc chưa công khai đủ; điều đó được ghi ngay trong dữ liệu
- Số liệu chỉ có giá trị tại mốc ngày đã chụp; báo cáo dùng để lập shortlist và ngân sách sơ bộ, không thay cho báo giá của nhà cung cấp