Python cho học sinh: tự làm tool tạo mã QR cho bài thuyết trình trong 45 phút, không cần cài thêm thư viện
Đọc một đường link dài trước lớp gần như không bao giờ hiệu quả: người nghe gõ sai một ký tự là mở ra trang khác, còn bạn thì mất cả phút đọc từng chữ cái. Mã QR giải quyết đúng việc đó: bạn chiếu mã lên slide, cả lớp mở camera điện thoại quét một lần là tới đúng trang.
Bài này hướng dẫn bạn tự làm một tool nhỏ bằng Python để tạo mã QR cho bài thuyết trình: tạo một mã, tạo hàng loạt từ file CSV, chỉnh cỡ và màu, và kiểm tra mã quét được thật trước khi lên lớp. Các ảnh trong bài chỉ là sơ đồ minh họa — phần làm thật nằm ở code và lệnh có thể copy trực tiếp trong bài. Bạn chỉ cần Python có sẵn trên máy; tool dùng thêm một thư viện duy nhất là qrcode .
1. Mã QR chứa được gì, và vì sao mã càng dài càng khó quét
Mã QR không chứa “trang web” mà chứa một đoạn chữ: thường là một đường link, nhưng cũng có thể là một đoạn văn bản ngắn, thông tin Wi-Fi, hay thông tin liên hệ. Điện thoại quét mã, đọc đoạn chữ đó, rồi tự mở link hoặc tự điền thông tin.
Có hai điều quyết định mã có dễ quét hay không:
Độ dài nội dung: nội dung càng dài, mã càng dày đặc ô vuông, camera càng khó đọc, nhất là khi chiếu lên màn hình từ xa. Vì vậy link dùng cho thuyết trình nên ngắn gọn — dùng link gốc ngắn, hoặc rút gọn trước rồi mới tạo mã.
Mức sửa lỗi: mã QR có sẵn phần dữ liệu dự phòng để vẫn đọc được khi bị rách, bẩn hay che một phần. Chuẩn QR (ISO/IEC 18004) chia 4 mức: L chịu hỏng khoảng 7%, M khoảng 15%, Q khoảng 25%, H khoảng 30%. Mức càng cao, mã càng dày, nên chỉ tăng khi thật sự cần.
Ảnh minh họa: link trên máy tính chuyển thành mã QR, điện thoại quét mã rồi mở ra trang web.
2. Cài đặt và làm mã QR đầu tiên
Tool dùng thư viện qrcode (kèm Pillow để lưu ảnh PNG). Cài một lần bằng lệnh sau:
pip install "qrcode[pil]"
Nếu máy báo lỗi externally-managed-environment (thường gặp trên Linux), đừng dùng --break-system-packages vội — tạo môi trường riêng rồi cài vào đó:
python3 -m venv venv-qr venv-qr/bin/pip install "qrcode[pil]"
Sau đó lưu đoạn code dưới đây thành file qr.py , đặt cùng thư mục với file CSV (nếu có) ở phần 4:
#!/usr/bin/env python3 """Tao ma QR cho bai thuyet trinh. Mac dinh chi XEM TRUOC: in ma QR dang chu ASCII ra terminal + thong tin ve ma, KHONG ghi file. Them --go moi luu file PNG that. Cai dat (1 lan): pip install "qrcode[pil]" Vi du: python3 qr.py "https://learnixedu.site" python3 qr.py "https://learnixedu.site" --go --ten bai-thuyet-trinh python3 qr.py "https://learnixedu.site" --go --muc-do H --co 12 python3 qr.py --csv links.csv # xem truoc hang loat python3 qr.py --csv links.csv --go # luu hang loat """ import argparse import csv import re import sys from pathlib import Path try: import qrcode from qrcode.constants import ERROR_CORRECT_H, ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q except ImportError: sys.exit("Chua cai thu vien qrcode. Chay: pip install \"qrcode[pil]\"") MUC_DO = {"L": ERROR_CORRECT_L, "M": ERROR_CORRECT_M, "Q": ERROR_CORRECT_Q, "H": ERROR_CORRECT_H} # Muc do sua loi: L chiu hong ~7%, M ~15%, Q ~25%, H ~30% cua ma. def ten_file_an_toan(ten: str, mac_dinh: str) -> str: """Don gian hoa ten file: chi giu chu, so, gach ngang, gach duoi.""" ten = re.sub(r"[^a-zA-Z0-9_-]+", "-", ten.strip().lower()).strip("-") return ten or mac_dinh def tao_qr(noi_dung: str, muc_do: str, co_o: int, vien: int): """Tao doi tuong QR, tra ve (qr, so_o_ngang_doc).""" qr = qrcode.QRCode( version=None, # tu chon phien ban vua voi do dai noi dung error_correction=MUC_DO[muc_do], box_size=co_o, border=vien, ) qr.add_data(noi_dung) qr.make(fit=True) return qr, qr.modules_count def xem_truoc(noi_dung: str, muc_do: str, co_o: int, vien: int, ten: str): qr, so_o = tao_qr(noi_dung, muc_do, co_o, vien) rong_anh = (so_o + vien * 2) * co_o print(f"Noi dung: {noi_dung}") print(f"Do dai: {len(noi_dung)} ky tu | Muc sua loi: {muc_do} " f"| Phien ban QR: {qr.version} | Ma co {so_o}x{so_o} o") print(f"File se luu: {ten}.png | Anh {rong_anh}x{rong_anh} px " f"(co o {co_o}, vien {vien})") print("Ban xem truoc (chua luu file — them --go de luu):") qr.print_ascii(invert=False) return qr, rong_anh def luu_qr(noi_dung: str, muc_do: str, co_o: int, vien: int, mau: str, nen: str, duong_dan: Path) -> Path: qr, so_o = tao_qr(noi_dung, muc_do, co_o, vien) anh = qr.make_image(fill_color=mau, back_color=nen) duong_dan.parent.mkdir(parents=True, exist_ok=True) anh.save(duong_dan) return duong_dan def doc_csv(duong_dan: Path): """Doc file CSV, tra ve danh sach (ten, link).""" try: with duong_dan.open(newline="", encoding="utf-8-sig") as f: doc = list(csv.DictReader(f)) except FileNotFoundError: sys.exit(f"Khong tim thay file: {duong_dan}") if not doc or "ten" not in doc[0] or "link" not in doc[0]: sys.exit("CSV sai dinh dang. Dong dau phai la: ten,link") ket_qua = [] for i, dong in enumerate(doc, start=2): ten, link = (dong.get("ten") or "").strip(), (dong.get("link") or "").strip() if not ten and not link: continue if not link: print(f"Dong {i}: thieu link, bo qua.") continue ket_qua.append((ten or f"qr-{i}", link)) if not ket_qua: sys.exit("CSV khong co dong hop le nao.") return ket_qua def main(): p = argparse.ArgumentParser(description="Tao ma QR cho bai thuyet trinh.") p.add_argument("noi_dung", nargs="?", help="Link hoac doan chu muon ma hoa") p.add_argument("--go", action="store_true", help="Luu file PNG that (khong co --go thi chi xem truoc)") p.add_argument("--ten", default="qr", help="Ten file khi luu (mac dinh: qr)") p.add_argument("--thu-muc", default=".", help="Thu muc luu file PNG") p.add_argument("--muc-do", default="Q", choices=list(MUC_DO), help="Muc sua loi: L, M, Q, H (mac dinh: Q)") p.add_argument("--co", type=int, default=10, help="Co moi o QR, tinh theo pixel (mac dinh: 10)") p.add_argument("--vien", type=int, default=4, help="Vien trang quanh ma, tinh theo so o (mac dinh: 4)") p.add_argument("--mau", default="black", help="Mau ma QR (mac dinh: black)") p.add_argument("--nen", default="white", help="Mau nen (mac dinh: white)") p.add_argument("--csv", help="File CSV co 2 cot ten,link de lam hang loat") args = p.parse_args() if args.co < 1 or args.vien < 0: sys.exit("--co phai >= 1 va --vien phai >= 0.") if args.csv: danh_sach = doc_csv(Path(args.csv)) print(f"Tim thay {len(danh_sach)} link trong {args.csv}.") for ten, link in danh_sach: ten = ten_file_an_toan(ten, "qr") if args.go: duong_dan = Path(args.thu_muc) / f"{ten}.png" try: luu_qr(link, args.muc_do, args.co, args.vien, args.mau, args.nen, duong_dan) except Exception as e: print(f"{ten}: LOI — {e}") else: print(f"Da luu: {duong_dan}") else: print(f"- {ten}: {link}") if not args.go: print("Chua luu file nao. Them --go de luu tat ca.") return if not args.noi_dung: p.print_usage() sys.exit("Thieu noi dung. Vi du: python3 qr.py \"https://learnixedu.site\"") ten = ten_file_an_toan(args.ten, "qr") duong_dan = Path(args.thu_muc) / f"{ten}.png" try: if args.go: luu_qr(args.noi_dung, args.muc_do, args.co, args.vien, args.mau, args.nen, duong_dan) print(f"Da luu: {duong_dan}") else: xem_truoc(args.noi_dung, args.muc_do, args.co, args.vien, duong_dan.with_suffix("").as_posix()) except ValueError as e: sys.exit(f"Khong tao duoc ma QR: {e}") except Exception as e: sys.exit(f"Loi: {e}") if __name__ == "__main__": main()
3. Xem trước rồi mới lưu: chạy thử và kết quả thật
Tool này học theo cách làm của bài dọn file trước đó: mặc định chỉ xem trước, không ghi file . Chạy không có --go , tool in mã QR dạng ký tự ASCII ra terminal kèm thông tin về mã, để bạn kiểm tra trước; thêm --go mới lưu file PNG thật.
python3 qr.py "https://learnixedu.site"
Kết quả chạy thật (đã chạy thử bằng Python 3.12.3, thư viện qrcode ):
Noi dung: https://learnixedu.site Do dai: 23 ky tu | Muc sua loi: Q | Phien ban QR: 3 | Ma co 29x29 o File se luu: qr.png | Anh 370x370 px (co o 10, vien 4) Ban xem truoc (chua luu file — them --go de luu): [ma QR dang ky tu ASCII hien ra trong terminal] (chua co file PNG nao — dung nhu thiet ke)
Lưu file thật:
python3 qr.py "https://learnixedu.site" --go --ten bai-thuyet-trinh
Da luu: bai-thuyet-trinh.png
Để chắc mã quét được thật chứ không chỉ “nhìn giống mã QR”, mình đã giải mã lại các file PNG đã tạo bằng thư viện OpenCV ( cv2.QRCodeDetector ): cả 4 file đều giải ra đúng y nguyên đường link đã nhập — bai-thuyet-trinh.png và 3 file làm hàng loạt ở phần 4 đều khớp 100%. Bạn không cần OpenCV để dùng tool; ở nhà, cách kiểm tra đơn giản nhất vẫn là mở camera điện thoại quét thử file PNG vừa lưu, và quét thử ngay trên slide trước giờ thuyết trình.
4. Làm hàng loạt từ file CSV
Một bài thuyết trình thường cần nhiều mã: link bài viết, link tài liệu tham khảo, link form phản hồi. Thay vì gõ từng lệnh, chuẩn bị một file links.csv với đúng 2 cột ten,link :
ten,link bai-thuyet-trinh,https://learnixedu.site tai-lieu-tham-khao,https://learnixedu.site/blog form-phan-hoi,https://example.com/phan-hoi
Xem trước danh sách, rồi lưu hàng loạt:
python3 qr.py --csv links.csv python3 qr.py --csv links.csv --go --thu-muc qr-lo
Kết quả chạy thật:
Tim thay 3 link trong links.csv. - bai-thuyet-trinh: https://learnixedu.site - tai-lieu-tham-khao: https://learnixedu.site/blog - form-phan-hoi: https://example.com/phan-hoi Chua luu file nao. Them --go de luu tat ca. Tim thay 3 link trong links.csv. Da luu: qr-lo/bai-thuyet-trinh.png Da luu: qr-lo/tai-lieu-tham-khao.png Da luu: qr-lo/form-phan-hoi.png
Tool tự đổi tên file về dạng an toàn (chữ thường, dấu cách thành dấu - ), bỏ qua dòng trống, và báo rõ dòng nào thiếu link. Nếu file CSV sai header (ví dụ ghi ten,url thay vì ten,link ), tool dừng lại và báo CSV sai dinh dang. Dong dau phai la: ten,link thay vì tạo ra mã sai lặng lẽ.
5. Đặt mã QR lên slide sao cho cả lớp quét được
Mã tạo ra đúng chưa đủ — mã còn phải quét được từ cuối lớp. Bốn quy tắc thực tế:
Đủ lớn: mã nên chiếm một góc rõ rệt của slide, đừng thu nhỏ thành một ô tí hon ở mép. Trước giờ thuyết trình, đứng ở hàng ghế cuối và quét thử bằng điện thoại của chính bạn — quét được ở đó thì cả lớp quét được.
Để yên đủ lâu: chiếu slide có mã ít nhất trong lúc bạn nói tới phần đó, đừng lướt qua trong 3 giây. Người nghe cần thời gian mở camera và ngắm mã.
Giữ vùng trống quanh mã: tool mặc định để viền trắng 4 ô ( --vien 4 ) — đó cũng là khoảng trống tối thiểu mà chuẩn QR yêu cầu quanh mã. Đừng kéo mã sát mép slide, đừng để chữ hay hình khác đè lên viền đó.
Tương phản rõ, không méo: mã đậm trên nền sáng quét tốt nhất. Đừng kéo dãn ảnh mã theo một chiều, đừng đặt mã lên nền ảnh nhiều màu. Và luôn ghi kèm đường link bằng chữ ngay dưới mã — nếu hôm đó máy chiếu mờ hoặc mạng lớp chập chờn, người nghe vẫn có cách khác để mở trang.
Ảnh minh họa: mã QR đặt lớn ở góc slide, có khoảng trống trắng xung quanh, người ngồi cuối lớp vẫn quét được.
Muốn mã lớn và bền hơn, tăng cỡ ô và mức sửa lỗi khi lưu:
python3 qr.py "https://learnixedu.site" --go --ten qr-mau --mau "#1a5276" --nen white --co 12 --muc-do H
Đã chạy thử: file qr-mau.png màu xanh đậm #1a5276 trên nền trắng, mức sửa lỗi H, giải mã lại vẫn ra đúng https://learnixedu.site . Lưu ý: màu chỉ nên đậm trên nền sáng; mã màu nhạt, màu vàng trên nền trắng hay mã đảo ngược (sáng trên nền tối) rất dễ khiến camera không đọc được — nếu đổi màu, nhất định quét thử lại trước khi thuyết trình.
6. Mức sửa lỗi: khi nào mã rách vẫn quét được
Mặc định tool dùng mức Q (chịu hỏng khoảng 25%) — đủ cho slide chiếu trong lớp. Đổi mức bằng --muc-do :
L (khoảng 7%): mã gọn nhất, chỉ nên dùng khi mã hiển thị sắc nét trên màn hình và không ai chạm vào nó.
M (khoảng 15%): dùng chung cho hầu hết trường hợp.
Q (khoảng 25%, mặc định): nên dùng cho slide thuyết trình, vì màn chiếu có thể mờ, chói, hoặc người ngồi xa quét ở góc nghiêng.
H (khoảng 30%): dùng khi in mã ra giấy, dán ngoài trời, hoặc mã có thể bị gấp, dính bẩn.
Ảnh minh họa: mã rách một góc nhỏ vẫn quét được nhờ dữ liệu dự phòng; mã bị che mất một nửa thì không.
“Chịu hỏng 25%” không có nghĩa là che mất ô vuông định vị ở 3 góc thì vẫn quét được — 3 ô vuông lớn ở góc là thứ camera dùng để nhận ra mã, mất một ô đó là mã gần như không đọc được dù mức sửa lỗi có cao. Vì vậy đừng chèn logo hay hình vào giữa mã, và đừng cắt mất góc mã để “cho đẹp”.
7. Thêm: mã QR Wi-Fi — tiện, nhưng đừng dùng bừa
Cùng một tool có thể tạo mã Wi-Fi để cả lớp vào mạng mà không cần đọc mật khẩu to lên. Định dạng đoạn chữ cần mã hóa là:
WIFI:T:WPA;S:Ten-WiFi;P:mat-khau;;
python3 qr.py "WIFI:T:WPA;S:LopHoc-5A;P:mat-khau-vi-du;;" --go --ten wifi-lop
Đã chạy thử: giải mã lại file wifi-lop.png ra đúng y nguyên đoạn chữ Wi-Fi đã nhập. Nhưng có một cảnh báo quan trọng: mật khẩu nằm lộ thiên trong chính mã QR — ai quét được mã là có mật khẩu. Chỉ dùng cho mạng của chính lớp hoặc gia đình bạn, và tuyệt đối đừng đưa mật khẩu Wi-Fi của trường hay mật khẩu thật vào slide rồi chia sẻ slide đó ra ngoài.
8. Lỗi hay gặp và cách sửa
Chưa có file PNG sau khi chạy: bạn quên --go . Tool mặc định chỉ xem trước. Chạy lại cùng lệnh kèm --go .
pip báo externally-managed-environment : máy Linux đang khóa cài đặt trực tiếp vào Python hệ thống. Dùng môi trường riêng như phần 2, rồi chạy bằng venv-qr/bin/python qr.py ... .
Mã quá dày, quét mãi không được: nội dung quá dài. Rút ngắn link trước khi tạo mã, hoặc giảm mức sửa lỗi xuống M nếu mã đang ở H mà không cần in ấn.
Đổi màu xong quét không được: màu mã quá nhạt so với nền, hoặc nền không phải màu sáng. Quay về đen trên trắng để kiểm tra, rồi mới thử lại màu khác.
CSV báo sai định dạng: kiểm tra dòng đầu phải đúng ten,link , file lưu dạng UTF-8, và mỗi dòng có đủ cả tên lẫn link.
Quét trên slide không được nhưng quét file PNG thì được: lỗi ở khâu trình chiếu, không phải ở mã — mã quá nhỏ, bị méo do kéo dãn, hoặc slide chuyển qua quá nhanh. Phóng to mã, giữ tỉ lệ gốc, để slide lâu hơn.
Trước giờ thuyết trình, kiểm tra lại đúng 3 việc: quét thử mã từ hàng ghế cuối, kiểm tra link mở ra đúng trang, và chắc chắn đường link bằng chữ vẫn nằm ngay dưới mã. Làm xong 3 việc đó, mã QR mới thật sự là cầu nối thay vì một hình trang trí trên slide.
Nguồn tham khảo
Thư viện qrcode cho Python — tài liệu và hướng dẫn sử dụng trên PyPI: pypi.org/project/qrcode
Chuẩn mã QR ISO/IEC 18004 — các mức sửa lỗi L, M, Q, H và vùng trống tối thiểu 4 ô quanh mã.