Hướng dẫn beautifulsoup trong python

Thư viện Beautiful Soup

Bản gốc

BeautifulSoup là một thư viện Python dùng để lấy dữ liệu ra khỏi các file HTML và XML. Nó hoạt động cùng với các parser [trình phân tích cú pháp] cung cấp cho bạn các cách để điều hướng, tìm kiếm và chỉnh sửa trong parse tree [cây phân tích được tạo từ parser]. Nhờ các parser này nó đã giúp các lập trình viên tiết kiệm được nhiều giờ làm việc.

Elsie, Lacie and Tillie; and they lived at the bottom of a well.

...

"""

Cho chuỗi trên vào BeautifulSoup constructor nó sẽ tạo ra một BeautifulSoup object, đại diện cho tài liệu dưới dạng cấu trúc dữ liệu lồng nhau:


from bs4 import BeautifulSoup
soup = BeautifulSoup[html_doc, 'html.parser']

print[soup.prettify[]]
# 
#  
#   
# The Dormouse's story
#   
#  
#  
#   

# #     The Dormouse's story # #  

#  

# Once upon a time there were three little sisters; and their names #     were # # Elsie # # , # # Lacie # # and # # Tillie # # ; and they lived at the bottom of a well. #  

#  

# ... #  

#  #

Dưới đây là một số cách đơn giản để điều hướng trong cấu trúc dữ liệu trên:

soup.title
# The Dormouse's story

soup.title.name
# u'title'

soup.title.string
# u'The Dormouse's story'

soup.title.parent.name
# u'head'

soup.p
# 

The Dormouse's story

soup.p['class'] # u'title' soup.a # Elsie soup.find_all['a'] # [Elsie, # Lacie, # Tillie] soup.find[id="link3"] # Tillie

Một công việc khá phổ biến là lấy ra tất cả URL tìm thấy trong thẻ Elsie, Lacie and Tillie; and they lived at the bottom of a well.

...

""" from bs4 import BeautifulSoup soup = BeautifulSoup[html_doc, 'html.parser']

Ta sẽ sử dụng cái này làm ví dụ xuyên suốt trong phần này.

Đi xuống

