DEV Community

Cover image for Viết script Python đo mật độ và vị trí từ khóa trong một bài HTML
Onic Ai
Onic Ai

Posted on Originally published at onic.vn

Viết script Python đo mật độ và vị trí từ khóa trong một bài HTML

Đội nội dung thường hỏi đội kỹ thuật một câu: "Có cách nào kiểm tra nhanh từ khóa trong bài có đặt đúng chỗ không, mà không phải cài thêm plugin?". Bài này dựng một script Python nhỏ làm đúng việc đó: đo mật độ từ khóa và kiểm tra từ khóa có mặt ở các vị trí quan trọng của một file HTML. Trọng tâm không chỉ là code, mà là lý do thiết kế: vì sao script ưu tiên kiểm tra vị trí hơn đếm số lần.

Bài toán: đo cái gì

Mật độ từ khóa (keyword density) là tỷ lệ số lần từ khóa xuất hiện so với tổng số chữ của bài. Bài 1.000 chữ có từ khóa xuất hiện 10 lần thì mật độ là 1%. Đây là thứ dễ tính nhất, nhưng không phải thứ quan trọng nhất.

Google không có con số mật độ chuẩn. Hướng dẫn SEO cơ bản của Google nói không có số chữ thần kỳ nào, và việc lặp đi lặp lại cùng một từ, kể cả biến thể, vi phạm chính sách spam về nhồi nhét từ khóa. Thang 0,5–3% mà Yoast SEO dùng để báo xanh là thang của plugin. Vì vậy script này coi mật độ là đèn cảnh báo: chỉ báo khi vượt khoảng 3%.

Thứ script thật sự cần kiểm tra là vị trí. Một từ khóa chính đặt đúng 5–7 chỗ quan trọng có tác dụng hơn 30 lần rải khắp bài.

Các vị trí cần kiểm tra

Vị trí được chia thành ba tầng theo mức độ bắt buộc:

  • Bắt buộc: SEO title, URL (slug), đoạn mở đầu (100 chữ đầu).
  • Nên có: meta description, 1–2 heading, kết luận.
  • Tự nhiên: thân bài, alt ảnh, chú thích, không cần kiểm tra cứng.

Thiết kế này có nghĩa là script trả về hai loại kết quả: lỗi (thiếu ở tầng bắt buộc) và gợi ý (thiếu ở tầng nên có).

Chuẩn hóa tiếng Việt

Tiếng Việt có dấu, và URL thì không dấu. Để so khớp từ khóa với slug, cần một hàm bỏ dấu. Thư viện chuẩn của Python làm được việc này:

import re
import unicodedata

def bo_dau(s: str) -> str:
    s = s.replace("đ", "d").replace("Đ", "D")
    s = unicodedata.normalize("NFD", s)
    return "".join(c for c in s if unicodedata.category(c) != "Mn")

def slugify(s: str) -> str:
    return re.sub(r"[^a-z0-9]+", "-", bo_dau(s).lower()).strip("-")
Enter fullscreen mode Exit fullscreen mode

Với phần nội dung, script so khớp trên chữ thường nhưng giữ dấu, vì "từ khóa" và "tu khoa" là hai chuỗi khác nhau với người đọc.

Tách các phần của trang

Script dùng BeautifulSoup để tách title, meta description, heading, đoạn đầu và đoạn cuối:

from bs4 import BeautifulSoup

def tach_trang(html: str) -> dict:
    soup = BeautifulSoup(html, "html.parser")
    meta = soup.find("meta", attrs={"name": "description"})
    paras = [p.get_text(" ", strip=True) for p in soup.find_all("p")]
    text = soup.get_text(" ", strip=True)
    return {
        "title": soup.title.get_text(strip=True) if soup.title else "",
        "meta": meta.get("content", "") if meta else "",
        "headings": [h.get_text(" ", strip=True) for h in soup.find_all(["h2", "h3"])],
        "mo_dau": " ".join(text.split()[:100]),
        "ket_luan": paras[-1] if paras else "",
        "text": text,
    }
