BeautifulSoup4 模块中文文档
原文出處 ->?Beautiful Soup Documentation
目錄
一、前言
1.1 尋求幫助
二、快速開始
三、安裝 BeautifulSoup?
3.1 安裝完成后的問題
3.2 安裝解析器
四、如何使用
五、對象的種類
5.1 Tag(標簽)
5.1.1 Name(標簽名)
5.1.2 Attributes(屬性)
5.3 BeautifulSoup
5.4?Comments and other special strings(注釋及特殊字符串)
六、遍歷文檔樹
6.1 子節(jié)點(向下遍歷)
6.1.1 使用標簽名進行遍歷
6.1.2 .contents 和 .children
6.1.3 .descendants
6.1.4 .string
6.1.5 .strings 和 stripped_strings
6.2?父節(jié)點(向上遍歷)
6.2.1 .parent
6.2.2 .parents
6.3?兄弟節(jié)點(左右遍歷)
6.3.1 .next_sibling 和 .previous_sibling
6.3.2 .next_siblings 和 .previous_siblings
6.4?回退和前進
6.4.1 .next_element 和 .previous_element
6.4.2 .next_elements 和 .previous_elements
七、搜索文檔樹
7.1 幾種過濾器
7.1.1 字符串
7.1.2 正則表達式
7.1.3 列表
7.1.4 True
7.1.5 函數(shù)
7.2 find_all()
7.2.1 name 參數(shù)
7.2.2 keyword 參數(shù)
7.2.3 根據(jù) CSS 進行搜索
7.2.4 string 參數(shù)
7.2.5 limit 參數(shù)
7.2.6 recursive 參數(shù)
7.3?像調用 find_all() 一樣調用一個標簽
7.4?find() 方法
7.5 find_parents() 和 find_parent()
7.6?find_next_siblings() 和?find_next_sibling()
7.7?find_previous_siblings() 和 find_previous_sibling()
7.8?find_all_next() 和 find_next()
7.9?find_all_previous() 和 find_previous()
7.10 CSS選擇器
8 修改文檔樹
8.1 修改tag的名稱和屬性
8.2 修改 .string
8.3 append()
8.4 extend()
8.5 NevigableString() 和 .new_tag()
8.6 insert()
8.7 insert_before() 和 insert_after()
8.8 clear()
8.9 extract()
8.10 decompose()
8.11 replace_with()
8.12 wrap()
8.13 unwrap()
九、輸出
9.1 格式化輸出
9.2 壓縮輸出
9.3 輸出格式
9.4 get_text()
十、指定文檔解析器
10.1 解析器之間的區(qū)別
十一、編碼
11.1 輸出編碼
11.2 Unicode, dammit! (靠!)
11.2.1 智能引號
矛盾的編碼
十二、比較對象是否相等
十三、復制 Beautiful Soup 對象
十四、解析部分文檔
?
一、前言
BeautifulSoup 其實官方已經有了比較完善的中文文檔,但對初學者可能不是特別友好,所以這里試圖通過“添鹽加醋”的方式給大家二次解讀該文檔。
BeautifulSoup 是一個可以從 HTML 和 XML 文件中提取數(shù)據(jù)的 Python 庫。
它可以通過你最喜歡的解析器實現(xiàn)遍歷、查找和修改網頁數(shù)據(jù)的功能。
使用 BeautifulSoup 進行工作,至少可以幫你節(jié)省數(shù)小時甚至是數(shù)天的時間。
這篇文檔介紹了 BeautifulSoup4 中的所有主要特性,并附有生動的小例子。
在這里我(作者,下同)準備告訴你這個庫擅長做什么工作,它的原理是怎樣的,以及如何使用它……
反正你看完這篇文檔,就可以做到人湯合一的境界(BeautifulSoup 直譯過來就是美妙的湯)。
文檔中出現(xiàn)的例子在 Python2.7 和 Python3.x 中的執(zhí)行結果相同。
呃……你可能還在找 BeautifulSoup3 的文檔,不夠很遺憾,那個已經過時了,我們推薦你在現(xiàn)有的項目中使用 BeautifulSoup4,參考 移植到 BS4 章節(jié)內容。
1.1 尋求幫助
如果你有關于BeautifulSoup的問題,可以發(fā)送郵件到?討論組?.如果你的問題包含了一段需要轉換的HTML代碼,那么確保你提的問題描述中附帶這段HTML文檔的?代碼診斷?[1]
二、快速開始
下面的一段 HTML 將作為例子在本文中被多次引用。這是《愛麗絲夢游仙境》的一段內容:
>>> html_doc = """<html><head><title>睡鼠的故事</title></head> <body> <p class="title"><b>睡鼠的故事</b></p><p class="story">從前有三位小姐姐,她們的名字是: <a href="http://example.com/elsie" class="sister" id="link1">埃爾西</a>, <a href="http://example.com/lacie" class="sister" id="link2">萊斯</a>和 <a href="http://example.com/tillie" class="sister" id="link3">蒂爾莉</a>; 她們住在一個井底下面。</p><p class="story">...</p> """使用 BeautifulSoup 解析上面文檔,從而夠得到一個 BeautifulSoup 對象,它能夠按照文檔的嵌套結構輸出:
>>> from bs4 import BeautifulSoup >>> soup = BeautifulSoup(html_doc, "html.parser")>>> print(soup.prettify()) <html> <head><title>睡鼠的故事</title> </head> <body><p class="title"><b>睡鼠的故事</b></p><p class="story">從前有三位小姐姐,她們的名字是:<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>,<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>和<a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>; 她們住在一個井底下面。</p><p class="story">...</p> </body> </html>下面是幾種簡單地遍歷結構化數(shù)據(jù)的方法:
>>> soup.title <title>睡鼠的故事</title>>>> soup.title.name 'title'>>> soup.title.string '睡鼠的故事'>>> soup.title.parent.name 'head'>>> soup.p <p class="title"><b>睡鼠的故事</b></p>>>> soup.p['class'] ['title']>>> soup.a <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>>>> soup.find_all('a') [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]>>> soup.find(id='link3') <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>從文檔中找到所有?<a>?標簽的鏈接:
>>> for link in soup.find_all('a'):print(link.get('href'))http://example.com/elsie http://example.com/lacie http://example.com/tillie從文檔中獲取所有的文本:
>>> print(soup.get_text())睡鼠的故事睡鼠的故事 從前有三位小姐姐,她們的名字是: 埃爾西, 萊斯和 蒂爾莉; 她們住在一個井底下面。 ...這是你想要的操作嗎?如果是,請繼續(xù)往下學習……
三、安裝 BeautifulSoup?
如果你使用的是新版的 Debain 或 ubuntu,那么可以通過系統(tǒng)的軟件包管理來進行安裝:
$ apt-get install Python-bs4BeautifulSoup4 通過 PyPi 發(fā)布,所以如果你無法使用系統(tǒng)包管理安裝,那么也可以通過?easy_install?或?pip?來進行安裝。包的名字是?beautifulsoup4,這個包同時兼容 Python2.x 和 Python3.x。
請確保使用與 Python 版本相匹配的?pip?或?easy_install?命令(如果你使用的是 Python3,那么應該使用?pip3?和?easy_install3?命令)。
$ easy_install beautifulsoup4 $ pip install beautifulsoup4(注意:在 PyPi 中還有一個名字是?BeautifulSoup?的包,但那個是 BeautifulSoup3 的發(fā)布版本,因為很多舊的項目還在使用它,所以?BeautifulSoup?包依然有效……但是如果你在編寫新項目,那么請應該安裝的?beautifulsoup4?)
如果你沒有安裝?easy_install?或?pip,那你也可以下載 BS4?的源碼,然后通過?setup.py?來安裝。
$ Python setup.py install如果上述安裝方法都行不通,BeautifulSoup 的發(fā)布協(xié)議還允許你將 BS4 的代碼打包在你的項目中,這樣無須安裝即可使用。你可以下載壓縮包,并將 BS4 目錄拷貝到你的應用程序代碼庫,這樣就可以在不安裝的情況下直接使用 BeautifulSoup 了。
我在 Python2.7 和 Python3.2 的版本下開發(fā) BeautifulSoup, 理論上 Beautiful Soup 在所有當前的 Python 版本中均可正常工作(在本文檔中的所有案例是使用 Python3.6 演示的)。
3.1 安裝完成后的問題
BeautifulSoup 包是以 Python2 代碼的形式打包的,在 Python3 環(huán)境下安裝時,會自動轉換成 Python3 的代碼,如果沒有一個安裝的過程,那么代碼就不會被轉換。還有一些是在 Windows 操作系統(tǒng)上安裝了錯誤版本的報告。
如果代碼拋出了?ImportError?的異常:"No module named HTMLParser",這是因為你在 Python3 版本中執(zhí)行 Python2 版本的代碼。
如果代碼拋出了?ImportError?的異常:"No module named html.parser",這是因為你在 Python2 版本中執(zhí)行 Python3 版本的代碼。
如果遇到上述 2 種情況,最好的解決方法是先完全卸載 BeautifulSoup(包括解壓時創(chuàng)建的任何文件夾)后再重新安裝 BeautifulSoup4。
如果在?ROOT_TAG_NAME = u'[document]'?代碼處遇到?SyntaxError "Invalid syntax"?錯誤,則需要將 BS4 的 Python 代碼版本從 Python2 轉換到 Python3,你可以通過安裝包來實現(xiàn)這一步:
$ Python3 setup.py install或者在?bs4?目錄中(Python\Python36\Lib\site-packages\bs4)執(zhí)行 Python 代碼版本轉換代碼?2to3:
$ 2to3-3.2 -w bs43.2 安裝解析器
BeautifulSoup 支持 Python 標準庫中的 HTML 解析器,還支持一些第三方的解析器,lxml?就是其中比較火的一個。
下面提供了各種不同操作系統(tǒng)安裝 lxml 的方法:
$ apt-get install python-lxml $ easy_install lxml $ pip install lxml另一個可供選擇的解析器是純 Python 實現(xiàn)的 html5lib,html5lib 的解析 HTML 的方式與瀏覽器相同,可以選擇下列方法來安裝 html5lib:
$ apt-get install python-html5lib $ easy_install html5lib $ pip install html5lib下表總結了各個主流解析器的優(yōu)缺點:
| 解析器 | 典型用法 | 優(yōu)點 | 缺點 | 
| Python 的 html.parser | BeautifulSoup(markup, "html.parser") | 
 | 
 | 