Các Tag có thể chứa string hoặc một tag khác. Những phần tử như này gọi là tag con [tag's children]. BeautifulSoup cung cấp rất nhiều thuộc tính khác nhau để điều hướng và lặp qua các tag con này.

Lưu ý nữa là các Beautiful Soup string không hỗ trợ các thuộc tính này vì một string không thể có tag con.

Điều hướng bằng tên thẻ

Cách đơn giản nhất để điều hướng trong một parse tree là sử dụng tên của tag mà bạn muốn duyệt. Nếu bạn muốn duyệt tag , hãy dùng soup.head:

soup.head
# The Dormouse's story

soup.title
# The Dormouse's story

Bạn có thể sử dụng cách này nhiều lần để đi xuống một phần nhất định nằm sâu trong parse tree. Với code này, bạn sẽ lấy ra được thẻ bên dưới thẻ :

soup.body.b
# The Dormouse's story

Sử dụng tên thẻ như một thuộc tính ta sẽ chỉ lấy được thẻ đầu tiên bằng tên đó:

soup.a
# Elsie

Nếu bạn cần lấy hết tất cả các thẻ Elsie, # Lacie, # Tillie]

.content và .children

Một tag con được chứa trong một list được gọi là .contents:

head_tag = soup.head
head_tag
# The Dormouse's story

head_tag.contents
# [The Dormouse's story]

title_tag = head_tag.contents[0]
title_tag
# The Dormouse's story
title_tag.contents
# [u'The Dormouse's story']

Bản thân BeautifulSoup object cũng có một thẻ con. Trong trường hợp này, thẻ là thẻ con của BeautifulSoup object:

len[soup.contents]
# 1
soup.contents[0].name
# u'html'

Một string thì không có .content, bởi vì nó có chứa cái thứ gì đâu:

text = title_tag.contents[0]
text.contents
# AttributeError: 'NavigableString' object has no attribute 'contents'

Thay vì để chúng trong một list, ta có thể lặp qua một thẻ con bằng cách sử dụng .children generator:

for child in title_tag.children:
    print[child]
# The Dormouse's story

.descendants

Hai thuộc tính .contents và .children chỉ duyệt qua một phần tử con trực tiếp của thẻ. Chẳng hạn, thẻ có một thẻ con trực tiếp là thẻ .

head_tag.contents
# [The Dormouse's story]

Nhưng chính thẻ cũng có một phần tử con, là chuỗi “The Dormouse’s story”. Điều này cũng có nghĩa là chuỗi này cũng là một phần tử con của thẻ . Thuộc tính .descendants cho bạn lặp qua các thẻ con, bằng cách đệ quy: Phần tử con trực tiếp, phần tử con của phần tử con trực tiếp và vân vân:

for child in head_tag.descendants:
    print[child]
# The Dormouse's story
# The Dormouse's story

Thẻ chỉ có một phần tử con, nhưng nó có hai phần tử descendant [con cháu]: Đó là thẻ và phần tử con của thẻ . BeautifulSoup object chỉ có một phần tử con trực tiếp [thẻ ] nhưng nó có rất nhiều descendants:

len[list[soup.children]]
# 1
len[list[soup.descendants]]
# 25

.string

Nếu một thẻ chỉ có một phần tử con, và nó là một NavigableString, thì phần tử con đó sẽ được gọi ra bằng .string:

title_tag.string
# u'The Dormouse's story'

Nếu phần tử con của một thẻ là một thẻ khác và thẻ đó có một .string, thì thẻ cha được coi là có cùng .string với thẻ con của nó.

head_tag.contents
# [The Dormouse's story]

head_tag.string
# u'The Dormouse's story'

Nếu một thẻ chứa nhiều hơn một phần tử con, thì nó sẽ không hiểu .string nói đến cái gì. Vì vậy .string được định nghĩa là None:

print[soup.html.string]
# None

.strings and .stripped_strings

Nếu có nhiều hơn một phần tử con có trong một thẻ. Bạn cũng có thể lấy ra những string có trong những thẻ đó. Bằng cách sử dụng bộ tạo .strings:

for string in soup.strings:
    print[repr[string]]
# u"The Dormouse's story"
# u'\n\n'
# u"The Dormouse's story"
# u'\n\n'
# u'Once upon a time there were three little sisters; and their names were\n'
# u'Elsie'
# u',\n'
# u'Lacie'
# u' and\n'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# u'...'
# u'\n'

Những chuỗi này có xu hướng có thêm những khoảng trắng bổ xung, bạn có thể loại bỏ chúng bằng cách sử dụng bộ tạo .stripped_strings thay thế:

for string in soup.stripped_strings:
    print[repr[string]]
# u"The Dormouse's story"
# u"The Dormouse's story"
# u'Once upon a time there were three little sisters; and their names were'
# u'Elsie'
# u','
# u'Lacie'
# u'and'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'...'

Ở đây các chuỗi chứa toàn khoảng trắng sẽ bị bỏ qua, và khoảng trắng ở phần bắt đầu và kết thúc của một chuỗi cũng sẽ bị loại bỏ.

Đi lên

Tiếp tục với “family tree”, mỗi tag hay mỗi string đều có phần tử cha chứa nó.

.parent

Bạn có thể truy cập phần tử cha bằng thuộc tính .parent. Trong tài liệu ví dụ “Three Sisters”, thẻ là thẻ cha của thẻ :

title_tag = soup.title
title_tag
# The Dormouse's story
title_tag.parent
# The Dormouse's story

String trong thẻ title cũng có một phần tử cha chứa nó, đó là thẻ :

title_tag.string.parent
# The Dormouse's story

Phần tử cha chứa thẻ là một BeautifulSoup object:

html_tag = soup.html
type[html_tag.parent]
# 

Và .parent của BeautifulSoup object được định nghĩa là None:

print[soup.parent]
# None

.parents

Bạn có thể lặp qua tất cả các phần tử cha bằng .parents. Ví dụ này sử dụng .parents để đi từ một thẻ Elsie for parent in link.parents:     if parent is None:         print[parent]     else:         print[parent.name] # p # body # html # [document] # None

Đi ngang

Ta sẽ sử dụng ví dụ bên dưới:

sibling_soup = BeautifulSoup["Elsie
Lacie
Tillie

Bạn có thể nghĩ rằng .next_sibling của thẻ Elsie link.next_sibling # u',\n'

Thẻ Lacie

.next_siblings và .previous_siblings

Bạn có thể lặp qua tất cả các thẻ siblings [anh em] bằng .next_siblings và .previous_siblings:

for sibling in soup.a.next_siblings:
    print[repr[sibling]]
# u',\n'
# Lacie
# u' and\n'
# Tillie
# u'; and they lived at the bottom of a well.'
# None

for sibling in soup.find[id="link3"].previous_siblings:
    print[repr[sibling]]
# ' and\n'
# Lacie
# u',\n'
# Elsie
# u'Once upon a time there were three little sisters; and their names were\n'
# None

Đi tới đi lùi

Hãy nhìn vào phần đầu của tài liệu “three sisters”:

The Dormouse's story

The Dormouse's story

HTML parser lấy chuỗi ký tự này và biến nó thành một chuỗi các sự kiện: "open an tag", "open a tag", "open a tag", "add a string", "close the tag", "open a

tag", và vân vân. Beautiful Soup cung cấp các công cụ để xây dựng lại initial parse của tài liệu.

.next_element và .previous_element

Thuộc tính .next_element của một chuỗi hoặc một tag trỏ tới bất cứ thứ gì đã được parse ngay sau đó. Nó có thể giống như .next_sibling, nhưng nó thường rất khác nhau.

Ở đây là thẻ Tillie last_a_tag.next_sibling # '; and they lived at the bottom of a well.

Nhưng .next_element của thẻ Tillie

.next_elements và previous_elements

Bằng cách này bạn có thể duyệt tiến tới hoặc duyệt lùi trong tài liệu được phân tích:

for element in last_a_tag.next_elements:
    print[repr[element]]
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# 

...

# u'...' # u'\n' # None

Tìm kiếm trong cây

Beautiful Soup định nghĩa rất nhiều phương thức dùng để tìm kiếm trong parse tree, nhưng chúng rất giống nhau. Mình sẽ dành nhiều thời gian để giải thích về hai phương thức phổ biến nhất: find[] và find_all[]. Các phương thức khác có các đối số gần như giống nhau vì thế mình sẽ chỉ trình bày ngắn gọn về chúng.

Một lần nữa, ta sẽ sử dụng tài liệu “three sisters” làm ví dụ:

html_doc = """
The Dormouse's story

The Dormouse's story

Once upon a time there were three little sisters; and their names were Elsie, Lacie and Tillie; and they lived at the bottom of a well.

...

""" from bs4 import BeautifulSoup soup = BeautifulSoup[html_doc, 'html.parser']

Bằng cách truyền vào một hàm như find_all[] một bộ lọc với đối số, ta có thể đi tới một phần trong tài liệu mà bạn quan tâm.

Các loại bộ lọc

Trước khi nói chi tiết về phương thức find_all[] và các phương thức tương tự. Mình muốn show cho các bạn các ví dụ về các bộ lọc khác nhau mà bạn có thể truyền vào các phương thức này. Các bộ lọc này xuất hiện nhiều lần trong suốt search API. Bạn có thể sử dụng chúng để lọc dựa trên tên của một tag, thuộc tính của nó, văn bản trong một chuỗi, hoặc kết hợp lại các thứ trên.

Chuỗi

Đây là bộ lọc đơn giản nhất. Truyền một chuỗi vào một phương thức tìm kiếm và BeautifulSoup sẽ thực hiện so khớp chính xác với chuỗi đó. Đoạn code này tìm tất cả thẻ có trong tài liệu:

soup.find_all['b']
# [The Dormouse's story]

Nếu bạn truyền vào một byte string, Beautiful Soup sẽ cho rằng chuỗi được mã hóa UTF-8. Thay vào đó bạn có thể tránh điều này bằng cách truyền vào một Unicode string thay thế.

Regular Expression

Nếu bạn truyền vào một regular expression object, Beautiful Soup sẽ lọc theo regular expression đó bằng cách sử dụng phương thức search[] của nó. Đoạn code này sẽ tìm tất cả các thẻ có tên bắt đầu bằng chữ “b”; trong trường hợp này là thẻ và thẻ :

import re
for tag in soup.find_all[re.compile["^b"]]:
    print[tag.name]
# body
# b

Đoạn code này sẽ tìm tất cả các thẻ mà trong tên có chữ “t”:

for tag in soup.find_all[re.compile["t"]]:
    print[tag.name]
# html
# title

List

Nếu bạn truyền vào một list, Beautiful Soup sẽ cho phép một chuỗi match bất kì phần tử nào có trong list đó. Code này sẽ tìm tất cả thẻ Elsie, # Lacie, # Tillie]

True

Giá trị True sẽ so khớp tất cả. Đoạn code này sẽ tìm tất cả thẻ có trong tài liệu, nhưng không có text string nào:

for tag in soup.find_all[True]:
    print[tag.name]
# html
# head
# title
# body
# p
# b
# p
# a
# a
# a
# p

Function

Nếu không có matcher nào hợp với ý bạn, hãy định nghĩa một function có 1 param là tag [param này mặc định sẽ được truyền vào một element]. Hàm này trả về True nếu element đó match, và ngược lại trả về False.

Đây là một hàm, nó sẽ trả về True nếu trong một tag có định nghĩa thuộc tính class mà không định nghĩa thuộc tính id:

def has_class_but_no_id[tag]:
    return tag.has_attr['class'] and not tag.has_attr['id']

Truyền hàm này vào trong find_all[] và bạn sẽ chọn tất cả các thẻ

:

soup.find_all[has_class_but_no_id]
# [

The Dormouse's story

, #

Once upon a time there were...

, #

...

]

Hàm này chỉ chọn những thẻ

. Nó không chọn những thẻ Elsie, # Tillie]

Bạn có thể làm nó phức tạp hơn tùy theo mục đích của bạn. Đây là một hàm sẽ trả về True nếu một thẻ được bao quanh bởi những string object.

from bs4 import NavigableString
def surrounded_by_strings[tag]:
    return [isinstance[tag.next_element, NavigableString]
        and isinstance[tag.previous_element, NavigableString]]

for tag in soup.find_all[surrounded_by_strings]:
print tag.name
# p
# a
# a
# a
# p

Sau khi đã đọc hết các phần trên thì bạn đã sẵn sàng đến phần tiếp theo, chi tiết về các phương thức tìm kiếm.

find_all[]

find_all[name, attrs, recursive, string, limit, **kwargs]

Phương thức find_all[] sẽ duyệt qua tất cả các thẻ con và lấy tất cả các thẻ mà phù hợp với bộ lọc của bạn. Mình sẽ đưa ra một số ví dụ về các loại bộ lọc, như ví dụ dưới đây:

soup.find_all["title"]
# [The Dormouse's story]

soup.find_all["p", "title"]
# [

The Dormouse's story

] soup.find_all["a"] # [Elsie, # Lacie, # Tillie] soup.find_all[id="link2"] # [Lacie] import re soup.find[string=re.compile["sisters"]] # u'Once upon a time there were three little sisters; and their names were\n'

