一、初始化
from bs4 import BeautifulSoup
soup = BeautifulSoup("<html>A Html Text</html>", "html.parser")
兩個參數:第一個參數是要解析的html文本,第二個參數是使用那種解析器,對於HTML來講就是html.parser,這個是bs4自帶的解析器。
如果一段HTML或XML文檔格式不正確的話,那么在不同的解析器中返回的結果可能是不一樣的。
soup.prettify() # prettify 有括號和沒括號都可以格式化輸出
二、對象
Beautfiful Soup將復雜HTML文檔轉換成一個復雜的樹形結構,每個節點都是Python對象,所有對象可以歸納為4種:tag,NavigableString,BeautifulSoup,Comment。
1、tag
Tag對象與 xml 或 html 原生文檔中的 tag 相同。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>
如果不存在,則返回 None,如果存在多個,則返回第一個。
Name
每個 tag 都有自己的名字
tag.name
# 'b'
Attributes
tag['class']
# 'boldest'
tag.attrs
# {'class': 'boldest'}
type(tag.attrs)
# <class 'dict'>
多值屬性
最常見的多值屬性是class,多值屬性的返回 list。
soup = BeautifulSoup('<p class="body strikeout"></p>')
print(soup.p['class']) # ['body', 'strikeout']
print(soup.p.attrs) # {'class': ['body', 'strikeout']}
如果某個屬性看起來好像有多個值,但在任何版本的HTML定義中都沒有被定義為多值屬性,那么Beautiful Soup會將這個屬性作為字符串返回。
soup = BeautifulSoup('<p id="my id"></p>', 'html.parser')
print(soup.p['id']) # 'my id'
Text
text 屬性返回 tag 的所有字符串連成的字符串。
其他方法
tag.has_attr('id') # 返回 tag 是否包含 id 屬性
當然,以上代碼還可以寫成 'id' in tag.attrs,之前說過,tag 的屬性是一個字典。順便提一下,has_key是老舊遺留的api,為了支持2.2之前的代碼留下的。Python3已經刪除了該函數。
2、NavigableString
字符串常被包含在 tag 內,Beautiful Soup 用 NavigableString 類來包裝 tag 中的字符串。但是字符串中不能包含其他 tag。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
s = soup.b.string
print(s) # Extremely bold
print(type(s)) # <class 'bs4.element.NavigableString'>
3、BeautifulSoup
BeautifulSoup 對象表示的是一個文檔的全部內容。大部分時候,可以把它當作 Tag 對象。但是 BeautifulSoup 對象並不是真正的 HTM L或 XML 的 tag,它沒有attribute屬性,name 屬性是一個值為“[document]”的特殊屬性。
4、Comment
Comment 一般表示文檔的注釋部分。
soup = BeautifulSoup("<b><!--This is a comment--></b>")
comment = soup.b.string
print(comment) # This is a comment
print(type(comment)) # <class 'bs4.element.Comment'>
三、遍歷
1、子節點
contents 屬性
contents 屬性返回所有子節點的列表,包括 NavigableString 類型節點。如果節點當中有換行符,會被當做是 NavigableString 類型節點而作為一個子節點。
NavigableString 類型節點沒有 contents 屬性,因為沒有子節點。
soup = BeautifulSoup("""<div>
<span>test</span>
</div>
""")
element = soup.div.contents
print(element) # ['\n', <span>test</span>, '\n']
children 屬性
children 屬性跟 contents 屬性基本一樣,只不過返回的不是子節點列表,而是子節點的可迭代對象。
descendants 屬性
descendants 屬性返回 tag 的所有子孫節點。
string 屬性
如果 tag 只有一個 NavigableString 類型子節點,那么這個 tag 可以使用 .string 得到子節點。
如果一個 tag 僅有一個子節點,那么這個 tag 也可以使用 .string 方法,輸出結果與當前唯一子節點的 .string 結果相同。
如果 tag 包含了多個子節點,tag 就無法確定 .string 方法應該調用哪個子節點的內容, .string 的輸出結果是 None。
soup = BeautifulSoup("""<div>
<p><span><b>test</b></span></p>
</div>
""")
element = soup.p.string
print(element) # test
print(type(element)) # <class 'bs4.element.NavigableString'>
特別注意,為了清楚顯示,一般我們會將 html 節點換行縮進顯示,而在BeautifulSoup 中會被認為是一個 NavigableString 類型子節點,導致出錯。上例中,如果改成 element = soup.div.string 就會出錯。
strings 和 stripped_strings 屬性
如果 tag 中包含多個字符串,可以用 strings 屬性來獲取。如果返回結果中要去除空行,則可以用 stripped_strings 屬性。
soup = BeautifulSoup("""<div>
<p> </p>
<p>test 1</p>
<p>test 2</p>
</div>
""", 'html.parser')
element = soup.div.stripped_strings
print(list(element)) # ['test 1', 'test 2']
2、父節點
parent 屬性
parent 屬性返回某個元素(tag、NavigableString)的父節點,文檔的頂層節點的父節點是 BeautifulSoup 對象,BeautifulSoup 對象的父節點是 None。
parents 屬性
parent 屬性遞歸得到元素的所有父輩節點,包括 BeautifulSoup 對象。
3、兄弟節點
next_sibling 和 previous_sibling
next_sibling 返回后一個兄弟節點,previous_sibling 返回前一個兄弟節點。直接看個例子,注意別被換行縮進攪了局。
soup = BeautifulSoup("""<div>
<p>test 1</p><b>test 2</b><h>test 3</h></div>
""", 'html.parser')
print(soup.b.next_sibling) # <h>test 3</h>
print(soup.b.previous_sibling) # <p>test 1</p>
print(soup.h.next_sibling) # None
next_siblings 和 previous_siblings
next_siblings 返回后面的兄弟節點
previous_siblings 返回前面的兄弟節點
4、回退和前進
把html解析看成依次解析標簽的一連串事件,BeautifulSoup 提供了重現解析器初始化過程的方法。
next_element 屬性指向解析過程中下一個被解析的對象(tag 或 NavigableString)。
previous_element 屬性指向解析過程中前一個被解析的對象。
另外還有next_elements 和 previous_elements 屬性,不贅述了。
四、搜索
1、過濾器
介紹 find_all() 方法前,先介紹一下過濾器的類型,這些過濾器貫穿整個搜索的API。過濾器可以被用在tag的name中,節點的屬性中,字符串中或他們的混合中。
示例使用的 html 文檔如下:
html = """
<div>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a></p>
</div>
"""
soup = BeautifulSoup(html, 'html.parser')
字符串
查找所有的<b>標簽
soup.find_all('b') # [<b>The Dormouse's story</b>]
正則表達式
傳入正則表達式作為參數,返回滿足正則表達式的標簽。下面例子中找出所有以b開頭的標簽。
soup.find_all(re.compile("^b")) # [<b>The Dormouse's story</b>]
列表
傳入列表參數,將返回與列表中任一元素匹配的內容。下面例子中找出所有<a>標簽和<b>標簽。
soup.find_all(["a", "b"])
True
True可以匹配任何值,下面的代碼查找到所有的tag,但是不會返回字符串節點。
soup.find_all(True)
方法
如果沒有合適過濾器,那么還可以自定義一個方法,方法只接受一個元素參數,如果這個方法返回True表示當前元素匹配被找到。下面示例返回所有包含 class 屬性但不包含 id 屬性的標簽。
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
print(soup.find_all(has_class_but_no_id))
返回結果:
[<p class="title"><b>The Dormouse's story</b></p>, <p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a></p>]
這個結果乍一看不對,<a>標簽含有 id 屬性,其實返回的 list 中只有2個元素,都是<p>標簽,<a>標簽是<p>標簽的子節點。
2、find 和 find_all
搜索當前 tag 的所有 tag 子節點,並判斷是否符合過濾器的條件
語法:
find(name=None, attrs={}, recursive=True, text=None, **kwargs)
find_all(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
參數:
name:查找所有名字為 name 的 tag,字符串對象會被自動忽略掉。上面過濾器示例中的參數都是 name 參數。當然,其他參數中也可以使用過濾器。
attrs:按屬性名和值查找。傳入字典,key 為屬性名,value 為屬性值。
recursive:是否遞歸遍歷所有子孫節點,默認 True。
text:用於搜索字符串,會找到 .string 方法與 text 參數值相符的tag,通常配合正則表達式使用。也就是說,雖然參數名是 text,但實際上搜索的是 string 屬性。
limit:限定返回列表的最大個數。
kwargs:如果一個指定名字的參數不是搜索內置的參數名,搜索時會把該參數當作 tag 的屬性來搜索。這里注意,如果要按 class 屬性搜索,因為 class 是 python 的保留字,需要寫作 class_。
Tag 的有些屬性在搜索中不能作為 kwargs 參數使用,比如 html5 中的 data-* 屬性。
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
print(data_soup.find_all(data-foo="value"))
# SyntaxError: keyword can't be an expression
但是可以通過 attrs 參數傳遞:
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
print(data_soup.find_all(attrs={"data-foo": "value"}))
# [<div data-foo="value">foo!</div>]
而按 class_ 查找時,只要一個CSS類名滿足即可,如果寫了多個CSS名稱,那么順序必須一致,而且不能跳躍。以下示例中,前三個可以查找到元素,后兩個不可以。
css_soup = BeautifulSoup('<p class="body bold strikeout"></p>')
print(css_soup.find_all("p", class_="strikeout"))
print(css_soup.find_all("p", class_="body"))
print(css_soup.find_all("p", class_="body bold strikeout"))
# [<p class="body strikeout"></p>]
print(css_soup.find_all("p", class_="body strikeout"))
print(css_soup.find_all("p", class_="strikeout body"))
# []
3、像調用find_all()一樣調用tag
find_all() 幾乎是 BeautifulSoup 中最常用的搜索方法,所以我們定義了它的簡寫方法。BeautifulSoup 對象和 tag 對象可以被當作一個方法來使用,這個方法的執行結果與調用這個對象的 find_all() 方法相同,下面兩行代碼是等價的:
soup.find_all('b')
soup('b')
4、其他搜索方法
find_parents() 返回所有祖先節點
find_parent() 返回直接父節點
find_next_siblings() 返回后面所有的兄弟節點
find_next_sibling() 返回后面的第一個兄弟節點
find_previous_siblings() 返回前面所有的兄弟節點
find_previous_sibling() 返回前面第一個兄弟節點
find_all_next() 返回節點后所有符合條件的節點
find_next() 返回節點后第一個符合條件的節點
find_all_previous() 返回節點前所有符合條件的節點
find_previous() 返回節點前所有符合條件的節點
五、CSS選擇器
BeautifulSoup支持大部分的CSS選擇器,這里直接用代碼來演示。
from bs4 import BeautifulSoup html = """ <html> <head><title>標題</title></head> <body> <p class="title" name="dromouse"><b>標題</b></p> <div name="divlink"> <p> <a href="http://example.com/1" class="sister" id="link1">鏈接1</a> <a href="http://example.com/2" class="sister" id="link2">鏈接2</a> <a href="http://example.com/3" class="sister" id="link3">鏈接3</a> </p> </div> <p></p> <div name='dv2'></div> </body> </html> """ soup = BeautifulSoup(html, 'lxml') # 通過tag查找 print(soup.select('title')) # [<title>標題</title>] # 通過tag逐層查找 print(soup.select("html head title")) # [<title>標題</title>] # 通過class查找 print(soup.select('.sister')) # [<a class="sister" href="http://example.com/1" id="link1">鏈接1</a>, # <a class="sister" href="http://example.com/2" id="link2">鏈接2</a>, # <a class="sister" href="http://example.com/3" id="link3">鏈接3</a>] # 通過id查找 print(soup.select('#link1, #link2')) # [<a class="sister" href="http://example.com/1" id="link1">鏈接1</a>, # <a class="sister" href="http://example.com/2" id="link2">鏈接2</a>] # 組合查找 print(soup.select('p #link1')) # [<a class="sister" href="http://example.com/1" id="link1">鏈接1</a>] # 查找直接子標簽 print(soup.select("head > title")) # [<title>標題</title>]
print(soup.select("p > #link1")) # [<a class="sister" href="http://example.com/1" id="link1">鏈接1</a>] print(soup.select("p > a:nth-of-type(2)")) # [<a class="sister" href="http://example.com/2" id="link2">鏈接2</a>] # nth-of-type 是CSS選擇器 # 查找兄弟節點(向后查找) print(soup.select("#link1 ~ .sister")) # [<a class="sister" href="http://example.com/2" id="link2">鏈接2</a>, # <a class="sister" href="http://example.com/3" id="link3">鏈接3</a>] print(soup.select("#link1 + .sister")) # [<a class="sister" href="http://example.com/2" id="link2">鏈接2</a>] # 通過屬性查找 print(soup.select('a[href="http://example.com/1"]')) # ^ 以XX開頭 print(soup.select('a[href^="http://example.com/"]')) # * 包含 print(soup.select('a[href*=".com/"]')) # 查找包含指定屬性的標簽 print(soup.select('[name]')) # 查找第一個元素 print(soup.select_one(".sister"))