源:Python之禪 作者:劉志軍
題圖:@Miguel Mateo
系列文章的第3篇介紹了網(wǎng)絡請求庫神器 Requests ,請求把數(shù)據(jù)返回來之后就要提取目標數(shù)據(jù),不同的網(wǎng)站返回的內(nèi)容通常有多種不同的格式,一種是 json 格式,這類數(shù)據(jù)對開發(fā)者來說最友好。另一種 XML 格式的,還有一種最常見格式的是 HTML 文檔,今天就來講講如何從 HTML 中提取出感興趣的數(shù)據(jù)
直接字符串處理?自己寫個 HTML 解析器來解析嗎?還是用正則表達式?這些都不是最好的辦法,好在,Python 社區(qū)在這方面早就有了很成熟的方案,BeautifulSoup 就是這一類問題的克星,它專注于 HTML 文檔操作。
BeautifulSoup 是一個用于解析 HTML 文檔的 Python 庫,通過 BeautifulSoup,你只需要用很少的代碼就可以提取出 HTML 中任何感興趣的內(nèi)容,此外,它還有一定的 HTML 容錯能力,對于一個格式不完整的HTML 文檔,它也可以正確處理。
安裝 BeautifulSoup
pip install beautifulsoup4
BeautifulSoup3 被官方放棄維護,你要下載最新的版本 BeautifulSoup4。
HTML 標簽
學習 BeautifulSoup4 前有必要先對 HTML 文檔有一個基本認識,如下代碼,HTML 是一個樹形組織結(jié)構(gòu)。
<html><head><title>hello, world</title></head><body><h1>BeautifulSoup</h1><p>如何使用BeautifulSoup</p><body></html>
它由很多標簽(Tag)組成,比如 html、head、title等等都是標簽
一個標簽對構(gòu)成一個節(jié)點,比如 <html>...</html>是一個根節(jié)點
節(jié)點之間存在某種關系,比如 h1 和 p 互為鄰居,他們是相鄰的兄弟(sibling)節(jié)點
h1 是 body 的直接子(children)節(jié)點,還是 html 的子孫(descendants)節(jié)點
body 是 p 的父(parent)節(jié)點,html 是 p 的祖輩(parents)節(jié)點
嵌套在標簽之間的字符串是該節(jié)點下的一個特殊子節(jié)點,比如 “hello, world” 也是一個節(jié)點,只不過沒名字。
使用 BeautifulSoup
構(gòu)建一個 BeautifulSoup 對象需要兩個參數(shù),第一個參數(shù)是將要解析的 HTML 文本字符串,第二個參數(shù)告訴 BeautifulSoup 使用哪個解析器來解析 HTML。
解析器負責把 HTML 解析成相關的對象,而 BeautifulSoup 負責操作數(shù)據(jù)(增刪改查)?!癶tml.parser” 是 Python 內(nèi)置的解析器,“l(fā)xml” 則是一個基于c語言開發(fā)的解析器,它的執(zhí)行速度更快,不過它需要額外安裝
通過 BeautifulSoup 對象可以定位到 HTML 中的任何一個標簽節(jié)點。
from bs4 import BeautifulSouptext = """<html><head><title >hello, world</title></head><body><h1>BeautifulSoup</h1><p class="bold">如何使用BeautifulSoup</p><p class="big" id="key1"> 第二個p標簽</p><a soup = BeautifulSoup(text, "html.parser")# title 標簽>>> soup.title<title>hello, world</title># p 標簽>>> soup.p<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p># p 標簽的內(nèi)容>>> soup.p.stringu'\u5982\u4f55\u4f7f\u7528BeautifulSoup'
BeatifulSoup 將 HTML 抽象成為 4 類主要的數(shù)據(jù)類型,分別是Tag , NavigableString , BeautifulSoup,Comment 。每個標簽節(jié)點就是一個Tag對象,NavigableString 對象一般是包裹在Tag對象中的字符串,BeautifulSoup 對象代表整個 HTML 文檔。例如:
>>> type(soup)<class 'bs4.BeautifulSoup'>>>> type(soup.h1)<class 'bs4.element.Tag'>>>> type(soup.p.string)<class 'bs4.element.NavigableString'>
Tag
每個 Tag 都有一個名字,它對應 HTML 的標簽名稱。
>>> soup.h1.nameu'h1'>>> soup.p.nameu'p'
標簽還可以有屬性,屬性的訪問方式和字典是類似的,它返回一個列表對象
>>> soup.p['class'][u'bold']
NavigableString
獲取標簽中的內(nèi)容,直接使用 .stirng 即可獲取,它是一個 NavigableString 對象,你可以顯式地將它轉(zhuǎn)換為 unicode 字符串。
>>> soup.p.stringu'\u5982\u4f55\u4f7f\u7528BeautifulSoup'>>> type(soup.p.string)<class 'bs4.element.NavigableString'>>>> unicode_str = unicode(soup.p.string)>>> unicode_stru'\u5982\u4f55\u4f7f\u7528BeautifulSoup'
基本概念介紹完,現(xiàn)在可以正式進入主題了,如何從 HTML 中找到我們關心的數(shù)據(jù)?BeautifulSoup 提供了兩種方式,一種是遍歷,另一種是搜索,通常兩者結(jié)合來完成查找任務。
遍歷文檔樹
遍歷文檔樹,顧名思義,就是是從根節(jié)點 html 標簽開始遍歷,直到找到目標元素為止,遍歷的一個缺陷是,如果你要找的內(nèi)容在文檔的末尾,那么它要遍歷整個文檔才能找到它,速度上就慢了。因此還需要配合第二種方法。
通過遍歷文檔樹的方式獲取標簽節(jié)點可以直接通過 .標簽名
的方式獲取,例如:
獲取 body 標簽:
>>> soup.body<body>\n<h1>BeautifulSoup</h1>\n<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>\n</body>
獲取 p 標簽
>>> soup.body.p<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>
獲取 p 標簽的內(nèi)容
>>> soup.body.p.string\u5982\u4f55\u4f7f\u7528BeautifulSoup
前面說了,內(nèi)容也是一個節(jié)點,這里就可以用 .string的方式得到。
遍歷文檔樹的另一個缺點是只能獲取到與之匹配的第一個子節(jié)點,例如,如果有兩個相鄰的 p 標簽時,第二個標簽就沒法通過 .p的方式獲取,這是需要借用 next_sibling 屬性獲取相鄰的節(jié)點。
此外,還有很多不怎么常用的屬性,比如:.contents 獲取所有子節(jié)點,.parent 獲取父節(jié)點,更多的參考請查看官方文檔。
搜索文檔樹
搜索文檔樹是通過指定標簽名來搜索元素,還可以通過指定標簽的屬性值來精確定位某個節(jié)點元素,最常用的兩個方法就是 find 和 find_all。這兩個方法在 BeatifulSoup 和 Tag 對象上都可以被調(diào)用。
find_all()
find_all( name , attrs , recursive , text , **kwargs )
find_all 的返回值是一個 Tag 組成的列表,方法調(diào)用非常靈活,所有的參數(shù)都是可選的。
第一個參數(shù) name 是標簽節(jié)點的名字。
# 找到所有標簽名為title的節(jié)點>>> soup.find_all("title")
[<title>hello, world</title>]
>>> soup.find_all("p")
[<p class="bold">\xc8\xe7\xba\xce\xca....</p>,
<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p...</p>]
第二個參數(shù)是標簽的class屬性值
# 找到所有class屬性為big的p標簽>>> soup.find_all("p", "big")
[<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]
等效于
>>> soup.find_all("p", class_="big")
[<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]
因為 class 是 Python 關鍵字,所以這里指定為 class_。
kwargs 是標簽的屬性名值對,例如:查找有href屬性值為 “http://foofish.net” 的標簽
>>> soup.find_all()
[<a >python</a>]
當然,它還支持正則表達式
>>> import re>>> soup.find_all(href=re.compile("^http"))
[<a >python</a>]
屬性除了可以是具體的值、正則表達式之外,它還可以是一個布爾值(True/Flase),表示有屬性或者沒有該屬性。
>>> soup.find_all(id="key1")
[<p class="big" id="key1"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]
>>> soup.find_all(id=True)
[<p class="big" id="key1"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]
遍歷和搜索相結(jié)合查找,先定位到 body 標簽,縮小搜索范圍,再從 body 中找 a 標簽。
>>> body_tag = soup.body>>> body_tag.find_all("a")
[<a >python</a>]
find()
find 方法跟 find_all 類似,唯一不同的地方是,它返回的單個 Tag 對象而非列表,如果沒找到匹配的節(jié)點則返回 None。如果匹配多個 Tag,只返回第0個。
>>> body_tag.find("a")
<a >python</a>
>>> body_tag.find("p")
<p class="bold">\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup</p>
get_text()
獲取標簽里面內(nèi)容,除了可以使用 .string 之外,還可以使用 get_text 方法,不同的地方在于前者返回的一個 NavigableString 對象,后者返回的是 unicode 類型的字符串。
>>> p1 = body_tag.find('p').get_text()
實際場景中我們一般使用 get_text 方法獲取標簽中的內(nèi)容。
總結(jié)
BeatifulSoup 是一個用于操作 HTML 文檔的 Python 庫,初始化 BeatifulSoup 時,需要指定 HTML 文檔字符串和具體的解析器。它有3類常用的數(shù)據(jù)類型,分別是 Tag、NavigableString、和 BeautifulSoup。查找 HTML元素有兩種方式,分別是遍歷文檔樹和搜索文檔樹,通常快速獲取數(shù)據(jù)需要二者結(jié)合。
級鏈接標簽
目的頁面的地區(qū)用 name屬性定義(name的值可自己定義,一般為英文),鏈接頁面的鏈接應寫為<a href=”url#name”></a>
未被訪問的鏈接帶有下劃線而且是藍色的
已被訪問的鏈接帶有下劃線而且是紫色的
活動鏈接帶有下劃線而且是紅色的
關于錨的進一步說明:
<a> 標簽可定義錨,錨 (anchor) 有兩種用法:
通過使用 href 屬性,創(chuàng)建指向另外一個文檔的鏈接(或超鏈接)
通過使用 name 或 id 屬性,創(chuàng)建一個文檔內(nèi)部的書簽(也就是說,可以創(chuàng)建指向文檔片段的鏈接)
圖片標簽 <img>
1、img標簽是一個單標簽,必須和src(source:指出圖像的路徑)屬性連用,
網(wǎng)頁上圖像的路徑有絕對路徑和相對路徑兩種,但在實際運用中往往只用相對路徑
絕對路徑:圖片在硬盤上的路徑 (分割符號是/)
相對路徑:圖片相對于網(wǎng)頁而言的路徑
如果圖片與網(wǎng)頁在同一個文件夾下,那么可以直接用圖片的名稱表示其路徑
如果圖片在網(wǎng)頁的下一層文件夾中,比如圖片在網(wǎng)頁文件下面的images文件夾中,則可用images/表示
我們也可以用./表示網(wǎng)頁所在的文件夾
如果圖片在網(wǎng)頁的上一層文件夾中,則用../表示
事實上,不但圖片文件是如此,網(wǎng)頁中應用的文件都分為相對路徑和絕對路徑兩種表達方式。
2、在網(wǎng)頁上,支持的圖片格式包括.gif .jpg .png .bmp,一般用前三者居多,因為前三者的圖片壓縮比較好。但是,gif格式只有256種顏色,所以,在需要豐富顏色的場合,往往多用jpg和png格式。不過,gif擁有動態(tài)功能,而后兩者則不具備。
3、img標簽可以與其它標簽共處一行,如果有多個圖形出現(xiàn)時,默認為同一行顯示
4、img標簽有4個常用標簽,分別是
alt 圖片說明,在圖像無法顯示時表現(xiàn)為圖像的替代文本
width 寬 屬性值可以是象素,也可以是%
height 高 屬性值可以是象素,也可以是%
border 邊框
5、圖片鏈接仍然是用a標簽來顯示
例子:<a href=“http://www.rwxy.xnc.edu.cn”><img src="sample.jpg" ></a>
6、可以用圖像映射實現(xiàn)圖像不同區(qū)域的鏈接
圖象映射
所謂圖象映射是指一個圖片上的不同位置被指定了不同的超級鏈接;點擊圖片的不同位置會打開不同的超級鏈接目標。這與前面的默認超級鏈把整個圖片作為超級鏈接的元素是很不一樣的。
圖象映射由<map>定義。<map>有一個基本屬性是name。name給圖象映射命名,這個命名將會被<img>元素用usemap屬性引用。所以,圖象上的圖象映射實際上是對<map>定義的映射的一個引用。
<map>在定義圖象映射時,可以定義三種形狀的映射: circle(圓形)、rect(矩形rectangle)、poly(多邊形)
圖象映射實例
<img src="bear.jpg" usemap="#map" >
<map name=“map">
<area shape="rect" coords="46,29,253,164" href="#" >
<area shape="circle" coords="76,510,59" href="#" >
<area shape="poly" coords="219,482,253,448,310,462,297,527,220,523" href="#" >
</map>
表格標簽
表格由三個標簽構(gòu)成,分別是
<table>...</table> - 定義表格
<tr> - 定義表行
<th> - 定義表頭
<td> - 定義表格單元(表格的具體數(shù)據(jù))
在表格標簽中,table、tr、td標簽都具備不同的屬性
table:border(邊框尺寸設置);width(表格的寬);height(表格的高);align(橫向?qū)R:left center right);bgcolor(背景色彩);background(背景圖像);cellspacing(表格單元的間隙設置);cellpadding(表元內(nèi)部空白設置);
tr:height(行高);align(橫向?qū)R:left center right);valign(縱向?qū)R:top middle bottom);bgcolor(背景色彩);
td:width(表格的寬);height(表格的高);align(橫向?qū)R:left center right);valign(縱向?qū)R:top middle bottom);bgcolor(背景色彩);background(背景圖像);
注:當talble、tr、td有共同的屬性而且屬性值發(fā)生沖突之際,其優(yōu)先性是td > tr > table
表格在html中最大的作用不是用來整理數(shù)據(jù),而是用來排版,所以它是html中用處最廣的標簽之一。
表格內(nèi)部可以繼續(xù)放入表格,這被稱之為表格嵌套,利用表格嵌套可以制作出非常復雜的排版。
表格的單元格可以跨行跨列顯示
跨多列的單元格 <td colspan=#>
<table border=1>
<tr><td colspan=3> morning menu</td>
<tr><td>food</td> <td>drink</td> <td>sweet</td>
<tr><td>a</td><td>b</td><td>c</td>
</table>
morning menu
food
drink
sweet
a
b
c
跨多行的單元格 <td rowspan=#>
<table border=1>
<tr><td rowspan=3> morning menu</td>
<td>food</td> <td>a</td></tr>
<tr><td>drink</td> <td>b</td></tr>
<tr><td>sweet</td> <td>c</td></tr>
</table>
morning menu
food
a
drink
b
sweet
c
....................................................................
我的微信公眾號:UI嚴選 —越努力,越幸運
ext()方法會把包含的字符轉(zhuǎn)義處理,html()則不會.所謂轉(zhuǎn)義就是字符的另一種顯示方法,例如"<" 顯示成 "<",這就是轉(zhuǎn)義了,其中的<就是<的轉(zhuǎn)義字符.還有很多可以轉(zhuǎn)義的字符,可以搜索下看看.
下邊是項目中用到的2個語句:span中包含了jquery的語句輸出結(jié)果.
1. $('.title').text("<img src=" ">");
顯示結(jié)果為<img src=" ">,這里的<img src=" ">不會被解析成html的img標簽,而是以存字母文字的形式顯示,也就是單純的字符串:<img src=" "> .并且f12查看源碼時看到span包含的<img src=" ">文字內(nèi)容外層有雙引號哦.看下圖,
?
如果你用右鍵選擇編輯為html,則看到其中的轉(zhuǎn)義字符<這就說明我們的<被轉(zhuǎn)移了,
?
如果我們想把<img>顯示成標簽,被瀏覽器解析.那么就需要如下方法.
2. $('.title').html("<img >");
顯示結(jié)果為解析后的html代碼段,那么這里的<img >就會按照h5的標簽img圖片進行解析顯示了.下圖是f12的頁面代碼結(jié)果span中的<img>標簽外層無雙引號,且頁面此時會顯示出來圖片.
?
?
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。