Bạn đã khá quen thuộc với một số ví dụ có trong ví dụ trên, nhưng có một số khá mới. Các giá trị truyền vào như string hay id chúng có ý nghĩa gì? Tại sao hàm find_all[“p”, “title”] sẽ tìm thẻ

cùng với CSS class “title”? Nào, ta cùng tìm hiểu về đối số của hàm find_all[].

Name argument

Truyền một giá trị vào name và bạn sẽ nói cho Beautiful Soup biết là chỉ xem xét những thẻ có tên là giá trị được truyền vào name. Các text string sẽ bị bỏ qua cũng như các thẻ có tên không khớp.

Đây là cách sử dụng đơn giản nhất:

soup.find_all["title"]
# [The Dormouse's story]

Xem lại phần Các loại bộ lọc, giá trị truyền vào name có thể là một chuỗi, biểu thức chính quy, list, function, hoặc True.

Keyword arguments

Bất kỳ đối số nào không có trong hàm find_all[] đều sẽ được chuyển thành một dạng bộ lọc trong thuộc tính của thẻ. Nếu bạn truyền vào một giá trị cho một đối số được gọi là id, Beautiful Soup sẽ lọc các thẻ có thuộc tính id với giá trị được chỉ định:

soup.find_all[id='link2']
# [Lacie]

