Việc này đôi khi gây thừa thãi - đại lý cá độ

Trong thư viện BeautifulSoup, khi bạn dùng contents hoặc children để duyệt qua các nút con của một phần tử, nếu phần tử đó chứa chuỗi văn bản, nó sẽ trả về cả đối tượng Tag lẫn đối tượng NavigableString. Đây là một tính năng khá phiền phức vì thường thì chúng ta chỉ cần lấy các đối tượng Tag, trong khi BeautifulSoup đã cung cấp sẵn các phương pháp stringsstripped_strings để lấy riêng các chuỗi từ nút. Việc này đôi khi gây thừa thãi.

Dưới đây, chúng ta sẽ phân tích kỹ hơn về vấn đề này thông qua ví dụ cụ thể với thuộc tính contents và đưa ra hai cách giải quyết.

Một ví dụ minh họa

Giả sử có đại lý cá độ đoạn mã XML sau:

 1<tab>
 2<tabletitle>
 3<title name="Công ty"/>
 4<title name="Ngành nghề"/>
 5<title name="Hoạt động kinh doanh"/>
 6<title name="Chủ tịch hội đồng quản trị"/>
 7<title name="Người kiểm soát cuối cùng"/>
 8</tabletitle>
 9<tablelist>
10<col con="Tập đoàn Điện khí Xuch继"/>
11<col con="Thiết bị điện"/>
12<col con="Nghiên cứu và bán thiết bị điện thứ cấp và thứ nhất"/>
13<col con="Tập đoàn Xuch继"/>
14<col con="Ủy ban Quản lý và Giám sát Tài sản Nhà nước Quốc vụ viện"/>
15</tablelist>
16<tablelist>
17<col con="Tập đoàn Hàng không Nam Phương Trung Quốc"/>
18<col con="Vận tải hàng không"/>
19<col con="Cung cấp dịch vụ vận chuyển hành khách, hàng hóa và bưu phẩm trong và ngoài nước"/>
20<col con="Tập đoàn Hàng không Nam Phương Trung Quốc"/>
21<col con="Ủy ban Quản lý và Giám sát Tài sản Nhà nước Quốc vụ viện"/>
22</tablelist>
23</tab>

Mục tiêu là chuyển đổi dữ liệu trên thành bảng thông thường để dễ dàng lưu trữ vào file Excel. Dưới đây là đoạn mã Python thực hiện công việc này:

 1from bs4 import BeautifulSoup
 2from_str = '''
 3<tab>
 4<tabletitle>
 5<title name="Công ty"/>
 6<title name="Ngành nghề"/>
 7<title name="Hoạt động kinh doanh"/>
 8<title name="Chủ tịch hội đồng quản trị"/>
 9<title name="Người kiểm soát cuối cùng"/>
10</tabletitle>
11<tablelist>
12<col con="Tập đoàn Điện khí Xuch继"/>
13<col con="Thiết bị điện"/>
14<col con="Nghiên cứu và bán thiết bị điện thứ cấp và thứ nhất"/>
15<col con="Tập đoàn Xuch继"/>
16<col con="Ủy ban Quản lý và Giám sát Tài sản Nhà nước Quốc vụ viện"/>
17</tablelist>
18<tablelist>
19<col con="Tập đoàn Hàng không Nam Phương Trung Quốc"/>
20<col con="Vận tải hàng không"/>
21<col con="Cung cấp dịch vụ vận chuyển hành khách, hàng hóa và bưu phẩm trong và ngoài nước"/>
22<col con="Tập đoàn Hàng không Nam Phương Trung Quốc"/>
23<col con="Ủy ban Quản lý và Giám sát Tài sản Nhà nước Quốc vụ viện"/>
24</tablelist>
25</tab>'''
26soup = BeautifulSoup(from_str, 'lxml-xml')
27tablelist = soup.findAll('tablelist')
28for i in tablelist:
29  for j in i.contents:
30    print(j['con'], end='\t')

Khi chạy đoạn mã trên, bạn sẽ gặp lỗi ở câu lệnh print:

1TypeError: string indices must be integers

Lỗi này xuất hiện vì kiểu dữ liệu string chỉ chấp nhận chỉ số nguyên. Để hiểu rõ hơn, hãy thay thế dòng print(j['con']) bằng print(type(j)) để kiểm tra kiểu dữ liệu của biến j. Kết quả cho thấy rằng j vừa là Tag vừa là NavigableString. Mặc dù XML không hiển thị rõ ràng bất kỳ chuỗi nào, nhưng giữa các thẻ có ký tự xuống dòng (\n), dẫn đến việc contents cũng lấy cả các đối tượng NavigableString.

Để khắc phục vấn đề này, chúng ta có hai cách tiếp cận phổ biến.

Hai phương pháp giải quyết

Phương pháp 1: Sử dụng isinstance để kiểm tra kiểu dữ liệu

Bạn có thể thêm điều kiện kiểm tra kiểu dữ liệu trong vòng lặp để loại bỏ các nút con có kiểu NavigableString. Đoạn mã như sau:

1from bs4 import NavigableString
2
3for i in tablelist:
4  for j in [xem bóng đá](/post/04997d8c02ad227f/)  i.contents:
5    if not isinstance(j, NavigableString):  # Kiểm tra kiểu dữ liệu
6      print(j['con'], end='\t')

Lưu ý rằng bạn cần nhập module NavigableString bằng cách thêm dòng from bs4 import NavigableString trước khi sử dụng.

Phương pháp 2: Sử dụng findAll(True)

Một cách khác là sử dụng phương thức findAll() với tham số True. Điều này đảm bảo rằng chỉ các đối tượng Tag mới được trả về:

1for i in tablelist:
2  for j in i.findAll(True):  # Chỉ trả về các nút con có kiểu Tag
3    print(j['con'], end='\t')

Cả hai phương pháp đều giúp bạn chỉ lấy các đối tượng Tag mà không bị ảnh hưởng bởi NavigableString. Tuy nhiên, phương pháp thứ hai thường được ưu tiên hơn vì nó gọn gàng hơn, giảm thiểu số lần lặp và không cần thêm import bổ sung.

Kết luận, trong nhiều trường hợp, việc sử dụng contentschildren có thể trở nên dư thừa khi đã có các phương pháp hiệu quả hơn như findAll(True).