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 strings
và stripped_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 contents
và children
có thể trở nên dư thừa khi đã có các phương pháp hiệu quả hơn như findAll(True)
.