Nếu bạn truyền một giá trị vào href, Beautiful Soup sẽ lọc dựa theo thuộc tính href:

soup.find_all[href="/redirect?Id=VV4DCjRYHnaSds2LlPoP7D5C1bH%2bvGxljiVtPSdQHhws6gq91nZVQ8U74LNWms1V"
# [Elsie]

Bạn cũng có thể lọc các thuộc tính dựa theo một chuỗi, biểu thức chính quy, list, function, hay bằng giá trị True:

Đoạn code này sẽ tìm tất cả các thẻ mà trong nó có thuộc tính id, bất kể giá trị trong nó là gì:

soup.find_all[id=True]
# [Elsie,
# Lacie,
# Tillie]

Bạn có thể lọc nhiều thuộc tính cùng một lúc bằng cách truyền vào nhiều hơn một keyword argument:

soup.find_all[href="/redirect?Id=VV4DCjRYHnaSds2LlPoP7IYWyJrlG8f8MIWKFhNe73RQef%2b%2fpwIqwlc%2bcVx%2bmwJJ" id='link1']
# [three]


Một vài thuộc tính, như data-* trong HTML 5, có tên không thể được sử dụng như tên của keyword arguments:

data_soup = BeautifulSoup['
foo!
'] data_soup.find_all[data-foo="value"] # SyntaxError: keyword can't be an expression

Bạn có thể sử dụng những thuộc tính này để lọc bằng cách đưa chúng vào một dict, dict này sẽ được truyền vào đối số attrs trong hàm find_all[]:

data_soup.find_all[attrs={"data-foo": "value"}]
# [
foo!
]

Bạn không thể sử dụng một keyword argument để tìm phần tử name trong HTML, vì Beautiful Soup sử dụng đối số name để chứa tên của các thẻ. Thay vào đó, bạn hãy đưa nó vào một dict thông qua đối số attrs:

name_soup = BeautifulSoup['']
name_soup.find_all[name="email"]
# []
name_soup.find_all[attrs={"name": "email"}]
# []

Tìm kiếm bằng class CSS

Thật hữu ích khi có thể tìm kiếm một tag bằng một class CSS cụ thể. Nhưng tên của thuộc tính CSS, “class”, nó trùng với từ khóa class trong Python. Sử dụng class như một keyword argument sẽ bắn ra syntax error. Từ Beautiful Soup 4.1.2, bạn có thể tìm kiếm bằng CSS class bằng cách sử dụng keyword argument class_:

soup.find_all["a", class_="sister"]
# [Elsie,
# Lacie,
# Tillie]

Giống như bất kỳ keyword argument nào, bạn có thể truyền vào class_ một chuỗi, một biểu thức chính quy, hàm hoặc True:

soup.find_all[class_=re.compile["itl"]]
# [

The Dormouse's story

] def has_sicharacters[css_class]:     return css_class is not None and len[css_class] == 6 soup.find_all[class_=has_sicharacters] # [Elsie, # Lacie, # Tillie]