Enter fullscreen mode Exit fullscreen mode

Kiểm tra vị trí và mật độ

def kiem_tra(html: str, slug: str, keyword: str) -> dict:
    t = tach_trang(html)
    kw = keyword.lower()
    loi, goi_y = [], []
    if kw not in t["title"].lower():
        loi.append("SEO title thiếu từ khóa chính")
    if slugify(keyword) not in slug:
        loi.append("URL chưa chứa từ khóa chính")
    if kw not in t["mo_dau"].lower():
        loi.append("100 chữ đầu chưa có từ khóa chính")
    if kw not in t["meta"].lower():
        goi_y.append("Meta description chưa có từ khóa chính")
    if not any(kw in h.lower() for h in t["headings"]):
        goi_y.append("Chưa heading nào chứa từ khóa chính")
    if kw not in t["ket_luan"].lower():
        goi_y.append("Kết luận chưa nhắc lại từ khóa chính")
    words = len(t["text"].split())
    count = t["text"].lower().count(kw)
    density = count * len(kw.split()) / words * 100 if words else 0
    if density > 3:
        goi_y.append(f"Mật độ {density:.1f}%: kiểm tra câu lặp cụm")
    return {"loi": loi, "goi_y": goi_y, "so_lan": count, "mat_do": round(density, 2)}
Enter fullscreen mode Exit fullscreen mode

Lưu ý cách tính mật độ: từ khóa nhiều chữ được nhân với số chữ của nó. Mỗi plugin có thể tính khác nhau một chút, nên con số này chỉ để so sánh giữa các bài của cùng một website.

Những gì script không làm được

Script đếm chữ và kiểm tra vị trí. Nó không biết bài có đúng ý định tìm kiếm không, và cũng không biết hai bài trên cùng website có đang nhắm một từ khóa chính hay không. Lỗi thứ hai là lỗi tôi gặp nhiều nhất trong 14 năm làm SEO: hai, ba bài cùng nhắm một từ khóa rồi tự tranh nhau. Muốn bắt lỗi này, cần một bảng kế hoạch từ khóa cho cả website, với mỗi từ khóa chính gắn với đúng một URL.

Script cũng không kiểm tra từ đồng nghĩa và từ semantic, hai loại từ khóa giúp bài đọc tự nhiên và giúp máy hiểu ngữ cảnh. Phần lý thuyết về 4 loại từ khóa trong một bài, cùng bảng phân bổ theo từng vị trí, có trong bài từ khóa SEO là gì trên Onic.vn.

Một ca kiểm thử

Bộ test tốt nhất cho script là một bài bị nhồi từ khóa thật. Ví dụ một bài 800 chữ về "giày chạy bộ nữ" lặp cụm này 38 lần, mật độ gần 5%. Script sẽ báo mật độ cao. Sau khi viết lại, giữ cụm chính 6 lần ở title, URL, đoạn đầu, 2 heading và kết luận, script không còn báo mật độ, đồng thời mọi kiểm tra vị trí vẫn đạt.

Ca ngược lại cũng nên có trong bộ test: một bài 7.850 chữ có từ khóa 87 lần vẫn được Yoast chấm tốt vì từ khóa rải tự nhiên. Script không nên báo lỗi bài này. Nếu báo, ngưỡng của anh em đang đặt sai.

Mở rộng

Từ đây có thể thêm kiểm tra alt ảnh có chứa từ khóa khi ảnh đúng là về nó, hoặc chạy script trên toàn bộ file trong một thư mục rồi xuất CSV để đội nội dung lọc bài cần sửa. Giữ nguyên nguyên tắc thiết kế ban đầu: vị trí là lỗi, mật độ chỉ là cảnh báo.


Bài gốc: https://onic.vn/kien-thuc/tu-khoa-seo-la-gi/

Top comments (0)