GitHub@ orca-j35,全部筆記均託管於 python_notes 倉庫。
歡迎任何形式的轉載,但請務必註明出處。
⚠官方文檔中混雜了 Py2 和 Py3 的術語和代碼,本筆記針對 Py3 梳理了文檔中的內容,在瞭解 BeautifulSoup 的過程當中,建議將本筆記與官方文檔配合食用。css
Beautiful Soup 是一個用來從 HTML 或 XML 文件中提取數據的 Python 庫。在使用 BeautifulSoup 時,咱們選擇本身喜歡的解析器,從而以本身熟悉的方式來導航、查找和修改解析樹。html
相關資源:html5
安裝:python
pip install beautifulsoup4
若是遇到安裝問題,能夠參考:git
若是能順利執行如下代碼,則說明安裝成功:github
from bs4 import BeautifulSoup soup = BeautifulSoup('<p>Hello</p>', 'lxml') print(soup.p.string) #> Hello
⚠在安裝庫和導入庫時使用的名稱不必定相同,例如: 在安裝 BeautifulSoup4 時,使用的名稱是 beautifulsoup4
;在導入時,使用的名稱是 bs4
(路徑爲 ~\Python\Lib\site-packages\bs4
)。shell
若是在使用過程當中遇到本文未涵蓋的問題,請參考: https://www.crummy.com/softwa...數據結構
下面這段名爲 "Three sisters" 文檔是本筆記的 HTML 示例文檔(官方文檔中也用的這段代碼):ide
html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <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>; and they lived at the bottom of a well.</p> <p class="story">...</p> """
這段 HTML 文檔存在 "tag soup",HTML 解析器會自動修復 "tag soup"函數
BeautifulSoup 的速度永遠會低於其使用的解析器的速度。若是對速度有嚴格要求,應直接使用 lxml 庫來解析。
對 BeautifulSoup 而言,lxml 解析器的速度比 html.parser 或 html5lib 更快。
能夠經過安裝 cchardet 庫來顯著提高檢測編碼方案的速度。
僅解析部分文檔並不會節省大量的解析時間,可是能夠節省大量內存,並有效提高檢索文檔的速度。
🛠BeautifulSoup(self, markup="", features=None, builder=None, parse_only=None, from_encoding=None, exclude_encodings=None, **kwargs)
構造器 BeautifulSoup()
中各參數的含義以下:
markup
- 要解析的標籤(markup),能夠是字符串或 file-like 對象。
from bs4 import BeautifulSoup with open("index.html") as fp: soup = BeautifulSoup(fp) soup = BeautifulSoup("<html>data</html>")
features
- 用來設置解析器,可以使用解析器的名稱("lxml", "lxml-xml", "html.parser", "html5lib"),或使用標籤的類型("html", "html5", "xml")。建議明確給出須要使用的解析器,以便 BeautifulSoup 在不一樣的平臺和虛擬環境中提供相同的結果。默認狀況下,BeautifulSoup 會以 HTML 格式解析文檔,若是要以 XML 格式解析文檔,則需設置 features='xml'
。目前支持解析 XML 的解析器僅有 lxml。
若是沒有手動設置解析器,BeautifulSoup 將會在已安裝的解析器中選一個最好用的 HTML 解析器,解析器的優先級依次是 lxml’s HTML parser > html5lib's parser > Python’s html.parser。
若是已手動設置某解析器,可是併爲安裝該解析器,BeautifulSoup 將忽略該設置並按照優先級選擇一個解析器。
builder
- 不須要使用的參數(A specific TreeBuilder to use instead of looking one up based on features
)。parse_only
- 以 SoupStrainer 對象做爲實參值。在解析文檔的過程當中只會考慮與 SoupStrainer 匹配的部分。當咱們只須要解析某部分文檔時很是有用,好比因爲文檔太大而沒法放所有放入內存時,即可以考慮只解析某部分文檔。from_encoding
- 一個字符串,表示被解析的文檔的編碼。若是 BeautifulSoup 在猜想文檔編碼時出現錯誤,請傳遞此參數。exclude_encodings
- 一個字符串列表,表示已知的錯誤編碼。若是你不知道文檔編碼,但你知道 BeautifulSoup 的猜想出現錯誤時,請傳遞此參數。**kwargs
- 爲了保證向後兼容,構造可接受 BeautifulSoup3 中使用的某些關鍵字參數,但這些關鍵字參數在 BeautifulSoup4 中並不會執行任何操做。Beautiful Soup 支持 Python 標準庫中的 HTML 解析器,同時還支持一些第三方的解析器(如 lxml):
BeautifulSoup(markup,"html.parser")
BeautifulSoup(markup, "lxml")
BeautifulSoup(markup, "lxml-xml")
或 BeautifulSoup(markup, "xml")
BeautifulSoup(markup, "html5lib")
默認狀況下,BeautifulSoup 會以 HTML 格式解析文檔,若是要以 XML 格式解析文檔,則需設置 features='xml'
。目前支持解析 XML 的解析器僅有 lxml。
若是沒有手動設置解析器,BeautifulSoup 將會在已安裝的解析器中選一個最好用的 HTML 解析器,解析器的優先級依次是 lxml’s HTML parser > html5lib's parser > Python’s html.parser。
若是已手動設置某解析器,可是併爲安裝該解析器,BeautifulSoup 將忽略該設置並按照優先級選擇一個解析器。
第三方解析器的安裝方法和優缺點對比: Installing a parser
建議使用 lxml 解析器來提升解析速度。早於 2.7.3 和 3.2.2 的 Python 版本,必須使用 lxml 和 html5lib 解析器,由於這些版本的內置 HTML 解析器不夠穩定。
Note: 若是試圖解析無效的 HTML/XML 文檔,不一樣解析器可能會給出不一樣的結果。
有關解析器間的具體差別,詳見: Specifying the parser to use
參考: https://www.crummy.com/softwa...
默認狀況下,BeautifulSoup 會以 HTML 格式解析文檔,若是要以 XML 格式解析文檔,則需設置 features='xml'
。目前支持解析 XML 的解析器僅有 lxml。
參考: https://www.crummy.com/softwa...
HTML 或 XML 文檔可能會採用不一樣的編碼方案(如 ASCII 或 UTF-8),當你將文檔加載到 BeautifulSoup 後,便會自動轉換爲 Unicode。
markup = "<h1>Sacr\xc3\xa9 bleu!</h1>" soup = BeautifulSoup(markup, 'lxml') print(soup.h1) #> <h1>Sacré bleu!</h1> print(soup.h1.string) #> u'Sacr\xe9 bleu!'
BeautifulSoup 會使用一個叫作 Unicode, Dammit 的子庫來檢測文檔編碼並將其轉換爲 Unicode。 BeautifulSoup
對象的 .original_encoding
屬性記錄了自動識別編碼的結果:
print(soup.original_encoding) #> 'utf-8'
在大多數時候,Unicode, Dammit 可以猜想出正確的編碼方案,可是偶爾也會犯錯。有時候即使猜想正確,但也須要先逐字節遍歷文檔後才能給出答案,這樣很是耗時。若是你知道文檔的編碼方案,則能夠經過 from_encoding
參數來設置編碼方案,從而避免錯誤和延遲。
Here’s a document written in ISO-8859-8. The document is so short that Unicode, Dammit can’t get a lock on it, and misidentifies it as ISO-8859-7:
markup = b"<h1>\xed\xe5\xec\xf9</h1>" soup = BeautifulSoup(markup) soup.h1 <h1>νεμω</h1> soup.original_encoding 'ISO-8859-7'We can fix this by passing in the correct
from_encoding
:soup = BeautifulSoup(markup, from_encoding="iso-8859-8") soup.h1 <h1>םולש</h1> soup.original_encoding 'iso8859-8'
若是你並不知道編碼方案,可是你知道 Unicode, Dammit 給出了錯誤答案,則可使用 exclude_encodings
參數來排除某些編碼方案:
soup = BeautifulSoup(markup, exclude_encodings=["ISO-8859-7"]) soup.h1 <h1>םולש</h1> soup.original_encoding 'WINDOWS-1255'Windows-1255 isn’t 100% correct, but that encoding is a compatible superset of ISO-8859-8, so it’s close enough. (
exclude_encodings
is a new feature in Beautiful Soup 4.4.0.)
若是須要了解更多信息,請閱讀: https://www.crummy.com/softwa...
參考: https://www.crummy.com/softwa...
對於僅須要解析 <a>
標籤狀況而言,先解析整個文檔而後再查找 <a>
標籤標準過程會浪費大量的時間和內存。若是一開始就忽略掉與 <a>
標籤無關的部分,則會有效提高查詢速度。
對於僅須要解析部分文檔的狀況而言,可以使用 SoupStrainer
類篩選出要保留的標籤。
⚠僅解析部分文檔並不會節省大量的解析時間,可是能夠節省大量內存,並有效提高檢索文檔的速度。
⚠html5lib 解析器不支持該功能,緣由以下:
If you use html5lib, the whole document will be parsed, no matter what. This is because html5lib constantly rearranges the parse tree as it works, and if some part of the document didn’t actually make it into the parse tree, it’ll crash. To avoid confusion, in the examples below I’ll be forcing Beautiful Soup to use Python’s built-in parser.
SoupStrainer()
構造器的參數與搜索解析樹的方法相同: name, attrs, text, **kwargs,不可將 text 寫做 string,對 SoupStrainer()
而言 text 和 string 不能等效使用。
示例 - SoupStrainer
對象的使用方法:
from bs4 import SoupStrainer html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <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>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ only_a_tags = SoupStrainer("a") soup = BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags) print([f'{type(i)}::{i.name}' for i in soup]) #> ["<class 'bs4.element.Tag'>::a", "<class 'bs4.element.Tag'>::a", "<class 'bs4.element.Tag'>::a"] only_tags_with_id_link2 = SoupStrainer(id="link2") soup = BeautifulSoup( html_doc, "html.parser", parse_only=only_tags_with_id_link2) print([f'{type(i)}::{i}' for i in soup]) #> ['<class \'bs4.element.Tag\'>::<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>'] def is_short_string(text: str): return len(text) < 10 only_short_strings = SoupStrainer(text=is_short_string) soup = BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings) print([repr(i) for i in soup]) #> ["'\\n'", "'\\n'", "'\\n'", "'\\n'", "'Elsie'", "',\\n'", "'Lacie'", "' and\\n'", "'Tillie'", "'\\n'", "'...'", "'\\n'"]
SoupStrainer
可用做搜索解析樹的方法的參數,這可能並不常見,但仍是提一下:
def is_short_string(text: str): return len(text) < 10 only_short_strings = SoupStrainer(text=is_short_string) soup = BeautifulSoup( html_doc, "html.parser", ) print([repr(i) for i in soup.find_all(only_short_strings)]) #> "'\\n'", "'\\n'", "'\\n'", "'\\n'", "'Elsie'", "',\\n'", "'Lacie'", "' and\\n'", "'Tillie'", "'\\n'", "'...'", "'\\n'"]
參考: Kinds of objects
BeautifulSoup 會將複雜的 HTML 文檔轉換爲複雜的 Python 對象樹,樹中的每一個節點都是一個 Python 對象,共有四種須要處理對象: Tag
, NavigableString
, BeautifulSoup
, Comment
Tag
對象對應於原始文檔中的 XML 或 HTML 標記(tag)。
from bs4 import BeautifulSoup soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml') tag = soup.b print(type(tag)) # <class 'bs4.element.Tag'>
Tag
對象擁有不少屬性和方法,在 Navigating the tree 和 Searching the tree 中有詳細解釋。本小節僅介紹 Tag
對象兩個最重要的特性。
每一個 Tag
對象都有本身的名字,經過 .name
字段訪問:
from bs4 import BeautifulSoup soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml') tag = soup.b print(tag.name) #> b
若是修改了 Tag
對象的 .name
字段,則會影響 BeautifulSoup
對象生成的 HTML 文檔:
from bs4 import BeautifulSoup soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml') tag = soup.b tag.name = "blockquote" print(tag) #> <blockquote class="boldest">Extremely bold</blockquote> print(soup) #> <html><body><blockquote class="boldest">Extremely bold</blockquote></body></html>
一個 HTML 標籤可包含任意數量的屬性(attributes)。例如,標籤 <b id="boldest">
包含名爲 "id"
的屬性,其值爲 "boldest"
。
可將 Tag
對象視做存放標籤屬性的字典,鍵值對由屬性名和屬性值構成,使用方法也與字典相同。另外,還可經過 .attrs
字段來獲取存放標籤屬性的字典。
from bs4 import BeautifulSoup soup = BeautifulSoup('<b id="boldest">Extremely bold</b>', 'lxml') tag = soup.b print(tag['id']) #> boldest print(tag.get('id')) #> boldest print(tag.attrs) #> {'id': 'boldest'}
Tag
對象支持對標籤的屬性進行添加、刪除、修改:
from bs4 import BeautifulSoup soup = BeautifulSoup('<b id="boldest">Extremely bold</b>', 'lxml') tag = soup.b tag['id'] = 'verybold' tag['another-attribute'] = 1 print(tag) #> <b another-attribute="1" id="verybold">Extremely bold</b> del tag['id'] del tag['another-attribute'] print(tag) #> <b>Extremely bold</b> print(tag.get('id', "Don't have")) #> Don't have print(tag['id'] #> KeyError: 'id'
.has_attr()
方法用於判斷 Tag
對象是否包含某個屬性:
from bs4 import BeautifulSoup soup = BeautifulSoup('<b id="boldest">Extremely bold</b>', 'lxml') print(soup.b.has_attr('id')) #> True print(soup.b.has_attr('class')) #> False
HTML 4 中某些屬性能夠具有多個值,HTML 5 在 HTML 4 的基礎上刪除了一些多值屬性,但又引入了一些多值屬性。最多見的多值屬性是 class
(HTML 標籤可持有多個 CSS 類),其它一些多值屬性的例子: rel
, rev
, accept-charset
, headers
, accesskey
。
BeautifulSoup 將多值屬性的值表示爲一個列表:
from bs4 import BeautifulSoup css_soup = BeautifulSoup('<p class="body"></p>', 'lxml') print(css_soup.p['class']) #> ["body"] css_soup = BeautifulSoup('<p class="body strikeout"></p>', 'lxml') print(css_soup.p['class']) #> ["body", "strikeout"]
若是某個屬性看起來好像有多個值,但在任何版本的 HTML 定義中都沒有被定義爲多值屬性,那麼 BeautifulSoup 會將這個屬性做爲字符組返回:
id_soup = BeautifulSoup('<p id="my id"></p>', 'lxml') print(id_soup.p['id']) #> my id
將 Tag
轉換成字符串時,會對多個屬性值進行合併:
print(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 = BeautifulSoup('<p class="body strikeout" id="my id"></p>', 'lxml') print(id_soup.p['class']) #> ['body', 'strikeout'] print(id_soup.p.get_attribute_list('class')) #> ['body', 'strikeout'] print(id_soup.p['id']) #> my id print(id_soup.p.get_attribute_list('id')) #> ['my id']
若是文檔以 XML 格式進行解析,則不會包含多值屬性:
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml') print(xml_soup.p['class']) #> body strikeout
🐘 bs4.element.NavigableString
NavigableString
繼承自 str
類和 PageElement
類,不能對 NavigableString
對象所含字符串進行編輯,可是可使用 replace_with()
方法進行替換:
from bs4 import BeautifulSoup soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml') tag = soup.b tag.string.replace_with("No longer bold") print(tag) #> <b class="boldest">No longer bold</b>
NavigableString
支持 Navigating the tree 和 Searching the tree 中描述大部分功能,但並不是所有功能。因爲 NavigableString
對象只能包含字符串,不能包含其它內容(Tag
對象能夠包含字符串或子 tag),因此 NavigableString
不支持 .contents
或 .string
字段,也不支持 find()
方法。在 NavigableString
上調用 name
字段時,會返回 None
若是想要在 BeautifulSoup
外部使用 NavigableString
中的字符串,你應該先調用 str()
把 NavigableString
對象轉換爲普通的字符串對象。若是不將其轉換爲普通字符串的話,你將始終持有對整個 BeautifulSoup
解析樹的引用,這會浪費大量內存。
可經過 .string
對象獲取 NavigableString
對象,詳見 .string🔧 小節
BeautifulSoup
對象表示整個文檔,在大部分時候,你能夠將其視爲 Tag
對象。BeautifulSoup
對象支持 Navigating the tree 和 Searching the tree 中描述大部分功。
因爲並無與 BeautifulSoup
對象對應的 HTML/XML tag,所以 BeautifulSoup
對象的 name
字段爲 '[document]'
,而且不包含 HTML attributes。
from bs4 import BeautifulSoup soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml') print(type(soup)) #> <class 'bs4.BeautifulSoup'> print(soup.name) #> [document]
Tag
, NavigableString
, BeautifulSoup
幾乎涵蓋了你在 HTML 或 XML 文件中看到的全部內容,可是仍有一些沒有覆蓋到的內容,好比註釋(comment):
from bs4 import BeautifulSoup markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup, 'lxml') comment = soup.b.string print(type(comment)) #> <class 'bs4.element.Comment'> print(comment) #> Hey, buddy. Want to buy a used parser?
Comment
類繼承自 PreformattedString
,PreformattedString
繼承自 NavigableString
。也就是說 Comment
是一種特殊的 NavigableString
類型。
可是當註釋出如今HTML文檔中時,Comment
對象會使用特殊的格式輸出:
from bs4 import BeautifulSoup markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup, 'lxml') print(soup.b.prettify()) '''Out: <b> <!--Hey, buddy. Want to buy a used parser?--> </b> '''
BeautifulSoup 還爲 XML 文檔中可能會出現的其它內容定義了各類類:
CData
ProcessingInstruction
Declaration
Doctype
與 Comment
相似,這些類都是 NavigableString
的子類,並進行了一些擴展。下面這個示例中,將使用 CDATA block 來替換 Comment
:
from bs4 import BeautifulSoup from bs4 import CData markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup, 'lxml') cdata = CData("A CDATA block") comment = soup.b.string comment.replace_with(cdata) print(soup.b.prettify()) '''Out: <b> <![CDATA[A CDATA block]]> </b> '''
參考: https://www.crummy.com/softwa...
Beautiful Soup says that two NavigableString
or Tag
objects are equal when they represent the same HTML or XML markup. In this example, the two <b>
tags are treated as equal, even though they live in different parts of the object tree, because they both look like 「<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
If you want to see whether two variables refer to exactly the same object, use is:
print first_b is second_b # False
參考: https://www.crummy.com/softwa...
You can use copy.copy()
to create a copy of any Tag
or NavigableString
:
import copy p_copy = copy.copy(soup.p) print p_copy # <p>I want <b>pizza</b> and more <b>pizza</b>!</p>
The copy is considered equal to the original, since it represents the same markup as the original, but it’s not the same object:
print soup.p == p_copy # True print soup.p is p_copy # False
The only real difference is that the copy is completely detached from the original Beautiful Soup object tree, just as if extract()
had been called on it:
print p_copy.parent # None
This is because two different Tag
objects can’t occupy the same space at the same time.
擴展閱讀: Output encoding
BeautifulSoup 兼容 Py2 和 Py3 ,但 Py2 和 Py3 中的 str
對象並不相同,這會導出輸出結果存在差別,在獲取輸出時需注意區分。
該方法會將 BeautifulSoup
對象和 Tag
對象中的內容轉換爲 Unicode 字符串。
源代碼中的註釋以下:
def decode(self, indent_level=None, eventual_encoding=DEFAULT_OUTPUT_ENCODING, formatter="minimal"): """Returns a Unicode representation of this tag and its contents. :param eventual_encoding: The tag is destined to be encoded into this encoding. This method is _not_ responsible for performing that encoding. This information is passed in so that it can be substituted in if the document contains a <META> tag that mentions the document's encoding. """
對 Py3 而言,decode()
將返回 str
對象(Uncode 字符串):
# in Python3 from bs4 import BeautifulSoup markup = '<a href="http://example.com/">鏈接到<i>example.com</i></a>' soup = BeautifulSoup(markup, 'lxml') print(type(soup.decode())) #> <class 'str'> print(soup.decode()) #> <html><body><a href="http://example.com/">鏈接到<i>example.com</i></a></body></html>
對 Py2 而言,decode()
將返回 Unicode
對象(Uncode 字符串):
# in Python2 >>> markup = u'<a href="http://example.com/">鏈接到<i>example.com</i></a>' >>> soup = BeautifulSoup(markup, 'lxml') >>> print(type(soup.decode())) <type 'unicode'> >>> print(soup.decode()) <html><body><a href="http://example.com/">鏈接到<i>example.com</i></a></body></html>
該方法會先將數據結構轉換爲 Unicode 字符串,再按照指定編碼對 Unicode 字符串進行編碼,默認採用 UTF-8 編碼。源代碼以下:
def encode(self, encoding=DEFAULT_OUTPUT_ENCODING, indent_level=None, formatter="minimal", errors="xmlcharrefreplace"): # Turn the data structure into Unicode, then encode the # Unicode. u = self.decode(indent_level, encoding, formatter) return u.encode(encoding, errors)
對 Py3 而言,encode()
將返回以 encoding
編碼(默認採用 UTF-8)的 bytes
對象:
# in Python3 from bs4 import BeautifulSoup markup = '<a href="http://example.com/">鏈接到<i>example.com</i></a>' soup = BeautifulSoup(markup, 'lxml') print(type(soup.encode())) #> <class 'bytes'> print(soup.encode()) #> b'<html><body><a href="http://example.com/">\xe8\xbf\x9e\xe6\x8e\xa5\xe5\x88\xb0<i>example.com</i></a></body></html>'
對 Py2 而言,encode()
將返回以 encoding
編碼(默認採用 UTF-8)的 str
對象(Py2 和 Py3 中的 str
對象並不相同):
# in Python2 >>> markup = u'<a href="http://example.com/">鏈接到<i>example.com</i></a>' >>> soup = BeautifulSoup(markup, 'lxml') >>> print(soup.encode()) <html><body><a href="http://example.com/">鏈接到<i>example.com</i></a></body></html> >>> soup.encode() '<html><body><a href="http://example.com/">\xe8\xbf\x9e\xe6\x8e\xa5\xe5\x88\xb0<i>example.com</i></a></body></html>' >>> type(soup.encode()) <type 'str'>
參考: https://www.crummy.com/softwa...
🔨prettify(self, encoding=None, formatter="minimal")
當 encoding==None
時,prettify()
會將 BeautifulSoup 解析樹轉換爲格式良好的 Unicode 字符串,在字符串中每一個 HTML/XML tag 和 字符串都會獨佔一行;當 encoding!=None
時,prettify()
會將 BeautifulSoup 解析樹編碼爲格式良好的 bytes
字符串。
prettify()
的源代碼以下:
# prettify()的源代碼 def prettify(self, encoding=None, formatter="minimal"): if encoding is None: return self.decode(True, formatter=formatter) else: return self.encode(encoding, True, formatter=formatter)
示例 - in Py3:
# in Python3 from bs4 import BeautifulSoup markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup, 'lxml') print(type(soup.prettify())) #> <class 'str'> print(soup.prettify()) '''Out: <html> <body> <a href="http://example.com/"> I linked to <i> example.com </i> </a> </body> </html> '''
prettify()
適用於 BeautifulSoup
對象和 Tag
對象:
print(soup.a.prettify()) '''Out: <a href="http://example.com/"> I linked to <i> example.com </i> </a> '''
示例 - in Py2:
# in Python2 from bs4 import BeautifulSoup markup = u'<a href="http://example.com/">鏈接到<i>example.com</i></a>' soup = BeautifulSoup(markup, 'lxml') print(soup.prettify()) '''Out: <html> <body> <a href="http://example.com/"> I linked to <i> example.com </i> </a> </body> </html> '''
參考: Output formatters
若是傳遞給 BeautifulSoup()
的文檔中包含 HTML 實體(entities),那麼在輸出文檔時,這些 HTML 實體將被轉換爲 Unicode 字符:
# in Python3 from bs4 import BeautifulSoup soup = BeautifulSoup("“Dammit!” he said.", 'lxml') print(soup) #> <html><body><p>「Dammit!」 he said.</p></body></html>
若是將文檔編碼爲 bytes
對象,encode()
方法會先將 HTML 文檔內容轉換爲 Unicode 字符串(此時 HTML 實體將被轉換爲 Unicode 字符),而後再將 Unicode 字符串編碼爲 bytes
對象,默認採用 UTF-8 編碼。HTML 實體將以 Unicode 字符的形式編碼。
# in Python3 # 注意觀察HTML實體的變化 from bs4 import BeautifulSoup soup = BeautifulSoup("“Dammit!” he said.", 'lxml') print(soup.encode()) #> b'<html><body><p>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</p></body></html>' print('「'.encode('utf-8')) #> b'\xe2\x80\x9c'
默認狀況下,在輸出的 Unicode 字符串中,爲了保證 BeautifulSoup 不會在無心中生成無效的 HTML 或 XML,獨立的 &
(ampersand)和尖括號會以 HTML 實體顯示:
# 獨立的&會顯示爲& &會保持原樣 # 獨立的<會顯示爲< <會保持原樣 # 獨立的>會顯示爲> >會保持原樣 # in Python3 from bs4 import BeautifulSoup soup = BeautifulSoup( "<p>The law firm of Dewey, Cheatem, > > < < & & Howe</p>", 'lxml') p = soup.p print(p) #> <p>The law firm of Dewey, Cheatem, > > < < & & Howe</p> soup = BeautifulSoup( '<a href="http://example.com/?foo=val1&bar=val2">A link</a>', 'lxml') print(soup.a) #> <a href="http://example.com/?foo=val1&bar=val2">A link</a>
若是須要改變 HTML 實體的呈現方式,便須要向 prettify()
, encode()
, decode()
傳遞 formatter
參數。formatter
的實參值有 6 種狀況,默認爲 formatter="minimal"
。另外,__str__()
, __unicode__()
, __repr__()
在輸出時只能採用默認行爲,不可修改。
當 formatter="minimal"
時,會按照前面敘述的規則來處理字符串,以確保生成有效的 HTML/XML:
# in Python3 from bs4 import BeautifulSoup french = "<p>Il a dit <<Sacré bleu!>></p>" soup = BeautifulSoup(french, 'lxml') print(soup.prettify(formatter="minimal")) '''Out: <html> <body> <p> Il a dit <<Sacré bleu!>> </p> </body> </html>'''
當 formatter="html"
時,BeautifulSoup 會盡量的將 Unicode 字符傳喚爲 HTML 實體:
# in Python3 from bs4 import BeautifulSoup french = "<p>Il a dit <<Sacré bleu!>> é</p>" soup = BeautifulSoup(french, 'lxml') print(soup.prettify(formatter="html")) '''Out: <html> <body> <p> Il a dit <<Sacré bleu!>> é </p> </body> </html>''' # If you pass in ``formatter="html5"``, it's the same as
當 formatter="html5"
時,BeautifulSoup 會省略 HTML 空 tag 的結束斜槓,例如:
# in Python3 from bs4 import BeautifulSoup soup = BeautifulSoup("<br>", 'lxml') print(soup.encode(formatter="html")) # <html><body><br/></body></html> print(soup.encode(formatter="html5")) # <html><body><br></body></html>
當 formatter=None
時,BeautifulSoup 將不會在輸出中修改字符串。此時的輸出速度最快,但可能會致使 BeautifulSoup 生成無效的 HTML/XML,例如:
# in Python3 from bs4 import BeautifulSoup french = "<p>Il a dit <<Sacré bleu!>></p>" soup = BeautifulSoup(french, 'lxml') print(soup.prettify(formatter=None)) '''Out: <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)) # <a href="http://example.com/?foo=val1&bar=val2">A link</a>
還能夠向 formatter
傳遞一個函數,BeautifulSoup 會爲文檔中的每一個"字符串"和"屬性值"調用一次該函數。你能夠在這個函數中作任何你想作的事情。下面這個 formatter 函數會將字符串和屬性值轉換爲大寫,並不會執行其它操做:
# in Python3 from bs4 import BeautifulSoup def uppercase(str): return str.upper() french = "<p>Il a dit <<Sacré bleu!>></p>" soup = BeautifulSoup(french, 'lxml') print(soup.prettify(formatter=uppercase)) '''Out: <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>', 'lxml') print(link_soup.a.prettify(formatter=uppercase)) '''Out: <a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2"> A LINK </a> '''
若是你正在編寫 formatter 函數,你應該先了解一下 bs4.dammit
模塊中的 EntitySubstitution
類——該類將 BeautifulSoup 中的標準 formatter 實現爲類方法:
EntitySubstitution.substitute_html
EntitySubstitution.substitute_xml
你可使用上述函數來模擬 formatter=html
或 formatter==minimal
,並添加一些你須要的擴展功能。
下面這個示例會盡量的將 Unicode 字符傳喚爲 HTML 實體,並將全部字符串轉換爲大寫:
from bs4 import BeautifulSoup from bs4.dammit import EntitySubstitution def uppercase_and_substitute_html_entities(str): return EntitySubstitution.substitute_html(str.upper()) french = "<p>Il a dit <<Sacré bleu!>> é</p>" soup = BeautifulSoup(french, 'lxml') print(soup.prettify(formatter=uppercase_and_substitute_html_entities)) '''Out: <html> <body> <p> IL A DIT <<SACRÉ BLEU!>> É </p> </body> </html> '''
若是建立建立了一個 CData
對象,則該對象內的文本將始終與其顯示徹底一致,並不會進行格式化操做。
Beautiful Soup will call the formatter method, just in case you’ve written a custom method that counts all the strings in the document or something, but it will ignore the return value:
from bs4.element import CData soup = BeautifulSoup("<a></a>") soup.a.string = CData("one < three") print(soup.a.prettify(formatter="xml")) # ?"xml"是什麼意思? # <a> # <![CDATA[one < three]]> # </a>
若是隻想獲得結果字符串,而且不在乎輸出格式,則能夠在 BeautifulSoup
對象和 Tag
對象上調用如下方法:
__unicode__()
- 對應內置函數 unicode()
,適用於 Py2__str__()
- 對應內置函數 str()
,因爲 Py2 中的 str
對象不是 Unicode 字符串,因此 str()
在 Py2 和 Py3 中的輸出並不相同__repr__()
- 對應於內置函數 repr()
,因爲 Py2 中的 str
對象不是 Unicode 字符串,因此 repr()
在 Py2 和 Py3 中的輸出並不相同這三個方法的源代碼以下:
def __repr__(self, encoding="unicode-escape"): """Renders this tag as a string.""" if PY3K: # "The return value must be a string object", i.e. Unicode return self.decode() else: # "The return value must be a string object", i.e. a bytestring. # By convention, the return value of __repr__ should also be # an ASCII string. return self.encode(encoding) def __unicode__(self): return self.decode() def __str__(self): if PY3K: return self.decode() else: return self.encode() if PY3K: __str__ = __repr__ = __unicode__
對 Py3 而言,上述三個方法徹底等效,均返回 str
對象(Unicode 字符串):
# in Python3 markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup, 'lxml') print(soup) # 調用__str__方法 #> <html><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>
對 Py2 而言,str()
將返回以 UTF-8 編碼的 str
對象(若是須要了解與編碼相關的內容,能夠參考 Encodings )
# in Python2 >>> markup = u'<a href="http://example.com/">I linked to 示例<i>example.com</i></a>' >>> soup = BeautifulSoup(markup, 'lxml') >>> str(soup) '<html><body><a href="http://example.com/">I linked to \xe7\xa4\xba\xe4\xbe\x8b<i>example.com</i></a></body></html>'
對 Py2 而言,repr()
將返回以 unicode-escape 編碼(詳見 Text Encodings)的 str
對象:
# in Python2 >>> markup = u'<a href="http://example.com/">I linked to 示例<i>example.com</i></a>' >>> soup = BeautifulSoup(markup, 'lxml') >>> repr(soup) # 以ASCII編碼,並將Unicode字面值表示爲quote形式 '<html><body><a href="http://example.com/">I linked to \\u793a\\u4f8b<i>example.com</i></a></body></html>'
🔨get_text(self, separator="", strip=False, types=(NavigableString, CData))
若是隻須要獲取文檔或 tag 的文本部分,則可使用 get_text()
方法,源代碼以下:
def get_text(self, separator="", strip=False, types=(NavigableString, CData)): """ Get all child strings, concatenated using the given separator. """ return separator.join([s for s in self._all_strings( strip, types=types)])
該方法會將文檔或 tag 中的全部文本合併爲一個 Unicode 字符串,並返回該字符串:
from bs4 import BeautifulSoup markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>' soup = BeautifulSoup(markup, 'lxml') print(soup.get_text()) print(soup.i.get_text())
輸出:
I linked to example.com example.com
separator 參數用於設置分隔符:
from bs4 import BeautifulSoup markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>' soup = BeautifulSoup(markup, 'lxml') print(repr(soup.get_text('|'))) #> '\nI linked to |example.com|\n'
strip 參數用於設置是否剝離每段文本開頭和結尾處的空白符(whitespace):
from bs4 import BeautifulSoup markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>' soup = BeautifulSoup(markup, 'lxml') print(repr(soup.get_text('|', strip=True))) #> 'I linked to|example.com'
若是須要本身處理文本,則可使用 .stripped_strings 生成器,它會爲咱們逐一提取每段文本:
from bs4 import BeautifulSoup markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>' soup = BeautifulSoup(markup, 'lxml') print([text for text in soup.stripped_strings]) #> ['I linked to', 'example.com']
text
字段的源代碼以下:
text = property(get_text)