Hãy nhớ rằng, một thẻ đơn có thể có nhiều giá trị trong thuộc tính "class" của nó. Khi bạn tìm kiếm một thẻ match với CSS class chỉ định, bạn có thể match bất kỳ CSS class nào của nó.

css_soup = BeautifulSoup['

'] css_soup.find_all["p", class_="strikeout"] # [

] css_soup.find_all["p", class_="body"] # [

]

Bạn cũng có thể tìm kiếm cách đưa y nguyên giá trị của thuộc tính class vào:

css_soup.find_all["p", class_="body strikeout"]
# [

]

Nhưng tìm ngược lại thì lại không hoạt động, ở đây chuỗi strikeout body bị đảo ngược so với body strikeout, cho nên nó không thể tìm thấy:

css_soup.find_all["p", class_="strikeout body"]
# []

Nếu bạn muốn tìm kiếm thẻ khớp hai hay nhiều hơn class CSS, bạn có thể dùng CSS selector:

css_soup.select["p.strikeout.body"]
# [

]

Trong các phiên bản cũ hơn của Beautiful Soup, thì không có shortcut class_, bạn có thể sử dụng mẹo attrs được đề cập bên trên. Tạo một dict chứa key là "class" và value là một chuỗi [hoặc một regular expression, hay bất cứ thứ gì] bạn muốn tìm:

soup.find_all["a", attrs={"class": "sister"}]
# [Elsie,
# Lacie,
# Tillie]

String argument

Với string bạn có thể tìm kiếm chuỗi thay cho thẻ. Như name và keyword arguments, bạn có thể truyền vào một chuỗi, một biểu thức chính quy, một list, một hàm, hoặc giá trị True. Dưới đây là một ví dụ:

