Python: Tách câu trong văn bản ở định dạng HTML

Trong nỗ lực đi tìm từ mới, anh có ý định tách riêng từng câu trong văn bản để làm “ngữ cảnh” cho các tổ hợp có thể là từ trong câu đấy.

Văn bản ở định dạng HTML đã được xử lí khá sạch sẽ. Kiểu như này:

doc = """
<h1>Sao bảo nó không đến</h1>
<p><strong>Sao bảo nó không đến</strong>, sao nó đến không bảo?. <a href="#">Sao bảo</a> không "đến nó".</p>
<h2>Nó đến, không bảo sao?</h2>

<ol>
   <li>Bảo nó: đến không sao... Bảo đến sao nó không? Bảo! Không đến nó sao?</li>
   <li>Đến! Sao bảo nó không (đến nó sao không bảo)… Đến không bảo nó sao.</li>
</ol>

<blockquote><p>"Không sao, bảo nó đến!" Không bảo sao nó đến. Không đến... bảo nó sao?</p></blockquote>
<dl>
   <dt>Đến nó bảo không sao.</dt>
   <dd>Đến bảo nó không sao! Đến không? Bảo sao nó…</dd>
</dl>
"""

Mục tiêu là tách ra các câu ở dạng thô (plain text). Trong văn bản định dạng HTML có xen lẫn các thẻ “inline” (ví dụ: a, span, strong,…) lẫn các thẻ dạng “block” (ví dụ: p, table, td, ol, li, dt,…). Ở đây, các thẻ “inline” được nằm trong câu, còn thẻ “block” là ranh giới giữa các câu.

Do đó sau khi gom toàn bộ văn bản HTML thành một dòng:

doc = re.sub(r'\s+', ' ', doc)

thì anh sẽ loại bỏ tất cả các thẻ inline:

doc = re.sub(r"</?(a|i|b|span|strong|em|small|sub|sup|abbr|code|font)\s?([^>]+)?>", '', doc)

Như thế các thẻ còn lại sẽ là ranh giới giữa các câu. Anh chuyển tất cả các thẻ đó thành kí tự dòng mới:

doc = re.sub(r'(</?[a-z]+\s?([^>]+)?>){1,}', '\n', doc)

Thường cuối câu sẽ là các dấu câu: dấu chấm (.), dấu ba chấm (… hoặc …), dấu hỏi (?) và dấu chấm cảm (!). Như thế, anh cũng cẩn thận tách thành dòng mới ở các vị trí này:

doc = re.sub(r'([.\?!…]+\s)', '\\1\n', doc)

Thực tế là vẫn có trường hợp các dấu câu kia xuất hiện ở giữa câu. Với biểu thức chính quy trên thì anh đã loại các trường hợp dấu chấm trong vai trò kí tự phân cách hàng nghìn trong các con số. Những trường hợp còn lại thì phức tạp hơn, mà có vẻ ít gặp, nên anh tạm thời bỏ qua.

Tiếp theo, anh loại bỏ các dòng trắng:

doc = re.sub(r'\n\s+', '\n', doc)
doc = re.sub(r'\n+', '\n', doc.strip())

Cuối cùng là tách văn bản thành các câu ở dạng một biến list.

sentences = doc.split("\n")

Kiểm tra kết quả:

for s in sentences:
    print('---', s)
--- Sao bảo nó không đến, sao nó đến không bảo?. 
--- Sao bảo không "đến nó".
--- Nó đến, không bảo sao?
--- Bảo nó: đến không sao... 
--- Bảo đến sao nó không? 
--- Bảo! 
--- Không đến nó sao?
--- Đến! 
--- Sao bảo nó không (đến nó sao không bảo)… 
--- Đến không bảo nó sao.
--- "Không sao, bảo nó đến!" Không bảo sao nó đến. 
--- Không đến... 
--- bảo nó sao?
--- Đến nó bảo không sao.
--- Đến bảo nó không sao! 
--- Đến không? 
--- Bảo sao nó…

Ứng dụng vào Dự án S:

Liệt kê ngữ cảnh của tổ hợp (từ)