| lxml 的 HTML parser | BeautifulSoup(markup, "lxml") | 
 | 
 | 
| lxml 的 XML parser | BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml") | 
 | 
 | 
| html5lib | BeautifulSoup(markup, "html5lib") | 
 | 
 | 
從效率層面考慮,推薦安裝和使用 lxml 解析器。如果是在 Python2.7.3 或 Python3.2.2 之前的版本,必須安裝 lxml 或 html5lib,因為那些 Python 版本的標準庫中內置的 HTML 解析方法還不夠穩(wěn)定。
注意:如果一個文檔的格式不合法的話,那么在不同的解析器中返回的結果可能是不一樣的,具體請查看? 解析器之間的區(qū)別??章節(jié)內容。
四、如何使用
解析一個文檔,只需要將其傳遞給?BeautifulSoup?構造方法。你可以傳遞一個字符串或者是一個文件句柄。
from bs4 import BeautifulSoupwith open("index.html") as fp:soup = BeautifulSoup(fp)soup = BeautifulSoup("<html>data</html>")首先,文檔將被轉換成 Unicode 編碼,并且 HTML 實體也都被轉換成 Unicode 字符:
>>> BeautifulSoup("Sacré bleu!") Sacré bleu!然后,BeautifulSoup 選擇最合適的解析器來解析這段文檔,如果要解析一個 XML 文檔,那么需要手動指定 XML 解析器(soup = BeautifulSoup(markup, "xml")),否則它仍然會嘗試使用 HTML 解析器。
五、對象的種類
BeautifulSoup 將復雜 HTML 文檔轉換成一個同樣復雜的樹形結構,每個節(jié)點都是 Python 對象,所有對象可以歸納為 4 種:Tag,NavigableString,BeautifulSoup?和?Comment。
5.1 Tag(標簽)
注:tag 在本文中即 “標簽”,兩者同義,在下文中將交替使用。
一個?Tag?對象對應一個 XML 或 HTML 原生文檔中的標簽:
>>> soup = BeautifulSoup('<b class="boldest">Extremely bold</b>') >>> tag = soup.b >>> type(tag) <class 'bs4.element.Tag'>標簽有很多屬性和方法,在 遍歷文檔樹 和 搜索文檔樹 章節(jié)中均有詳細解釋。
現(xiàn)在,讓我們先介紹一下 tag 中最重要的特征:name(標簽名)和 attributes(屬性)。
5.1.1 Name(標簽名)
每個標簽都有一個名字,通過?.name?來獲取:
>>> tag.name 'b'如果改變了標簽的名稱,那么將影響所有通過當前 BeautifulSoup 對象生成的 HTML 文檔:
>>> tag.name = "blockquote" >>> tag <blockquote class="boldest">Extremely bold</blockquote>5.1.2 Attributes(屬性)
一個標簽可以有很多個屬性。
比如標簽?<b id="boldest">?有一個叫“id”的屬性,它的值為“boldest”。
標簽訪問屬性方法與字典相同:
>>>tag['id'] ['boldest']也可以使用?.attrs?直接訪問字典:
>>> tag.attrs {'id': ['boldest']}標簽的屬性可以被添加、刪除或修改。再強調一次,標簽的屬性操作方法與 Python 字典是一樣的!
>>> tag['class'] = 'verybold' >>> tag['id'] = 1>>> tag <blockquote class="verybold" id="1">Extremely bold</blockquote>>>> del tag['class'] >>> del tag['id']>>> tag['class'] Traceback (most recent call last):File "<pyshell#40>", line 1, in <module>tag['class']File "C:\Users\goodb\AppData\Local\Programs\Python\Python36\lib\site-packages\bs4\element.py", line 1011, in __getitem__return self.attrs[key] KeyError: 'class'>>> print(tag.get('class')) NoneMulti-valued attributes(多值屬性)
HTML4 定義了一些可以包含多個值的屬性,在 HTML5 中略有增刪。其中最常見的多值屬性是?class(一個 tag 可以有多個 CSS 的 class)。還有一些屬性像?rel,rev,accept-charset,headers?和?accesskey。在 BeautifulSoup 中,是以列表的形式來處理多值屬性的:
>>> css_soup = BeautifulSoup('<p class="body"></p>') >>> css_soup.p['class'] ['body']>>> css_soup = BeautifulSoup('<p class="body strikeout"></p>') >>> css_soup.p['class'] ['body', 'strikeout']如果某個屬性看起來存在多個值,但在 HTML 的定義中卻不是一個多值屬性,那么 BeautifulSoup 會將其作為字符串返回:
>>> id_soup = BeautifulSoup('<p id="My id"></p>') >>> id_soup.p['id'] 'My id'將標簽轉換成字符串時,多值屬性會合并為一個值:
>>> rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')>>> rel_soup.a['rel'] ['index'] >>> rel_soup.a['rel'] = ['index', 'contents']>>> print(rel_soup.p) <p>Back to the <a rel="index contents">homepage</a></p>你可以使用?get_attribute_list()?方法以列表形式獲取一個屬性值:如果它是多值屬性,那么列表中存在多個字符串;否則列表中就只有一個字符串。
>>> id_soup.p.get_attribute_list('id') ['my id']如果解析的文檔是 XML 格式,那么 tag 中不包含多值屬性:
>>> xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml') >>> xml_soup.p['class'] 'body strikeout'5.2?NavigableString(可以遍歷的字符串)
字符串對應的是標簽內部包含的文本。BeautifulSoup 使用?NavigableString?類來包裝這些文本:
>>> tag.string 'Extremely bold'>>> type(tag.string) <class 'bs4.element.NavigableString'>一個?NavigableString?對象與 Python 中的 Unicode 字符串相似,并且還支持在遍歷文檔樹和搜索文檔樹中的一些特性。通過?str()?方法可以將?NavigableString?對象轉換成 Unicode 字符串:
>>> unicode_string = str(tag.string) >>> unicode_string 'Extremely bold'>>> type(unicode_string) <class 'str'>標簽中包含的字符串不能被編輯,但是可以被替換成其它字符串,用?replace_with()?方法:
>>> tag.string.replace_with("No longer bold") 'Extremely bold'>>> tag <blockquote>No longer bold</blockquote>NavigableString?對象支持 遍歷文檔樹 和 搜索文檔樹 中定義的大部分屬性,但不是全部哦~尤其是,一個字符串不能包含其它內容(標簽能夠包含字符串或是其它標簽),字符串不支持?.contents?或?.string?屬性或?find()?方法。
如果想在 BeautifulSoup 之外使用 NavigableString 對象,需要調用 str() 方法,將該對象轉換成普通的 Unicode 字符串。否則,就算 BeautifulSoup 已經執(zhí)行結束,該對象也會帶有整個 BeautifulSoup 解析樹的引用地址,這樣會造成內存的巨大浪費。
5.3 BeautifulSoup
BeautifulSoup?對象表示的是一個文檔的全部內容。大部分時候,可以把它當作?Tag?對象,它支持 遍歷文檔樹 和 搜索文檔樹 中描述的大部分的方法。
因為?BeautifulSoup?對象并不是真正的 HTML 或 XML 標簽,所以它沒有 name 和 attributes。但有時查看它的?.name?屬性是很方便的,所以 BeautifulSoup 對象包含了一個值為 "[document]" 的特殊屬性?.name:
5.4?Comments and other special strings(注釋及特殊字符串)
Tag,NavigableString?和?BeautifulSoup?幾乎涵蓋了 HTML 或 XML 文檔中的所有內容,但不包括注釋。
>>> markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" >>> soup = BeautifulSoup(markup)>>> comment = soup.b.string >>> type(comment) <class 'bs4.element.Comment'>Comment?對象是一個特殊類型的?NavigableString?對象:
>>> comment 'Hey, buddy. Want to buy a used parser?'但是當它出現(xiàn)在 HTML 文檔中時,Comment?對象會使用特殊的格式輸出:
>>> print(soup.b.prettify()) <b> <!--Hey, buddy. Want to buy a used parser?--> </b>BeautifulSoup 中定義的其它類都可能會出現(xiàn)在 XML 的文檔中:CData,ProcessingInstruction,Declaration 和 Doctype。與 Comment 對象類似,這些類都是 NavigableString 的子類,只是添加了一些額外方法的字符串。下面是用 CDATA 來替代注釋的例子:
>>> from bs4 import CData>>> cdata = CData("A CDATA block") >>> comment.replace_with(cdata) 'Hey, buddy. Want to buy a used parser?'>>> print(soup.b.prettify()) <b> <![CDATA[A CDATA block]]> </b>六、遍歷文檔樹
我們還是拿《愛麗絲夢游仙境》的文檔來做演示:
>>> html_doc = """<html><head><title>睡鼠的故事</title></head> <body> <p class="title"><b>睡鼠的故事</b></p> <p class="story">從前有三位小姐姐,她們的名字是: <a href="http://example.com/elsie" class="sister" id="link1">埃爾西</a>, <a href="http://example.com/lacie" class="sister" id="link2">萊斯</a>和 <a href="http://example.com/tillie" class="sister" id="link3">蒂爾莉</a>; 她們住在一個井底下面。</p><p class="story">...</p> """ >>> from bs4 import BeautifulSoup >>> soup = BeautifulSoup(html_doc, 'html.parser')下面我將給大家演示如何從文檔的一個位置移動到另一個位置!
6.1 子節(jié)點(向下遍歷)
標簽可能包含字符串或其它標簽,這些都是這個標簽的子節(jié)點。BeautifulSoup 提供了許多不同的屬性,用于遍歷和迭代一個標簽的子節(jié)點。
注意:BeautifulSoup 中的字符串節(jié)點是不支持這些屬性的,因為字符串本身沒有子節(jié)點。
6.1.1 使用標簽名進行遍歷
遍歷解析樹最簡單的方法就是告訴它你想要獲取的標簽的名稱。比如你想獲取 <head> 標簽,只要用?soup.head?即可:
>>> soup.head <head><title>睡鼠的故事</title></head>>>> soup.title <title>睡鼠的故事</title>你可以重復多次使用這個小技巧來深入解析樹的某一個部分。下面代碼獲取 <body> 標簽中的第一個 <b> 標簽:
>>> soup.body.b <b>睡鼠的故事</b>使用標簽名作為屬性的方法只能獲得當前名字后的第一個標簽:
>>> soup.a <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>如果想要獲取所有的 <a> 標簽,或者獲取一些更復雜的東西時,就要用到在 查找文檔樹 章節(jié)中講解的一個方法 ——?find_all():
>>> soup.find_all('a') [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]6.1.2 .contents 和 .children
一個標簽的子節(jié)點可以從一個叫?.contents?的列表中獲得:
>>> head_tag = soup.head >>> head_tag <head><title>睡鼠的故事</title></head> >>> head_tag.contents [<title>睡鼠的故事</title>]>>> title_tag = head_tag.contents[0] >>> title_tag.contents ['睡鼠的故事']BeautifulSoup?對象本身擁有子節(jié)點,也就是說 <html> 標簽也是?BeautifulSoup?對象的子節(jié)點:
>>> len(soup.contents) 1 >>> soup.contents[0].name 'html'字符串沒有?.contents?屬性,因此字符串沒有子節(jié)點:
>>> text = title_tag.contents[0] >>> text.contents Traceback (most recent call last):File "<pyshell#17>", line 1, in <module>text.contentsFile "C:\Users\goodb\AppData\Local\Programs\Python\Python36\lib\site-packages\bs4\element.py", line 737, in __getattr__self.__class__.__name__, attr)) AttributeError: 'NavigableString' object has no attribute 'contents'如果你不想通過?.contents?獲取一個列表,還可以通過標簽的?.children?屬性得到一個生成器:
>>> for child in title_tag.children:print(child)睡鼠的故事6.1.3 .descendants
.contents?和?.children?屬性僅包含標簽的直接子節(jié)點。例如 <head> 標簽只有一個直接子節(jié)點 <title> 標簽:
>>> head_tag.contents [<title>睡鼠的故事</title>]但是,<title> 標簽自身也有一個子節(jié)點:字符串 "睡鼠的故事",這種情況下字符串 "睡鼠的故事" 也屬于 <head> 標簽的子孫節(jié)點。如果要對多層子孫節(jié)點進行遞歸迭代,可以使用?.descendants?屬性完成任務:
>>> for child in head_tag.descendants:print(child)<title>睡鼠的故事</title> 睡鼠的故事上面的例子中,<head> 標簽只有一個子節(jié)點,但是有 2 個子孫節(jié)點:<head> 標簽和 <head> 標簽的子節(jié)點。BeautifulSoup?有一個直接子節(jié)點(<html> 標簽),卻有很多子孫節(jié)點:
>>> len(list(soup.children)) 1 >>> len(list(soup.descendants)) 266.1.4 .string
如果標簽只有一個子節(jié)點,并且這個子節(jié)點是一個?NavigableString?對象,那么可以用?.string?將其獲取:
>>> title_tag.string '睡鼠的故事'如果標簽的唯一子節(jié)點是另一個標簽,并且那個標簽擁有?.string,那么父節(jié)點可以直接通過?.string?來訪問其子孫節(jié)點的字符串:
>>> head_tag.contents [<title>睡鼠的故事</title>]>>> head_tag.string '睡鼠的故事'如果一個標簽包含不止一個子節(jié)點,那么就不清楚?.string?應該指向誰了,所以此時?.string?的值是?None:
>>> print(soup.html.string) None6.1.5 .strings 和 stripped_strings
如果一個標簽中不止一個子節(jié)點,你也是可以獲取里面包含的字符串的(不止一個),需要使用?.strings?生成器:
>>> for string in soup.strings:print(repr(string))'睡鼠的故事' '\n' '\n' '睡鼠的故事' '\n' '從前有三位小姐姐,她們的名字是:\n' '埃爾西' ',\n' '萊斯' '和\n' '蒂爾莉' ';\n她們住在一個井底下面。' '\n' '...' '\n'輸出的這些字符串中可能包含了很多空格或空行,對我們來說一點用都沒有……使用?.stripped_strings?可以去除多余空白:
>>> for string in soup.stripped_strings:print(repr(string))'睡鼠的故事' '睡鼠的故事' '從前有三位小姐姐,她們的名字是:' '埃爾西' ',' '萊斯' '和' '蒂爾莉' ';\n她們住在一個井底下面。' '...'6.2?父節(jié)點(向上遍歷)
我們繼續(xù)以“家族樹”作類比,每一個標簽和字符串都有一個父節(jié)點:它們總是被包含在另外一個標簽中。
6.2.1 .parent
你可以使用?.parent?屬性訪問一個元素的父節(jié)點。舉個栗子,《愛麗絲夢游仙境》這個文檔中,<head> 標簽就是 <title> 標簽的父節(jié)點:
>>> title_tag = soup.title >>> title_tag <title>睡鼠的故事</title> >>> title_tag.parent <head><title>睡鼠的故事</title></head>字符串本身有一個父節(jié)點,就是包含它的 <title> 標簽:
>>> title_tag.string.parent <title>睡鼠的故事</title>頂層節(jié)點比如 <html> 的父節(jié)點是?BeautifulSoup?對象本身:
>>> html_tag = soup.html >>> type(html_tag.parent) <class 'bs4.BeautifulSoup'>BeautifulSoup?對象的?.parent?是?None:
>>> print(soup.parent) None6.2.2 .parents
你可以使用?.parents?迭代一個元素的所有父節(jié)點。下面例子使用了?.parents?屬性遍歷了 <a> 標簽的所有父節(jié)點:
>>> link = soup.a >>> link <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a> >>> for parent in link.parents:if parent is None:print(parent)else:print(parent.name)p body html [document]6.3?兄弟節(jié)點(左右遍歷)
大家請看一段簡單的例子:
>>> sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>", "html.parser") >>> print(sibling_soup.prettify()) <a> <b>text1 </b> <c>text2 </c> </a><b> 標簽和 <c> 標簽在同一層:它們都是 <a> 標簽的直接子節(jié)點,我們將它們成為兄弟節(jié)點。當一段文檔以標準格式輸出時,兄弟節(jié)點有相同的縮進級別。
6.3.1 .next_sibling 和 .previous_sibling
你可以使用?.next_sibling?和?.previous_sibling?去遍歷解析樹里處于同一層的元素:
>>> sibling_soup.b.next_sibling <c>text2</c>>>> sibling_soup.c.previous_sibling <b>text1</b><b> 標簽有一個?.next_sibling,但是沒有?.previous_sibling,因為在同一層里,<b> 標簽的前面沒有其他東西了。同樣的道理,<c> 標簽擁有一個?.previous_sibling,但卻沒有?.next_sibling:
>>> print(sibling_soup.b.previous_sibling) None>>> print(sibling_soup.c.next_sibling) None字符串 "text1" 和 "text2" 并不是兄弟節(jié)點,因為它們沒有共同的老爸(父節(jié)點):
>>> sibling_soup.b.string 'text1' >>> print(sibling_soup.b.string.next_sibling) None在現(xiàn)實情況中,一個標簽的?.next_sibling?或?.previous_sibling?通常是一個包含空格的字符串。讓我回到《愛麗絲夢游仙境》中:
<a href="http://example.com/elsie" class="sister" id="link1">埃爾西</a>, <a href="http://example.com/lacie" class="sister" id="link2">萊斯</a>和 <a href="http://example.com/tillie" class="sister" id="link3">蒂爾莉</a>;如果你覺得第一個 <a> 標簽的?.next_sibling?是第二個 <a> 標簽,那你就錯了!事實上,它的結果是一個字符串 —— 由逗號和換行符構成,用于隔開第二個 <a> 標簽:
>>> link = soup.a >>> link <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>>>> link.next_sibling ',\n'第二個 <a> 標簽是逗號的?.next_sibling?屬性:
>>> link.next_sibling.next_sibling <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>6.3.2 .next_siblings 和 .previous_siblings
你可以通過?.next_siblings?和?.previous_siblings?屬性對當前節(jié)點的所有兄弟節(jié)點迭代輸出:
>>> link.next_sibling.next_sibling <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a> >>> for sibling in soup.a.next_siblings:print(repr(sibling))',\n' <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a> '和\n' <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a> ';\n她們住在一個井底下面。'>>> for sibling in soup.find(id="link3").previous_siblings:print(repr(sibling))'和\n' <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a> ',\n' <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a> '從前有三位小姐姐,她們的名字是:\n'6.4?回退和前進
看一下《愛麗絲夢游仙境》的開頭部分:
<html><head><title>睡鼠的故事</title></head> <p class="title"><b>睡鼠的故事</b></p>HTML 解析器把這段字符串轉換成一連串的事件:打開一個 <html> 標簽 -> 打開一個 <head> 標簽 -> 打開一個 <title> 標簽 -> 添加一段字符串 ->關閉一個 <title> 標簽 -> 打開 <p> 標簽等等。BeautifulSoup 提供了重現(xiàn)文檔初始解析的工具。
6.4.1 .next_element 和 .previous_element
字符串或對象的?.next_element?屬性指向下一個被解析的對象,結果可能與?.next_sibling?相同,但通常是不一樣的。
這是《愛麗絲夢游仙境》文檔中最后一個 <a> 標簽,它的?.next_sibling?屬性是指當前標簽后緊接著的字符串:
但是這個 <a> 標簽的?.next_element?屬性則是指在 <a> 標簽之后被解析內容,所以應該是字符串 "蒂爾莉":
>>> last_a_tag.next_element '蒂爾莉'這是因為在原始文檔中,字符串 "蒂爾莉" 在分號前出現(xiàn),解析器先進入 <a> 標簽,然后是字符串 "蒂爾莉",接著關閉 </a> 標簽,最后是分號和剩余部分。分號與 <a> 標簽在同一層級,但是字符串 "蒂爾莉" 會被先解析。
.previous_element?屬性剛好與?.next_element?相反,它指向當前被解析的對象的前一個解析對象:
>>> last_a_tag.previous_element '和\n' >>> last_a_tag.previous_element.next_element <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>6.4.2 .next_elements 和 .previous_elements
通過?.next_elements?和?.previous_elements?的迭代器就可以向前或向后訪問文檔的解析內容,就好像文檔正在被解析一樣:
>>> for element in last_a_tag.next_elements:print(repr(element))'蒂爾莉' ';\n她們住在一個井底下面。' '\n' <p class="story">...</p> '...' '\n'七、搜索文檔樹
BeautifulSoup 定義了很多搜索方法,但它們的用法都非常相似。這里我們用大篇幅著重介紹兩個最常用的方法:find()?和?find_all()。其它方法的參數(shù)和用法類似,就制作簡要說明,大家舉一反三即可。
我們再一次以《愛麗絲夢游仙境》作為例子:
>>> html_doc = """<html><head><title>睡鼠的故事</title></head> <body> <p class="title"><b>睡鼠的故事</b></p><p class="story">從前有三位小姐姐,她們的名字是: <a href="http://example.com/elsie" class="sister" id="link1">埃爾西</a>, <a href="http://example.com/lacie" class="sister" id="link2">萊斯</a>和 <a href="http://example.com/tillie" class="sister" id="link3">蒂爾莉</a>; 她們住在一個井底下面。</p><p class="story">...</p> """ >>> from bs4 import BeautifulSoup >>> soup = BeautifulSoup(html_doc, 'html.parser')通過將一個過濾器參數(shù)傳遞到類似?find_all()?的方法,可以搜索到感興趣的內容。
7.1 幾種過濾器
在講解?find_all()?和其他類似方法之前,我想通過一些例子來向你展示都有哪些過濾器可以使用。
這些過濾器貫穿了所有的搜索 API 函數(shù),它們可以被用在標簽的名稱、屬性、文本這些上面。
7.1.1 字符串
最簡單的過濾器是字符串,在搜索方法中傳入一個字符串參數(shù),BeautifulSoup 會查找與字符串完整匹配的內容,下面的例子用于查找文檔中所有的 <b> 標簽:
>>> soup.find('b') <b>睡鼠的故事</b>如果傳入的是字節(jié)碼參數(shù),BeautifulSoup 將假設它是 UTF-8 編碼。為了避免解碼出錯,可以直接傳入一段 Unicode 編碼。
7.1.2 正則表達式
如果傳入正則表達式作為參數(shù),BeautifulSoup 會通過正則表達式的?match()?方法來匹配內容。下面例子將找出所有以 b 開頭的標簽,這表示 <body> 和 <b> 標簽都能被找到:
>>> import re >>> for tag in soup.find_all(re.compile("^b")):print(tag.name)body b下面代碼找出所有名字中包含字母 "t" 的標簽:
>>> for tag in soup.find_all(re.compile("t")):print(tag.name)html title7.1.3 列表
如果傳入列表參數(shù),BeautifulSoup 會將與列表中任一元素匹配的內容返回。下面代碼找到文檔中所有 <a> 標簽和 <b> 標簽:
>>> soup.find_all(["a", "b"]) [<b>睡鼠的故事</b>, <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]7.1.4 True
True?值可以匹配任意值,下面代碼查找到所有的標簽,但是不會返回字符串節(jié)點:
>>> for tag in soup.find_all(True):print(tag.name)html head title body p b p a a a p7.1.5 函數(shù)
如果沒有合適過濾器,那么還可以自己定義一個函數(shù),該函數(shù)只接受一個元素作為參數(shù)。如果這個方法返回?True?表示當前元素匹配并且被找到,否則返回?False。
下面這個函數(shù)用于匹配那些包含 "class" 屬性但不包含 "id" 屬性的標簽:
>>> def has_class_but_no_id(tag):return tag.has_attr('class') and not tag.has_attr('id')將這個函數(shù)作為參數(shù)傳入?find_all()?方法,將得到所有 <p> 標簽:
>>> soup.find_all(has_class_but_no_id) [<p class="title"><b>睡鼠的故事</b></p>, <p class="story">從前有三位小姐姐,她們的名字是: <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>和 <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>; 她們住在一個井底下面。</p>, <p class="story">...</p>]返回結果中只有 <p> 標簽沒有 <a> 標簽(上面出現(xiàn)的 <a> 是包含在 <p> 中的),因為 <a> 標簽里面還定義了 "id",沒有返回 <html> 和 <head>,因為 <html> 和 <head> 中沒有定義 "class" 屬性。
如果你傳入一個函數(shù)來過濾一個像?href?這樣的特定屬性,傳入函數(shù)的參數(shù)將是屬性值,而不是整個標簽。
下面這個函數(shù)可以找到所有擁有?href?屬性,但不包含 "lacie" 字符串的標簽:
>>> def not_lacie(href):return href and not re.compile("lacie").search(href)>>> soup.find_all(href=not_lacie) [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]只要你需要,函數(shù)還可以更復雜。
下面這個函數(shù)在一個標簽包含字符串對象的時候返回?True:
>>> 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)body p a a a p現(xiàn)在,我們來了解一下搜索方法的細節(jié)。
7.2 find_all()
find_all(name, attrs, recursive, string, limit, **kwargs)
find_all()?方法搜索當前 tag 下的所有子節(jié)點,并判斷是否符合過濾器的條件。
這里有幾個過濾器的例子:
>>> soup.find_all("title") [<title>睡鼠的故事</title>] >>> >>> soup.find_all("p", "title") [<p class="title"><b>睡鼠的故事</b></p>] >>> >>> soup.find_all("a") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>] >>> >>> soup.find_all(id="link2") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>] >>> >>> import re >>> soup.find(string=re.compile("小姐姐")) '從前有三位小姐姐,她們的名字是:\n' >>>上面的?string?和?id?關鍵字參數(shù)代表什么呢?為什么?find_all("p", "title")?返回的是 Class 為 ”title” 的 <p> 標簽呢?請看下面的參數(shù)講解。
7.2.1 name 參數(shù)
通過?name?參數(shù),你可以根據(jù)指定名字來查找標簽。
簡單的用法如下:
>>> soup.find_all("title") [<title>睡鼠的故事</title>]上一節(jié)提到的幾種過濾器均可以作為?name?參數(shù)的值:字符串,正則表達式,列表,函數(shù),或者直接一個布爾類型值?True。
7.2.2 keyword 參數(shù)
如果一個指定名字的參數(shù)不是搜索內置的(name, attrs, recursive, string, limit)參數(shù)名,搜索時會把該參數(shù)當作指定 tag 的屬性來搜索。
比如你傳遞一個名為?id?的參數(shù),BeautifulSoup 將會搜索每個 tag 的 ”id” 屬性:
>>> soup.find_all(id="link2") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>]如果你傳遞一個名為?href?的參數(shù),BeautifulSoup 將會搜索每個 tag 的 ”href” 屬性:
>>> soup.find_all(href=re.compile("elsie")) [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]搜索指定名字的屬性時可以使用的參數(shù)值包括:字符串、正則表達式、列表、函數(shù)和?True?值。
下面的例子在文檔樹中查找所有包含?id?屬性的 tag,無論?id?的值是什么都將匹配:
>>> soup.find_all(id=True) [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]你還可以同時過濾多個屬性:
>>> soup.find_all(href=re.compile("elsie"), id="link1") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]注意:有些 tag 屬性在搜索不能使用,比如 HTML5 中的 data-* 屬性:
>>> data_soup = BeautifulSoup('<div data-foo="value">foo!</div>', "html.parser") >>> data_soup.find_all(data-foo="value") SyntaxError: keyword can't be an expression但是可以通過將這些屬性放進一個字典里,然后將其傳給?attrs?關鍵字參數(shù)來實現(xiàn) “曲線救國”:
>>> data_soup.find_all(attrs={"data-foo": "value"}) [<div data-foo="value">foo!</div>]你不能使用關鍵字參數(shù)來搜索 HTML 的 "name" 元素,因為 BeautifulSoup 使用?name?參數(shù)來表示標簽自身的名字。
取而代之,你可以將 "name" 添加到?attrs?參數(shù)的值中:
>>> name_soup = BeautifulSoup('<input name="email"/>') >>> name_soup.find_all(name="email") [] >>> name_soup.find_all(attrs={"name": "email"}) [<input name="email"/>]7.2.3 根據(jù) CSS 進行搜索
按照 CSS 類名搜索標簽的功能非常實用,但由于表示 CSS 類名的關鍵字 “class” 在 Python 中是保留字,所以使用 class 做參數(shù)會導致語法錯誤。從 BeautifulSoup 的 4.1.1 版本開始,可以通過?class_?參數(shù)搜索有指定 CSS 類名的標簽:
>>> soup.find_all("a", class_="sister") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]跟關鍵字參數(shù)一樣,class_?參數(shù)也支持不同類型的過濾器:字符串、正則表達式、函數(shù)或?True:
>>> soup.find_all(class_=re.compile("itl")) [<p class="title"><b>睡鼠的故事</b></p>] >>> >>> def has_six_characters(css_class):return css_class is not None and len(css_class) == 6>>> soup.find_all(class_=has_six_characters) [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]注意,標簽的 “class” 屬性支持同時擁有多個值,按照 CSS 類名搜索標簽時,可以分別搜索標簽中的每個 CSS 類名:
>>> css_soup = BeautifulSoup('<p class="body strikeout"></p>', "html.parser") >>> css_soup.find_all("p", class_="strikeout") [<p class="body strikeout"></p>] >>> >>> css_soup.find_all("p", class_="body") [<p class="body strikeout"></p>]搜索 class 屬性時也可以指定完全匹配的 CSS 值:
>>> css_soup.find_all("p", class_="body strikeout") [<p class="body strikeout"></p>]但如果 CSS 值的順序與文檔不一致,將導致結果搜索不到(盡管其字符串是一樣的):
>>> css_soup.find_all("p", class_="strikeout body") []如果你希望搜索結果同時匹配兩個以上的 CSS 類名,你應該使用 CSS 選擇器:
>>> css_soup.select("p.strikeout.body") [<p class="body strikeout"></p>]在那些沒有 class_ 關鍵字的 BeautifulSoup 版本中,你可以使用 attrs 技巧(上面咱舉過一個例子):
>>> soup.find_all("a", attrs={"class": "sister"}) [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]7.2.4 string 參數(shù)
通過?string?參數(shù)可以搜索標簽中的文本內容。與?name?參數(shù)一樣,string?參數(shù)接受字符串,正則表達式,列表,函數(shù),或者直接一個布爾類型值?True。
請看下面例子:
>>> soup.find_all(string="埃爾西") ['埃爾西'] >>>? >>> soup.find_all(string=["蒂爾莉", "埃爾西", "萊斯"]) ['埃爾西', '萊斯', '蒂爾莉'] >>>? >>> soup.find_all(string=re.compile("睡鼠")) ['睡鼠的故事', '睡鼠的故事'] >>>? >>> def is_the_only_string_within_a_tag(s):"""如果字符串是其父標簽的唯一子節(jié)點,則返回 True。"""return (s == s.parent.string)>>> soup.find_all(string=is_the_only_string_within_a_tag) ['睡鼠的故事', '睡鼠的故事', '埃爾西', '萊斯', '蒂爾莉', '...']盡管?string?參數(shù)是用于搜索字符串的,但你可以與其它參數(shù)混合起來使用:下面代碼中,BeautifulSoup 會找到所有與 string 參數(shù)值相匹配的 <a> 標簽:
>>> soup.find_all("a", string="埃爾西") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]string?參數(shù)是 BeautifulSoup 4.4.0 新增加的特性,在早期的版本中,它叫?text?參數(shù):
>>> soup.find_all("a", text="埃爾西") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]7.2.5 limit 參數(shù)
find_all()?方法返回匹配過濾器的所有標簽和文本。如果文檔樹很大,那么搜索就會變得很慢。如果你不需要全部的結果,可以使用?limit?參數(shù)限制返回結果的數(shù)量。效果與 SQL 中的 LIMIT 關鍵字類似 —— 當搜索到的結果數(shù)量達到 limit 的限制時,就停止搜索并返回結果。
文檔樹中有 3 個標簽符合搜索條件,但結果只返回了 2 個,因為我們限制了返回數(shù)量:
>>> soup.find_all("a", limit=2) [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>]7.2.6 recursive 參數(shù)
如果你調用?mytag.find_all()?方法,BeautifulSoup 將會獲取?mytag?的所有子孫節(jié)點。如果只想搜索?mytag?的直接子節(jié)點,可以使用參數(shù)?recursive=False。
對比一下:
>>> soup.html.find_all("title") [<title>睡鼠的故事</title>] >>> >>> soup.html.find_all("title", recursive=False) []文檔的原型是這樣的:
<html> <head><title>The Dormouse's story</title> </head> ...<title> 標簽在 <html> 標簽下,但并不是直接子節(jié)點,<head> 標簽才是直接子節(jié)點。在允許查詢所有后代節(jié)點的時候,BeautifulSoup 能夠查找到 <title> 標簽。但是使用了?recursive=False?參數(shù)之后,只能查找直接子節(jié)點,因此就查不到 <title> 標簽了。
BeautifulSoup 提供了多種 DOM 樹搜索方法(下面將展示給大家)。這些方法都使用了與?find_all()?類似的參數(shù):name、attrs、stirng、limit?和關鍵字參數(shù)。但是只有?find_all()?和?find()?支持?recursive?參數(shù)。給?find_parents()?方法傳遞?recursive=False?參數(shù)并沒有什么作用。
7.3?像調用 find_all() 一樣調用一個標簽
由于?find_all()?幾乎是 Beautiful Soup 中最常用的搜索方法,所以我們?yōu)樗x了一種簡寫的形式:如果你將 BeautifulSoup 對象或 Tag 對象當作一個方法來使用,那么這個方法的執(zhí)行結果與調用這個對象的?find_all()?方法是相同的。
因此,下面兩行代碼是等價的:
soup.find_all("a") soup("a")還有下面兩行代碼也是等價的:
soup.title.find_all(text=True) soup.title(text=True)7.4?find() 方法
find_all(name, attrs, recursive, string, **kwargs)
find_all()?方法將返回文檔中符合條件的所有tag,盡管有時候我們只想得到一個結果。比如文檔中只有一個<body>標簽,那么使用?find_all()?方法來查找<body>標簽就不太合適,使用?find_all?方法并設置?limit=1?參數(shù)不如直接使用?find()方法。下面兩行代碼是等價的:
>>> soup.find_all('title', limit=1) [<title>睡鼠的故事</title>] >>> soup.find('title') <title>睡鼠的故事</title>唯一的區(qū)別是?find_all()?方法的返回結果是值包含一個元素的列表,而?find()?方法直接返回結果。find_all()?方法沒有找到目標是返回空列表,?find()?方法找不到目標時,返回?None?。
>>> print(soup.find("nosuchtag")) None >>> print(soup.find_all("nosuchtag")) []soup.head.title?是?6.1 子節(jié)點(向下遍歷)-使用標簽名進行遍歷?方法的簡寫。這個簡寫的原理就是多次調用當前tag的?find()?方法:
>>> soup.head.title <title>睡鼠的故事</title> >>> soup.find("head").find("title") <title>睡鼠的故事</title>7.5 find_parents() 和 find_parent()
find_parents(name, attrs, string, limit, **kwargs)
find_parent(name, attrs, string, **kwargs)
我們已經用了很大篇幅來介紹?find_all()?和?find()?方法,Beautiful Soup中還有10個用于搜索的API。它們中的五個用的是與?find_all()?相同的搜索參數(shù)。另外5個與?find()?方法的搜索參數(shù)類似。區(qū)別僅是它們搜索文檔的不同部分。
記住:find_all()?和?find()?只搜索當前節(jié)點的所有子節(jié)點,孫子節(jié)點等。?find_parents()?和?find_parent()?用來搜索當前節(jié)點的父輩節(jié)點,搜索方法與普通tag的搜索方法相同,搜索文檔包含的內容。我們從一個文檔中的一個葉子節(jié)點開始:
>>> a_string = soup.find(text="萊斯") >>> a_string '萊斯'>>> a_string.find_parents("a") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>]>>> a_string.find_parents("p") [<p class="story">從前有三位小姐姐,她們的名字是: <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>和 <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>; 她們住在一個井底下面。</p>]>>> a_string.find_parents("p", class_="sister") []文檔中的一個<a>標簽是是當前葉子節(jié)點的直接父節(jié)點,所以可以被找到.還有一個<p>標簽,是目標葉子節(jié)點的間接父輩節(jié)點,所以也可以被找到。包含class值為”title”的<p>標簽不是不是目標葉子節(jié)點的父輩節(jié)點,所以通過?find_parents()?方法搜索不到。
find_parent()?和?find_parents()?方法會讓人聯(lián)想到?6.2?父節(jié)點(向上遍歷)中?.parent?和 .parents?屬性。它們之間的聯(lián)系非常緊密。搜索父輩節(jié)點的方法實際上就是對?.parents?屬性的迭代搜索.
7.6?find_next_siblings() 和?find_next_sibling()
find_next_siblings(name, attrs, string, limit, **kwargs)
find_next_sibling(name, attrs, string, **kwargs)
這2個方法通過 6.3?兄弟節(jié)點(左右遍歷)中??.next_siblings 屬性對當tag的所有后面解析的兄弟tag節(jié)點進行迭代,find_next_siblings()?方法返回所有符合條件的后面的兄弟節(jié)點,find_next_sibling()?只返回符合條件的后面的第一個tag節(jié)點。
>>> first_link = soup.a >>> first_link <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>>>> first_link.find_next_siblings("a") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]>>> first_story_paragraph = soup.find("p", "story") >>> first_story_paragraph.find_next_sibling("p") <p class="story">...</p>7.7?find_previous_siblings() 和 find_previous_sibling()
find_previous_siblings() (name, attrs, string, limit, **kwargs)
find_previous_sibling()(name, attrs, string, **kwargs)
這2個方法通過? 6.3?兄弟節(jié)點(左右遍歷)中?.previous_siblings?屬性對當前tag的前面解析的兄弟tag節(jié)點進行迭代,?find_previous_siblings()?方法返回所有符合條件的前面的兄弟節(jié)點,?find_previous_sibling()?方法返回第一個符合條件的前面的兄弟節(jié)點:
>>> last_link = soup.find("a", id="link3") >>> last_link <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>>>> last_link.find_previous_siblings("a") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]>>> first_story_paragraph = soup.find("p", "story") >>> first_story_paragraph.find_previous_sibling("p") <p class="title"><b>睡鼠的故事</b></p>7.8?find_all_next() 和 find_next()
find_all_next(name, attrs, string, limit, **kwargs)
find_next(name, attrs, string, **kwargs)
這2個方法通過?6.4?回退和前進 中?.next_elements?屬性對當前tag的之后的 tag和字符串進行迭代,?find_all_next()?方法返回所有符合條件的節(jié)點,?find_next()?方法返回第一個符合條件的節(jié)點:
>>> first_link = soup.a >>> first_link <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>>>> first_link.find_all_next(string=True) ['埃爾西', ',\n', '萊斯', '和\n', '蒂爾莉', ';\n她們住在一個井底下面。', '\n', '...', '\n']>>> first_link.find_next("p") <p class="story">...</p>第一個例子中,字符串 “埃爾西”也被顯示出來,盡管它被包含在我們開始查找的<a>標簽的里面。第二個例子中,最后一個<p>標簽也被顯示出來,盡管它與我們開始查找位置的<a>標簽不屬于同一部分。例子中,搜索的重點是要匹配過濾器的條件,并且在文檔中出現(xiàn)的順序而不是開始查找的元素的位置。
7.9?find_all_previous() 和 find_previous()
find_all_previous(name, attrs, string, limit, **kwargs)
find_previous(name, attrs, string, **kwargs)
這2個方法通過?6.4?回退和前進 中?.previous_elements?屬性對當前節(jié)點前面?的tag和字符串進行迭代,?find_all_previous()?方法返回所有符合條件的節(jié)點, find_previous()?方法返回第一個符合條件的節(jié)點。
>>> first_link = soup.a >>> first_link <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>>>> first_link.find_all_previous("p") [<p class="story">從前有三位小姐姐,她們的名字是: <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>和 <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>; 她們住在一個井底下面。</p>, <p class="title"><b>睡鼠的故事</b></p>]>>> first_link.find_previous("title") <title>睡鼠的故事</title>find_all_previous("p")?返回了文檔中的第一段(class=”title”的那段),但還返回了第二段,<p>標簽包含了我們開始查找的<a>標簽。不要驚訝,這段代碼的功能是查找所有出現(xiàn)在指定<a>標簽之前的<p>標簽,因為這個<p>標簽包含了開始的<a>標簽,所以<p>標簽一定是在<a>之前出現(xiàn)的。
7.10 CSS選擇器
從版本4.7.0開始,Beautiful Soup通過?SoupSieve 項目支持大多數(shù)CSS4選擇器。 如果您通過pip安裝了Beautiful Soup,則同時安裝了SoupSieve,因此您無需執(zhí)行任何額外操作。
BeautifulSoup有一個.select()方法,該方法使用SoupSieve對解析的文檔運行CSS選擇器并返回所有匹配的元素。 Tag有一個類似的方法,它針對單個標記的內容運行CSS選擇器。
(早期版本的Beautiful Soup也有.select()方法,但只支持最常用的CSS選擇器。)
SoupSieve文檔?列出了所有當前支持的CSS選擇器,但以下是一些基礎知識:
可以使用CSS選擇器的語法找到tag:
>>> soup.select("title") [<title>睡鼠的故事</title>]>>> soup.select("p:nth-of-type(3)") [<p class="story">...</p>]通過tag標簽逐層查找:
>>> soup.select("body a") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>] >>> soup.select("html head title") [<title>睡鼠的故事</title>]找到某個tag標簽下的直接子標簽:
>>> soup.select("head > title") [<title>睡鼠的故事</title>]>>> soup.select("p > a") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]>>> soup.select("p > a:nth-of-type(2)") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>]>>> soup.select("p > #link1") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]>>> soup.select("body > a") []找到兄弟節(jié)點標簽:
>>> soup.select("#link1 ~ .sister") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]>>> soup.select("#link1 + .sister") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>]通過CSS的類名查找:
>>> soup.select(".sister") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]>>> soup.select("[class~=sister]") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]通過tag的ID查找:
>>> soup.select("#link1") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]>>> soup.select("a#link2") [<a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>]查找與選擇器列表中的任何選擇器匹配的tag:
>>> soup.select("#link1,#link2") [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>]通過是否存在某個屬性來查找:
>>> soup.select('a[href]') [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]通過屬性的值來查找:
>>> soup.select('a[href="http://example.com/elsie"]') [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]>>> soup.select('a[href^="http://example.com/"]') [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>, <a class="sister" href="http://example.com/lacie" id="link2">萊斯</a>, <a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]>>> soup.select('a[href$="tillie"]') [<a class="sister" href="http://example.com/tillie" id="link3">蒂爾莉</a>]>>> soup.select('a[href*=".com/el"]') [<a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>]還有一個名為 select_one()的方法,它只查找與選擇器匹配的第一個標記:
>>> soup.select_one(".sister") <a class="sister" href="http://example.com/elsie" id="link1">埃爾西</a>如果您已經解析了定義名稱空間的XML,則可以在CSS選擇器中使用它們:
>>>from bs4 import BeautifulSoup >>>xml = """<tag xmlns:ns1="http://namespace1/" xmlns:ns2="http://namespace2/"><ns1:child>I'm in namespace 1</ns1:child><ns2:child>I'm in namespace 2</ns2:child> </tag> """ >>>soup = BeautifulSoup(xml, "xml")>>>soup.select("child") [<ns1:child>I'm in namespace 1</ns1:child>, <ns2:child>I'm in namespace 2</ns2:child>]>>>soup.select("ns1|child", namespaces=namespaces) [<ns1:child>I'm in namespace 1</ns1:child>]注意:這里需要安裝 xml 解析庫,如果出現(xiàn)以下報錯:
bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
需要 通過?pip install lxml 安裝 lxml:
處理使用命名空間的CSS選擇器時,Beautiful Soup使用在解析文檔時找到的命名空間縮寫。 您可以通過傳入自己的縮寫詞典來覆蓋它:
>>>namespaces = dict(first="http://namespace1/", second="http://namespace2/") >>>soup.select("second|child", namespaces=namespaces) [<ns1:child>I'm in namespace 2</ns1:child>]所有這些CSS選擇器的東西對于已經知道CSS選擇器語法的人來說都很方便。 您可以使用Beautiful Soup API完成所有這些工作。 如果你只需要CSS選擇器,你應該使用lxml解析文檔:它的速度要快得多。 但是這可以讓你將CSS選擇器與Beautiful Soup API結合起來。
8 修改文檔樹
Beautiful Soup的主要優(yōu)勢在于搜索解析樹,但您也可以修改樹并將更改寫為新的HTML或XML文檔。
8.1 修改tag的名稱和屬性
在?5.1 Tag(標簽)-?Attributes(屬性)?章節(jié)中已經介紹過這個功能,但是再看一遍也無妨. 重命名一個tag,改變屬性的值,添加或刪除屬性:
>>> soup = BeautifulSoup('<b class="boldest">Extremely bold</b>') >>> tag = soup.b>>> tag.name = "blockquote" >>> tag['class'] = 'verybold' >>> tag['id'] = 1 >>> tag <blockquote class="verybold" id="1">Extremely bold</blockquote>>>> del tag['class'] >>> del tag['id'] >>> tag <blockquote>Extremely bold</blockquote>8.2 修改 .string
給tag的?.string?屬性賦值,就相當于用當前的內容替代了原來的內容:
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' >>> soup = BeautifulSoup(markup)>>> tag = soup.a >>> tag.string = "New link text." >>> tag <a href="http://example.com/">New link text.</a>注意:如果當前的tag包含了其它tag,那么給它的?.string?屬性賦值會覆蓋掉原有的所有內容包括子tag。
8.3 append()
Tag.append()?方法想tag中添加內容,就好像Python的列表的?.append()?方法:
>>> soup = BeautifulSoup("<a>Foo</a>") >>> soup.a.append("Bar")>>> soup <a>FooBar</a> >>> soup.a.contents ['Foo', 'Bar']8.4 extend()
從Beautiful Soup 4.7.0開始,Tag還支持一個名為.extend()的方法,它就像在Python列表上調用.extend()一樣:
>>> soup = BeautifulSoup("<a>Soup</a>") >>> soup.a.extend(["'s", " ", "on"])>>> soup <a>Soup's on</a> >>> soup.a.contents ['Soup', "'s", ' ', 'on']8.5 NevigableString() 和 .new_tag()
如果想添加一段文本內容到文檔中也沒問題,可以調用Python的?append()?方法或調用 NavigableString() 構造函數(shù):?:
>>> soup = BeautifulSoup("<b></b>") >>> tag = soup.b >>> tag.append("Hello") >>> new_string = NavigableString(" there") >>> tag.append(new_string) >>> tag <b>Hello there.</b> >>> tag.contents ['Hello', ' there']如果要創(chuàng)建注釋或NavigableString的其他子類,只需調用構造函數(shù):
>>> from bs4 import Comment >>> new_comment = Comment("Nice to see you.") >>> tag.append(new_comment) >>> tag <b>Hello there<!--Nice to see you.--></b> >>> tag.contents ['Hello', ' there', 'Nice to see you.'](這是Beautiful Soup 4.4.0的新功能。)
創(chuàng)建一個tag最好的方法是調用工廠方法?BeautifulSoup.new_tag()?:
>>> soup = BeautifulSoup("<b></b>") >>> original_tag = soup.b>>> new_tag = soup.new_tag("a", href="http://www.example.com") >>> original_tag.append(new_tag) >>> original_tag <b><a href="http://www.example.com"></a></b>>>> new_tag.string = "Link text." >>> original_tag <b><a href="http://www.example.com">Link text.</a></b>第一個參數(shù)作為tag的name,是必填,其它參數(shù)選填。
8.6 insert()
Tag.insert()?方法與?Tag.append()?方法類似,區(qū)別是不會把新元素添加到父節(jié)點?.contents?屬性的最后,而是把元素插入到指定的位置。與Python列表總的?.insert()?方法的用法相同:
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' >>> soup = BeautifulSoup(markup) >>> tag = soup.a>>> tag.insert(1, "but did not endorse ")>>> tag <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>>>> tag.contents ['I linked to ', 'but did not endorse ', <i>example.com</i>]8.7 insert_before() 和 insert_after()
insert_before()?方法在當前tag或文本節(jié)點前插入tag 或者 字符串:
>>> soup = BeautifulSoup("<b>stop</b>") >>> tag = soup.new_tag("i") >>> tag.string = "Don't" >>> soup.b.string.insert_before(tag) >>> soup.b <b><i>Don't</i>stop</b>insert_after()?方法在當前tag或文本節(jié)點前插入tag 或者 字符串:
>>> div = soup.new_tag('div') >>> div.string = 'ever' >>> soup.b.i.insert_after(" you ", div) >>> soup.b <b><i>Don't</i> you <div>ever</div>stop</b> >>> soup.b.contents [<i>Don't</i>, ' you ', <div>ever</div>, 'stop']8.8 clear()
Tag.clear()?方法移除當前tag的內容:
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' >>> soup = BeautifulSoup(markup) >>> tag = soup.a>>> tag.clear() >>> tag <a href="http://example.com/"></a>8.9 extract()
PageElement.extract()?方法將當前tag移除文檔樹,并作為方法結果返回:
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' >>> soup = BeautifulSoup(markup) >>> a_tag = soup.a>>> i_tag = soup.i.extract()>>> a_tag <a href="http://example.com/">I linked to </a>>>> i_tag <i>example.com</i>>>> print(i_tag.parent) None這個方法實際上產生了2個文檔樹: 一個是用來解析原始文檔的?BeautifulSoup?對象,另一個是被移除并且返回的tag。被移除并返回的tag可以繼續(xù)調用?extract?方法:
>>> my_string = i_tag.string.extract() >>> my_string 'example.com'>>> print(my_string.parent) None >>> i_tag <i></i>8.10 decompose()
Tag.decompose()?方法將當前節(jié)點移除文檔樹并完全銷毀:
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' >>> soup = BeautifulSoup(markup) >>> a_tag = soup.a>>> soup.i.decompose()>>> a_tag <a href="http://example.com/">I linked to </a>8.11 replace_with()
PageElement.replace_with()?方法移除文檔樹中的某段內容,并用新tag或文本節(jié)點替代它:
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' >>> soup = BeautifulSoup(markup) >>> a_tag = soup.a>>> new_tag = soup.new_tag("b") >>> new_tag.string = "example.net" >>> a_tag.i.replace_with(new_tag)>>> a_tag <a href="http://example.com/">I linked to <b>example.net</b></a>replace_with()?方法返回被替代的tag或文本節(jié)點,可以用來瀏覽或添加到文檔樹其它地方
8.12 wrap()
PageElement.wrap()?方法可以對指定的tag元素進行包裝,并返回包裝后的結果:
>>> soup = BeautifulSoup("<p>I wish I was bold.</p>") >>> soup.p.string.wrap(soup.new_tag("b")) <b>I wish I was bold.</b>>>> soup.p.wrap(soup.new_tag("div") <div><p><b>I wish I was bold.</b></p></div>該方法在 Beautiful Soup 4.0.5 中添加
8.13 unwrap()
Tag.unwrap()?方法與?wrap()?方法相反。將移除tag內的所有tag標簽,該方法常被用來進行標記的解包:
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' >>> soup = BeautifulSoup(markup) >>> a_tag = soup.a>>> a_tag.i.unwrap() >>> a_tag <a href="http://example.com/">I linked to example.com</a>與?replace_with()?方法相同,?unwrap()?方法返回被移除的tag。
九、輸出
9.1 格式化輸出
prettify()?方法將Beautiful Soup的文檔樹格式化后以Unicode編碼輸出,每個XML/HTML標簽都獨占一行
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' >>> soup = BeautifulSoup(markup) >>> soup.prettify() '<html>\n <body>\n <a href="http://example.com/">\n I linked to\n <i>\n example.com\n </i>\n </a>\n </body>\n</html>' >>> print(soup.prettify()) <html><body><a href="http://example.com/">I linked to<i>example.com</i></a></body> </html>BeautifulSoup?對象和它的tag節(jié)點都可以調用?prettify()?方法:
>>> print(soup.a.prettify()) <a href="http://example.com/">I linked to<i>example.com</i> </a>9.2 壓縮輸出
如果只想得到結果字符串,不重視格式,那么可以對一個?BeautifulSoup?對象或?Tag?對象使用Python的?unicode()?或?str()?方法:
>>> str(soup) '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'>>> unicode(soup.a) u'<a href="http://example.com/">I linked to <i>example.com</i></a>'str()?方法返回UTF-8編碼的字符串,可以指定?編碼?的設置。
還可以調用?encode()?方法獲得字節(jié)碼或調用?decode()?方法獲得Unicode.
9.3 輸出格式
Beautiful Soup輸出是會將HTML中的特殊字符轉換成Unicode,比如“&lquot;”:
>>> soup = BeautifulSoup("“Dammit!” he said.") >>> unicode(soup) u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'如果將文檔轉換成字符串,Unicode編碼會被編碼成UTF-8.這樣就無法正確顯示HTML特殊字符了:
>>> str(soup) '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'默認情況下,輸出時轉義的唯一字符是裸字符和尖括號。 這些變成了“&amp;”,“&lt;”和“&gt;”,因此Beautiful Soup不會無意中生成無效的HTML或XML:
>>> soup = BeautifulSoup("<p>The law firm of Dewey, Cheatem, & Howe</p>") >>> soup.p <p>The law firm of Dewey, Cheatem, & Howe</p>>>> soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>') >>> soup.a <a href="http://example.com/?foo=val1&bar=val2">A link</a>您可以通過為prettify(),encode()或decode()提供formatter參數(shù)的值來更改此行為。 Beautiful Soup識別格式化程序的六個可能值。
默認值為formatter =“minimal”。 字符串只會被處理得足以確保Beautiful Soup生成有效的HTML / XML:
>>> french = "<p>Il a dit <<Sacré bleu!>></p>" >>> soup = BeautifulSoup(french) >>> print(soup.prettify(formatter="minimal")) <html><body><p>Il a dit <<Sacré bleu!>></p></body> </html>如果您傳入formatter =“html”,Beautiful Soup會盡可能將Unicode字符轉換為HTML實體:
>>> print(soup.prettify(formatter="html")) <html><body><p>Il a dit <<Sacré bleu!>></p></body> </html>#如果您傳入formatter =“html5”,是一樣的 >>> print(soup.prettify(formatter="html5")) <html><body><p>Il a dit <<Sacré bleu!>></p></body> </html>但是如果您傳入formatter =“html5”,Beautiful Soup會省略HTML空標簽中的結束斜杠,比如“br”:
>>> soup = BeautifulSoup("<br>")>>> print(soup.encode(formatter="html")) <html><body><br/></body></html>>>> print(soup.encode(formatter="html5")) <html><body><br></body></html>如果傳入formatter = None,Beautiful Soup將不會在輸出時修改字符串。 這是最快的選項,但它可能導致Beautiful Soup生成無效的HTML / XML,如下例所示:
>>> print(soup.prettify(formatter=None)) <html><body><p>Il a dit <<Sacré bleu!>></p></body> </html> >>> link_soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>') >>> print(link_soup.a.encode(formatter=None)) b'<a href="http://example.com/?foo=val1&bar=val2">A link</a>'最后,如果傳入格式化程序的函數(shù),Beautiful Soup將為文檔中的每個字符串和屬性值調用該函數(shù)一次。 你可以在這個功能中做任何你想做的事情。 這是一個將字符串轉換為大寫的格式化程序,并且完全沒有其他內容:
>>> def uppercase(str):return str.upper()>>> print(soup.prettify(formatter=uppercase)) <html><body><p>IL A DIT <<SACRé BLEU!>></p></body> </html> >>> print(link_soup.a.prettify(formatter=uppercase)) <a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2">A LINK </a>如果您正在編寫自己的函數(shù),則應該了解bs4.dammit模塊中的EntitySubstitution類。 此類將Beautiful Soup的標準格式化程序實現(xiàn)為類方法:“html”格式化程序是EntitySubstitution.substitute_html,“minimal”格式化程序是EntitySubstitution.substitute_xml。 您可以使用這些函數(shù)來模擬formatter = html或formatter == minimal,但然后再做一些額外的事情。
這是一個盡可能用HTML實體替換Unicode字符的示例,但也將所有字符串轉換為大寫:
>>> from bs4.dammit import EntitySubstitution >>> def uppercase_and_substitute_html_entities(str):return EntitySubstitution.substitute_html(str.upper())>>> print(soup.prettify(formatter=uppercase_and_substitute_html_entities)) <html><body><p>IL A DIT <<SACRÉ BLEU!>></p></body> </html>最后一點需要注意的是:如果您創(chuàng)建了一個CData對象,該對象內的文本將始終與其顯示完全一致,沒有格式化。 Beautiful Soup會調用formatter方法,以防萬一你編寫了一個自定義方法來計算文檔中的所有字符串,但它會忽略返回值:
>>> from bs4.element import CData >>> soup = BeautifulSoup("<a></a>") >>> soup.a.string = CData("one < three") >>> print(soup.a.prettify(formatter="xml")) <a><![CDATA[one < three]]> </a>9.4 get_text()
如果只想得到tag中包含的文本內容,那么可以使用?get_text()?方法,這個方法獲取到tag中包含的所有文版內容包括子孫tag中的內容,并將結果作為Unicode字符串返回:
>>> markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>' >>> soup = BeautifulSoup(markup)>>> soup.get_text() '\nI linked to example.com\n' >>> soup.i.get_text() 'example.com'可以通過參數(shù)指定tag的文本內容的分隔符:
>>> soup.get_text("|") '\nI linked to |example.com|\n'還可以去除獲得文本內容的前后空白:
>>> soup.get_text("|", strip=True) 'I linked to|example.com'或者使用?6.1 子節(jié)點(向下遍歷)中 .stripped_strings? 生成器,獲得文本列表后手動處理列表:
>>> [text for text in soup.stripped_strings] ['I linked to', 'example.com']十、指定文檔解析器
如果僅是想要解析HTML文檔,只要用文檔創(chuàng)建?BeautifulSoup?對象就可以了.Beautiful Soup會自動選擇一個解析器來解析文檔.但是還可以通過參數(shù)指定使用那種解析器來解析當前文檔。
BeautifulSoup?第一個參數(shù)應該是要被解析的文檔字符串或是文件句柄,第二個參數(shù)用來標識怎樣解析文檔.如果第二個參數(shù)為空,那么Beautiful Soup根據(jù)當前系統(tǒng)安裝的庫自動選擇解析器,解析器的優(yōu)先數(shù)序: lxml, html5lib, Python標準庫.在下面兩種條件下解析器優(yōu)先順序會變化:
- 要解析的文檔是什么類型: 目前支持, “html”, “xml”, 和 “html5”
- 指定使用哪種解析器: 目前支持, “l(fā)xml”, “html5lib”, 和 “html.parser”
3.2 安裝解析器?章節(jié)介紹了可以使用哪種解析器,以及如何安裝。
如果指定的解析器沒有安裝,Beautiful Soup會自動選擇其它方案.目前只有 lxml 解析器支持XML文檔的解析,在沒有安裝lxml庫的情況下,創(chuàng)建?beautifulsoup?對象時無論是否指定使用lxml,都無法得到解析后的對象
10.1 解析器之間的區(qū)別
Beautiful Soup為不同的解析器提供了相同的接口,但解析器本身時有區(qū)別的.同一篇文檔被不同的解析器解析后可能會生成不同結構的樹型文檔.區(qū)別最大的是HTML解析器和XML解析器,看下面片段被解析成HTML結構:
>>> BeautifulSoup("<a><b /></a>") <html><body><a><b></b></a></body></html>因為空標簽<b />不符合HTML標準,所以解析器把它解析成<b></b>
同樣的文檔使用XML解析如下(解析XML需要安裝lxml庫).注意,空標簽<b />依然被保留,并且文檔前添加了XML頭,而不是被包含在<html>標簽內:
>>> BeautifulSoup("<a><b /></a>", "xml") <?xml version="1.0" encoding="utf-8"?> <a><b/></a>HTML解析器之間也有區(qū)別,如果被解析的HTML文檔是標準格式,那么解析器之間沒有任何差別,只是解析速度不同,結果都會返回正確的文檔樹.
但是如果被解析文檔不是標準格式,那么不同的解析器返回結果可能不同.下面例子中,使用lxml解析錯誤格式的文檔,結果</p>標簽被直接忽略掉了:
>>> BeautifulSoup("<a></p>", "lxml") <html><body><a></a></body></html>使用html5lib庫解析相同文檔會得到不同的結果:
>>> BeautifulSoup("<a></p>", "html5lib") <html><head></head><body><a><p></p></a></body></html>html5lib庫沒有忽略掉</p>標簽,而是自動補全了標簽,還給文檔樹添加了<head>標簽.
使用pyhton內置庫解析結果如下:
>>> BeautifulSoup("<a></p>", "html.parser") <a></a>與lxml?庫類似的,Python內置庫忽略掉了</p>標簽,與html5lib庫不同的是標準庫沒有嘗試創(chuàng)建符合標準的文檔格式或將文檔片段包含在<body>標簽內,與lxml不同的是標準庫甚至連<html>標簽都沒有嘗試去添加.
因為文檔片段“<a></p>”是錯誤格式,所以以上解析方式都能算作”正確”,html5lib庫使用的是HTML5的部分標準,所以最接近”正確”.不過所有解析器的結構都能夠被認為是”正常”的.
不同的解析器可能影響代碼執(zhí)行結果,如果在分發(fā)給別人的代碼中使用了?BeautifulSoup?,那么最好注明使用了哪種解析器,以減少不必要的麻煩.
十一、編碼
任何HTML或XML文檔都有自己的編碼方式,比如ASCII 或 UTF-8,但是使用Beautiful Soup解析后,文檔都被轉換成了Unicode:
>>> markup = "<h1>Sacr\xc3\xa9 bleu!</h1>" >>> soup = BeautifulSoup(markup) >>> soup.h1 <h1>Sacr?? bleu!</h1> >>> soup.h1.string 'Sacr?? bleu!'這不是魔術(但很神奇),Beautiful Soup用了 編碼自動檢測?子庫來識別當前文檔編碼并轉換成Unicode編碼.?BeautifulSoup?對象的?.original_encoding?屬性記錄了自動識別編碼的結果:
>>> soup.original_encoding 'utf-8'編碼自動檢測?功能大部分時候都能猜對編碼格式,但有時候也會出錯.有時候即使猜測正確,也是在逐個字節(jié)的遍歷整個文檔后才猜對的,這樣很慢.如果預先知道文檔編碼,可以設置編碼參數(shù)來減少自動檢查編碼出錯的概率并且提高文檔解析速度.在創(chuàng)建?BeautifulSoup?對象的時候設置?from_encoding?參數(shù).
下面一段文檔用了ISO-8859-8編碼方式,這段文檔太短,結果Beautiful Soup以為文檔是用windows-1252編碼:
>>> markup = b"<h1>\xed\xe5\xec\xf9</h1>" >>> soup = BeautifulSoup(markup) >>> soup.h1 <h1>í?ìù</h1> >>> soup.original_encoding 'windows-1252'通過傳入?from_encoding?參數(shù)來指定編碼方式:
>>> soup = BeautifulSoup(markup, from_encoding="iso-8859-8") >>> soup.h1 <h1>????</h1> >>> soup.original_encoding 'iso-8859-8'少數(shù)情況下(通常是UTF-8編碼的文檔中包含了其它編碼格式的文件),想獲得正確的Unicode編碼就不得不將文檔中少數(shù)特殊編碼字符替換成特殊Unicode編碼,“REPLACEMENT CHARACTER” (U+FFFD, �)?. 如果Beautifu Soup猜測文檔編碼時作了特殊字符的替換,那么Beautiful Soup會把?UnicodeDammit?或?BeautifulSoup?對象的?.contains_replacement_characters?屬性標記為?True?.這樣就可以知道當前文檔進行Unicode編碼后丟失了一部分特殊內容字符.如果文檔中包含�而?.contains_replacement_characters?屬性是?False?,則表示�就是文檔中原來的字符,不是轉碼失敗.
11.1 輸出編碼
通過Beautiful Soup輸出文檔時,不管輸入文檔是什么編碼方式,輸出編碼均為UTF-8編碼,下面例子輸入文檔是Latin-1編碼:
>>> markup = b''' <html><head><meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" /></head><body><p>Sacr\xe9 bleu!</p></body> </html> ''' >>> soup = BeautifulSoup(markup) >>> print(soup.prettify()) <html><head><meta content="text/html; charset=utf-8" http-equiv="Content-type"/></head><body><p>Sacré bleu!</p></body> </html>注意,輸出文檔中的<meta>標簽的編碼設置已經修改成了與輸出編碼一致的UTF-8.
如果不想用UTF-8編碼輸出,可以將編碼方式傳入?prettify()?方法:
>>> print(soup.prettify("latin-1")) b'<html>\n <head>\n <meta content="text/html; charset=latin-1" http-equiv="Content-type"/>\n </head>\n <body>\n <p>\n Sacr\xe9 bleu!\n </p>\n </body>\n</html>\n'還可以調用?BeautifulSoup?對象或任意節(jié)點的?encode()?方法,就像Python的字符串調用?encode()?方法一樣:
>>> soup.p.encode("latin-1") b'<p>Sacr\xe9 bleu!</p>'>>> soup.p.encode("utf-8") b'<p>Sacr\xc3\xa9 bleu!</p>'如果文檔中包含當前編碼不支持的字符,那么這些字符將唄轉換成一系列XML特殊字符引用,下面例子中包含了Unicode編碼字符SNOWMAN:
>>> markup = u"<b>\N{SNOWMAN}</b>" >>> snowman_soup = BeautifulSoup(markup) >>> tag = snowman_soup.bSNOWMAN字符在UTF-8編碼中可以正常顯示(看上去像是?),但有些編碼不支持SNOWMAN字符,比如ISO-Latin-1或ASCII,那么在這些編碼中SNOWMAN字符會被轉換成“☃”:
>>> print(tag.encode("utf-8")) b'<b>\xe2\x98\x83</b>'>>> print(tag.encode("latin-1")) b'<b>☃</b>'>>> print(tag.encode("ascii")) b'<b>☃</b>'11.2 Unicode, dammit! (靠!)
編碼自動檢測 功能可以在Beautiful Soup以外使用,檢測某段未知編碼時,可以使用這個方法:
>>> from bs4 import UnicodeDammit >>> dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!") >>> print(dammit.unicode_markup) Sacr?? bleu! >>> dammit.original_encoding 'utf-8'如果Python中安裝了?chardet?或?cchardet?那么編碼檢測功能的準確率將大大提高.輸入的字符越多,檢測結果越精確,如果事先猜測到一些可能編碼,那么可以將猜測的編碼作為參數(shù),這樣將優(yōu)先檢測這些編碼:
>>> dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"]) >>> print(dammit.unicode_markup) Sacré bleu! >>> dammit.original_encoding 'latin-1'編碼自動檢測 功能中有2項功能是Beautiful Soup庫中用不到的
11.2.1 智能引號
使用Unicode時,Beautiful Soup還會智能的把引號?轉換成HTML或XML中的特殊字符:
>>> markup = b"<p>I just \x93love\x94 Microsoft Word\x92s smart quotes</p>">>> UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup u'<p>I just “love” Microsoft Word’s smart quotes</p>'>>> UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup u'<p>I just “love” Microsoft Word’s smart quotes</p>'也可以把引號轉換為ASCII碼:
>>> UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup u'<p>I just "love" Microsoft Word\'s smart quotes</p>'很有用的功能,但是Beautiful Soup沒有使用這種方式.默認情況下,Beautiful Soup把引號轉換成Unicode:
>>> UnicodeDammit(markup, ["windows-1252"]).unicode_markup u'<p>I just \u201clove\u201d Microsoft Word\u2019s smart quotes</p>'矛盾的編碼
有時文檔的大部分都是用UTF-8,但同時還包含了Windows-1252編碼的字符,就像微軟的智能引號?一樣.一些包含多個信息的來源網站容易出現(xiàn)這種情況.?UnicodeDammit.detwingle()?方法可以把這類文檔轉換成純UTF-8編碼格式,看個簡單的例子:
>>> snowmen = (u"\N{SNOWMAN}" * 3) >>> quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}") >>> doc = snowmen.encode("utf8") + quote.encode("windows_1252")這段文檔很雜亂,snowmen是UTF-8編碼,引號是Windows-1252編碼,直接輸出時不能同時顯示snowmen和引號,因為它們編碼不同:
>>> print(doc) ???�I like snowmen!�>>> print(doc.decode("windows-1252")) a??a??a??“I like snowmen!”如果對這段文檔用UTF-8解碼就會得到?UnicodeDecodeError?異常,如果用Windows-1252解碼就回得到一堆亂碼.幸好,?UnicodeDammit.detwingle()?方法會吧這段字符串轉換成UTF-8編碼,允許我們同時顯示出文檔中的snowmen和引號:
>>> new_doc = UnicodeDammit.detwingle(doc) >>> print(new_doc.decode("utf8")) ???“I like snowmen!”UnicodeDammit.detwingle()?方法只能解碼包含在UTF-8編碼中的Windows-1252編碼內容,但這解決了最常見的一類問題.
在創(chuàng)建?BeautifulSoup?或?UnicodeDammit?對象前一定要先對文檔調用?UnicodeDammit.detwingle()?確保文檔的編碼方式正確.如果嘗試去解析一段包含Windows-1252編碼的UTF-8文檔,就會得到一堆亂碼,比如: a??a??a??“I like snowmen!”.
UnicodeDammit.detwingle()?方法在Beautiful Soup 4.1.0版本中新增
十二、比較對象是否相等
Beautiful Soup表示當兩個NavigableString或Tag對象表示相同的HTML或XML標記時,它們是相同的。 在此示例中,兩個<b>標記被視為相等,即使它們位于對象樹的不同部分,因為它們看起來都像“<b> pizza </ b>”:
>>> markup = "<p>I want <b>pizza</b> and more <b>pizza</b>!</p>" >>> soup = BeautifulSoup(markup, 'html.parser') >>> first_b, second_b = soup.find_all('b')>>> print(first_b == second_b) True>>> print(first_b.previous_element == second_b.previous_element) False如果要查看兩個變量是否指向完全相同的對象,請使用 is :
>>> print(first_b is second_b) False十三、復制 Beautiful Soup 對象
您可以使用copy.copy()創(chuàng)建任何Tag或NavigableString的副本:
>>> import copy >>> p_copy = copy.copy(soup.p) >>> print(p_copy) <p>I want <b>pizza</b> and more <b>pizza</b>!</p>該副本被認為與原始副本相同,因為它表示與原始副本相同的標記,但它不是同一個對象:
>>> print(soup.p == p_copy) True>>> print(soup.p is p_copy) False唯一真正的區(qū)別是副本與原始的Beautiful Soup對象樹完全分離,就像在它上面調用了extract()一樣:
>>> print(p_copy.parent) None十四、解析部分文檔
?
?
?
總結
以上是生活随笔為你收集整理的BeautifulSoup4 模块中文文档的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: mysql5.7 删除复制_mysql5
- 下一篇: Windows Phone 开发人员设置