soup.find_all[string="Elsie"]
# [u'Elsie']

soup.find_all[string=["Tillie", "Elsie", "Lacie"]]
# [u'Elsie', u'Lacie', u'Tillie']

soup.find_all[string=re.compile["Dormouse"]]
[u"The Dormouse's story", u"The Dormouse's story"]

def is_the_only_string_within_a_tag[s]:
    """Return True if this string is the only child of its parent tag."""
    return [s == s.parent.string]

soup.find_all[string=is_the_only_string_within_a_tag]
# [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']

Mặc dù string là để tìm chuỗi, bạn có thể kết hợp nó với các đối số tìm kiếm khác: Beautiful Soup sẽ tìm tất cả các thẻ có .string khớp với giá trị string. Đoạn code này sẽ tìm những thẻ Elsie]

string là một đối số mới trong Beautiful Soup 4.4.0. Trong các phiên bản trước đó nó được gọi là text:

soup.find_all["a", text="Elsie"]
# [Elsie]

Limit argument

find_all[] trả về tất cả những thẻ và chuỗi khớp với bộ lọc của bạn. Điều này có thể khá tốn thời gian nếu tài liệu của bạn lớn. Nếu bạn không cần lấy hết kết quả, bạn có thể truyền thêm một số cho đối số limit. Nó hoạt động giống như keyword LIMIT trong SQL. Nó nói cho Beautiful Soup biết rằng ngừng thu thập kết quả sau khi lấy được một số lượng nhất định.

Có ba link trong tài liệu "three sisters", nhưng đoạn code này chỉ tìm thấy hai:

soup.find_all["a", limit=2]
# [Elsie,
# Lacie]

Recursive argument

Nếu bạn gọi mytag.find_all[], Beautiful Soup sẽ kiểm tra tất cả các descendant [con cháu] của mytag, con của con của nó, vân vân. Nếu bạn muốn Beautiful Soup duyệt trực tiếp phần tử con, bạn có thể truyền recursive=False, không đệ quy tiếp vào các phần tử con của phần tử con. Hãy xem sự khác biệt dưới đây:

soup.html.find_all["title"]
# [The Dormouse's story]

soup.html.find_all["title", recursive=False]
# []

Đây là phần đó trong tài liệu:


  
    The Dormouse's story
  
...

Thẻ nằm bên dưới thẻ , nhưng nó không trực tiếp nằm dưới mà nó phải đi qua thẻ . Beautiful Soup tìm thấy thẻ khi nó được phép xem tất cả các descendant của thẻ nhưng khi recursive=False nó sẽ giới hạn Beautiful Soup ở phần tử con trực tiếp của thẻ vì thế nó sẽ không tìm thấy gì.

Beautiful Soup cung cấp nhiều phương thức tree-searching [sẽ được nói dưới dây], và hầu hết chúng đều có các đối số giống như find_all[]: name, attrs, string, limit, và keyword argument. Nhưng recursive thì khác: Nó chỉ có ở hai phương thức find_all[] và find[]. Truyền recursive=False vào một phương thức như find_parents[] sẽ không hữu ích.

Gọi một thẻ như gọi find_all[]

Vì find_all[] là phương thức phổ biến nhất trong Beautiful Soup search API, bạn có thể sử dụng nó một cách gọn hơn bằng cách lược bỏ nó. Nếu bạn coi BeautifulSoup object hoặc Tag object như thể nó là một hàm, thì nó cũng giống như gọi find_all[] trên object đó. Hai dòng code này là tương tự nhau:

soup.find_all["a"]
soup["a"]

Hai dòng này cũng vậy:

soup.title.find_all[string=True]
soup.title[string=True]

find[]

find[name, attrs, recursive, string, **kwargs]

Phương thức find_all[] quét toàn bộ tài liệu để tìm kiếm kết quả. Nhưng đôi khi bạn chỉ muốn tìm một kết quả. Nếu bạn biết một tài liệu chỉ có một thẻ , thì thật lãng phí thời gian để quét toàn bộ tài liệu để tìm kiếm thêm. Thay vì thêm limit=1 mỗi khi gọi find_all, bạn có thể sử dụng phương thức find[]. Hai dòng code này gần như giống nhau:

soup.find_all['title', limit=1]
# [The Dormouse's story]

soup.find['title']
# The Dormouse's story

Chỉ khác đó là find_all[] trả về một list chứa kết quả, và find[] thì trả về kết quả.

Nếu find_all[] không thể tìm thấy gì, nó sẽ trả về một list rỗng. Còn với find[], không tìm thấy gì thì nó trả về None:

print[soup.find["nosuchtag"]]
# None

Bạn có nhớ mẹo soup.head.title từ Duyệt bằng cách sử dụng tag name? Mẹo đó hoạt động bằng cách gọi find[] liên tục:

soup.head.title
# The Dormouse's story

soup.find["head"].find["title"]
# The Dormouse's story

find_parents[] và find_parent[]

find_parents[name, attrs, string, limit, **kwargs]
find_parent[name, attrs, string, **kwargs]

Mình đã dành nhiều thời gian để nói về find_all[] và find[]. Beautiful Soup API định nghĩa tới mười phương thức khác nhau để tìm kiếm trong cây. Nhưng đừng sợ. Năm trong số những phương thức đó cơ bản thì giống như find_all[], và năm phương thức còn lại thì tương tự find[]. Chỉ khác là những phần trong cây mà chúng tìm kiếm.

Đầu tiên ta sẽ bắt đầu với find_parents[] và find_parent[]. Nhớ rằng cách mà find_all[] và find[] hoạt động là đi xuống trong một cây. Nhìn vào những các descendant [thẻ con cháu] của thẻ. Hai phương thức này thì làm ngược lại: cách mà chúng hoạt động là đi lên trong một cây, nhìn vào các phần tử cha của một thẻ hoặc một chuỗi. Ta sẽ thử xem, bắt đầu là một chuỗi nằm sâu trong tài liệu “three sisters”:

a_string = soup.find[string="Lacie"]
a_string
# u'Lacie'

a_string.find_parents["a"]
# [Lacie]

a_string.find_parent["p"]
# 

Once upon a time there were three little sisters; and their names were # Elsie, # Lacie and # Tillie; # and they lived at the bottom of a well.

a_string.find_parents["p", class="title"] # []

Một trong ba thẻ Elsie first_link.find_next_siblings["a"] # [Lacie, # Tillie] first_story_paragraph = soup.find["p", "story"] first_story_paragraph.find_next_sibling["p"] #

...

find_previous_siblings[] và find_previous_sibling[]

find_previous_siblings[name, atts, string, limit, **kwargs]
find_previous_sibling[name, atts, string, **kwargs]

Hai phương thức này sử dụng .previous_siblings để lặp qua các siblings của phần tử đó ở trước nó trong cây. Phương thức find_previous_siblings[] trả về một list chứa tất cả các siblings phù hợp, và find_previous_sibling[] chỉ trả về một kết quả:

last_link = soup.find["a", id="link3"]
last_link
# Tillie

last_link.find_previous_siblings["a"]
# [Lacie,
# Elsie]

first_story_paragraph = soup.find["p", "story"]
first_story_paragraph.find_previous_sibling["p"]
# 

The Dormouse's story

find_all_next[] và find_next[]

find_all_next[name, attrs, string, limit, **kwargs]
find_next[name, attrs, string, **kwargs]

Hai phương thức này sử dụng .next_element để lặp qua bất cứ thẻ hay chuỗi nào ở phía sau nó trong tài liệu. Phương thức find_all_next[] trả về một list chứa tất cả các kết quả phù hợp, còn find_next[] chỉ trả về một kết quả:

first_link = soup.a
first_link
# Elsie

first_link.find_all_next[string=True]
# [u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
# u';\nand they lived at the bottom of a well.', u'\n\n', u'...', u'\n']

first_link.find_next["p"]
# 

...

Trong ví dụ đầu tiên, chuỗi “Elsie” xuất hiện, mặc dù ta bắt đầu từ thẻ Elsie first_link.find_all_previous["p"] # [

Once upon a time there were three little sisters; ...

, #

The Dormouse's story

] first_link.find_previous["title"] # The Dormouse's story

Việc gọi find_all_previous["p"] tìm thấy đoạn đầu tiên trong tài liệu [đoạn có class=”title”], nhưng nó tìm thấy tới hai đoạn, thẻ

có chứa thẻ Elsie, # Lacie, # Tillie] soup.select["html head title"] # [The Dormouse's story]

Tìm những tag con trực tiếp của tag khác:

soup.select["head > title"]
# [The Dormouse's story]

soup.select["p > a"]
# [Elsie,
# Lacie,
# Tillie]

soup.select["p > a:nth-of-type[2]"]
# [Lacie]

soup.select["p > #link1"]
# [Elsie]

soup.select["body > a"]
# []

Tìm các siblings của tag:

soup.select["#link1 ~ .sister"]
# [Lacie,
# Tillie]

soup.select["#link1 + .sister"]
# [Lacie]

Tìm thẻ bằng CSS class:

soup.select[".sister"]
# [Elsie,
# Lacie,
# Tillie]

soup.select["[class~=sister]"]
# [Elsie,
# Lacie,
# Tillie]

Tìm thẻ bằng CSS ID:

soup.select["#link1"]
# [Elsie]

soup.select["a#link2"]
# [Lacie]

Tìm thẻ match với bất kỳ selector nào nằm trong danh sách các selectors:

soup.select["#link1,#link2"]
# [Elsie,
# Lacie]

Kiểm tra sự tồn tại của một thuộc tính:

soup.select['a[href]']
# [Elsie,
# Lacie,
# Tillie]

Tìm thẻ bằng giá trị của thuộc tính:

soup.select['a[href="/redirect?Id=f%2fKgPq4IDV0SyEq0zfYr0L1x0DM4mpSt97%2ftYgbxlC3lW0cWzhMK4ll9WwwLq4Ce"
# [Elsie]

soup.select['a[href^="//example.com/"]']
# [Elsie,
# Lacie,
# Tillie]

soup.select['a[href$="tillie"]']
# [Tillie]

soup.select['a[href*=".com/el"]']
# [Elsie]

Ngoài ra cũng có phương thức select_one[] lấy ra thẻ đầu tiên match với selector:

soup.select_one[".sister"]
# Elsie

Nếu bạn parse XML định nghĩa namespaces, bạn có thể sử dụng chúng trong CSS selectors:

from bs4 import BeautifulSoup
xml = """
    I'm in namespace 1
    I'm in namespace 2
 """
soup = BeautifulSoup[xml, "xml"]

soup.select["child"]
# [I'm in namespace 1, I'm in namespace 2]

soup.select["ns1|child", namespaces=namespaces]
# [I'm in namespace 1]

Khi xử lý CSS selector sử dụng namespaces, Beautiful Soup sử dụng các từ viết tắt namespace nó đã tìm thấy khi parse tài liệu. Bạn có thể ghi đè chúng bằng cách truyền vào một dict chứa các từ viết tắt của riêng bạn:

namespaces = dict[first="//namespace1/", second="//namespace2/"]
soup.select["second|child", namespaces=namespaces]
# [I'm in namespace 2]

Công cụ CSS selector là một công cụ hữu ích cho những ai đã biết về cú pháp của CSS selector. Bạn có thể làm tất cả điều này với Beautiful Soup API. Và nếu CSS selector là tất cả những gì bạn cần, bạn nên parse tài liệu bằng lxml: Nó nhanh hơn rất nhiều. Nó cho phép bạn kết hợp CSS selectors với Beautiful Soup API.

Chỉnh sửa cây

Sức mạnh chính của Beautiful Soup là tìm kiếm trong parse tree, nhưng ngoài ra bạn cũng có thể chỉnh sửa tree và ghi lại những thay đổi của bạn thành một tài liệu HTML/XML mới.

Thay đổi tên thẻ và thuộc tính

Mình đã đề cập cái này trước đó, trong phần Attributes, nhưng ta sẽ ôn lại một chút, thay đổi giá trị của các thuộc tính, thêm thuộc tính và xóa thuộc tính:

soup = BeautifulSoup['Extremely bold']
tag = soup.b

tag.name = "blockquote"
tag['class'] = 'verybold'
tag['id'] = 1
tag
# 
Extremely bold
del tag['class'] del tag['id'] tag #
Extremely bold

Chỉnh sửa .string

Nếu bạn đặt giá trị cho thuộc tính .string của thẻ, nội dung của thẻ đó sẽ được thay thế bằng giá trị bạn truyền vào:

markup = '

Chủ Đề