XPath
xpath(XML Path Language)是一門在XML和HTML文檔中查找信息的語言,可用來在XML和HTML文檔中對元素和屬性進行遍歷。
XPath開發工具:
-
- Chrome插件XPath Helper。
- Firefox插件Try XPath。
XPath語法:
選取節點:
XPath 使用路徑表達式來選取 XML 文檔中的節點或者節點集。這些路徑表達式和我們在常規的電腦文件系統中看到的表達式非常相似。
表達式 | 描述 | 示例 | 結果 |
---|---|---|---|
nodename | 選取此節點的所有子節點 | bookstore | 選取bookstore下所有的子節點 |
/ | 如果是在最前面,代表從根節點選取。否則選擇某節點下的某個節點 | /bookstore | 選取根元素下所有的bookstore節點 |
// | 從全局節點中選擇節點,隨便在哪個位置 | //book | 從全局節點中找到所有的book節點 |
@ | 選取某個節點的屬性 | //book[@price] | 選擇所有擁有price屬性的book節點 |
. | 當前節點 | ./a | 選取當前節點下的a標簽 |
謂語:
謂語用來查找某個特定的節點或者包含某個指定的值的節點,被嵌在方括號中。
在下面的表格中,我們列出了帶有謂語的一些路徑表達式,以及表達式的結果:(下標從1開始)
路徑表達式 | 描述 |
---|---|
/bookstore/book[1] | 選取bookstore下的第一個子元素 |
/bookstore/book[last()] | 選取bookstore下的最后一個book元素。 |
bookstore/book[position()<3] | 選取bookstore下前面兩個子元素。 |
//book[@price] | 選取擁有price屬性的book元素 |
//book[@price=10] | 選取所有屬性price等於10的book元素 |
通配符:
*表示通配符。
通配符 | 描述 | 示例 | 結果 |
---|---|---|---|
* | 匹配任意節點 | /bookstore/* | 選取bookstore下的所有子元素。 |
@* | 匹配節點中的任何屬性 | //book[@*] | 選取所有帶有屬性的book元素。 |
選取多個路徑:
通過在路徑表達式中使用“ | ”運算符,可以選取若干個路徑。

//bookstore/book | //book/title # 選取所有book元素以及book元素下所有的title元素
運算符:
注意:
/ 和 // 的區別: / 代表只獲取直接子節點; //獲取子孫節點。 一般// 用的比較多
contains: 有時候某個屬性中包含了多個值,那么可以用“contains”函數
謂詞中的下標是從1開始的
lxml庫
(使用xpath語法來解析xml代碼)
lxml 是 一個HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 數據。
lxml和正則一樣,也是用 C 實現的,是一款高性能的 Python HTML/XML 解析器,我們可以利用之前學習的XPath語法,來快速的定位特定元素以及節點信息。
安裝:
lxml python 官方文檔:http://lxml.de/index.html
需要安裝C語言庫,可使用 pip 安裝:pip install lxml
基本使用:
我們可以利用他來解析HTML代碼,並且在解析HTML代碼的時候,如果HTML代碼不規范,他會自動的進行補全。

# 使用 lxml 的 etree 庫 from lxml import etree text = ''' <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a> # 注意,此處缺少一個 </li> 閉合標簽 </ul> </div> ''' #利用etree.HTML,將字符串解析為HTML文檔 html = etree.HTML(text) # 按字符串序列化HTML文檔 result = etree.tostring(html) print(result) # 輸入結果如下: <html><body> <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a></li> </ul> </div> </body></html> # 可以看到。lxml會自動修改HTML代碼。例子中不僅補全了li標簽,還添加了body,html標簽。
解析字符串使用 “lxml.etree.HTML”進行解析
注意:
解析出來是一個element對象,后續可以去執行xpath語法
etree.HTML類會去補充標簽
從文件中讀取html代碼:
除了直接使用字符串進行解析,lxml還支持從文件中讀取內容。

# 新建一個hello.html 文件 <!-- hello.html --> <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a></li> </ul> </div> 然后利用etree.parse()方法來讀取文件。示例代碼如下: from lxml import etree # 讀取外部文件 hello.html html = etree.parse('hello.html') result = etree.tostring(html, pretty_print=True) print(result) # 輸入結果和之前是相同的。
解析文件使用 “lxml.etree.parser”進行解析。默認的是XML解析器
注意:
etree.parse函數只會去解析,它並不會去補充標簽,所以不能去處理那些有問題的標簽
可使用etree.HTMLParser解析器去解析HTML代碼,該解析器為etree.parse函數的第二個參數
在lxml中使用XPath語法:

獲取所有li標簽: from lxml import etree html = etree.parse('hello.html') print type(html) # 顯示etree.parse() 返回類型 result = html.xpath('//li') print(result) # 打印<li>標簽的元素集合 獲取所有li元素下的所有class屬性的值: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/@class') print(result) 獲取li標簽下href為www.baidu.com的a標簽: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a[@href="www.baidu.com"]') print(result) 獲取li標簽下所有span標簽: from lxml import etree html = etree.parse('hello.html') #result = html.xpath('//li/span') #注意這么寫是不對的: #因為 / 是用來獲取子元素的,而 <span> 並不是 <li> 的子元素,所以,要用雙斜杠 result = html.xpath('//li//span') print(result) 獲取li標簽下的a標簽里的所有class: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a//@class') print(result) 獲取最后一個li的a的href屬性對應的值: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()]/a/@href') # 謂語 [last()] 可以找到最后一個元素 print(result) 獲取倒數第二個li元素的內容: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()-1]/a') # text 方法可以獲取元素內容 print(result[0].text) 獲取倒數第二個li元素的內容的第二種方式: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()-1]/a/text()') print(result)
注意:
使用“xpath”語法,應該使用“element.xpath”方法,來執行xpath的選擇
xpath函數返回的永遠是列表
針對某一個標簽下面再進行xpath時獲取子孫元素不能用 "//" ,他會在整個網頁當中去尋找, 此時用" .// "
獲取文本是通過“xpath”中的“text()”函數
BeautifulSoup4庫
和 lxml 一樣,Beautiful Soup 也是一個HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 數據。
lxml 只會局部遍歷,而Beautiful Soup 是基於HTML DOM(Document Object Model)的,會載入整個文檔,解析整個DOM樹,因此時間和內存開銷都會大很多,所以性能要低於lxml。
BeautifulSoup 用來解析 HTML 比較簡單,API非常人性化,支持CSS選擇器、Python標准庫中的HTML解析器,也支持 lxml 的 XML解析器。
安裝:
- 安裝:
pip install bs4
- 中文文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
簡單使用:

from bs4 import BeautifulSoup html = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><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> """ #創建 Beautiful Soup 對象 # 使用lxml來進行解析 soup = BeautifulSoup(html,"lxml") print(soup.prettify())
四個常用的對象:
Beautiful Soup將復雜HTML文檔轉換成一個復雜的樹形結構,每個節點都是Python對象,所有對象可以歸納為4種:
- Tag
- NavigatableString
- BeautifulSoup
- Commen
Tag:
Tag 通俗點講就是 HTML 中的一個個標簽。

from bs4 import BeautifulSoup html = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><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> """ #創建 Beautiful Soup 對象 soup = BeautifulSoup(html,'lxml') print soup.title # <title>The Dormouse's story</title> print soup.head # <head><title>The Dormouse's story</title></head> print soup.a # <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> print soup.p # <p class="title" name="dromouse"><b>The Dormouse's story</b></p> print type(soup.p) # <class 'bs4.element.Tag'>
我們可以利用 soup 加標簽名輕松地獲取這些標簽的內容,這些對象的類型是bs4.element.Tag。
但是注意,它查找的是在所有內容中的第一個符合要求的標簽。
對於Tag,它有兩個重要的屬性,分別是name和attrs。

print soup.name # [document] #soup 對象本身比較特殊,它的 name 即為 [document] print soup.head.name # head #對於其他內部標簽,輸出的值便為標簽本身的名稱 print soup.p.attrs # {'class': ['title'], 'name': 'dromouse'} # 在這里,我們把 p 標簽的所有屬性打印輸出了出來,得到的類型是一個字典。 print soup.p['class'] # soup.p.get('class') # ['title'] #還可以利用get方法,傳入屬性的名稱,二者是等價的 soup.p['class'] = "newClass" print soup.p # 可以對這些屬性和內容等等進行修改 # <p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>
NavigableString:
如果拿到標簽后,還想獲取標簽中的內容。那么可以通過tag.string
獲取標簽中的文字。

print soup.p.string # The Dormouse's story print type(soup.p.string) # <class 'bs4.element.NavigableString'>thon
BeautifulSoup:
BeautifulSoup 對象表示的是一個文檔的全部內容.大部分時候,可以把它當作 Tag 對象,它支持 遍歷文檔樹 和 搜索文檔樹 中描述的大部分的方法。
因為 BeautifulSoup 對象並不是真正的HTML或XML的tag,所以它沒有name和attribute屬性。
但有時查看它的 .name 屬性是很方便的,所以 BeautifulSoup 對象包含了一個值為 “[document]” 的特殊屬性 .name。
Comment:
Tag , NavigableString , BeautifulSoup 幾乎覆蓋了html和xml中的所有內容,但是還有一些特殊對象.容易讓人擔心的內容是文檔的注釋部分:
Comment 對象是一個特殊類型的 NavigableString 對象

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup) comment = soup.b.string type(comment) # <class 'bs4.element.Comment'>
遍歷文檔樹:
1. contents和children:

html_doc = """ <html><head><title>The Dormouse's story</title></head> <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> """ from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc,'lxml') head_tag = soup.head # 返回所有子節點的列表 print(head_tag.contents) # 返回所有子節點的迭代器 for child in head_tag.children: print(child)
2. strings 和 stripped_strings:
如果tag中包含多個字符串 [2] ,可以使用 .strings 來循環獲取:

for string in soup.strings: print(repr(string)) # u"The Dormouse's story" # u'\n\n' # u"The Dormouse's story" # u'\n\n' # u'Once upon a time there were three little sisters; and their names were\n' # u'Elsie' # u',\n' # u'Lacie' # u' and\n' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'\n\n' # u'...' # u'\n'
輸出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白內容:

for string in soup.stripped_strings: print(repr(string)) # u"The Dormouse's story" # u"The Dormouse's story" # u'Once upon a time there were three little sisters; and their names were' # u'Elsie' # u',' # u'Lacie' # u'and' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'...'
搜索文檔樹:
1. find和find_all方法:
find
方法是找到第一個滿足條件的標簽后就立即返回,只返回一個元素。
find_all
方法是把所有滿足條件的標簽都選到,然后返回回去。
使用這兩個方法,最常用的用法是出入name
以及attr
參數找出符合要求的標簽。

soup.find_all("a",attrs={"id":"link2"}) 或者是直接傳入屬性的的名字作為關鍵字參數: soup.find_all("a",id='link2')
2. select方法:
使用以上方法可以方便的找出元素。但有時候使用css
選擇器的方式可以更加的方便。使用css
選擇器的語法,應該使用select
方法。以下列出幾種常用的css
選擇器方法:
(1)通過標簽名查找:
print(soup.select('a'))
(2)通過類名查找:
通過類名,則應該在類的前面加一個.
。
print(soup.select('.sister'))
(3)通過id查找:
通過id查找,應該在id的名字前面加一個#號。
print(soup.select("#link1"))
(4)組合查找:
組合查找即和寫 class 文件時,標簽名與類名、id名進行的組合原理是一樣的。

print(soup.select("p #link1"))
(5)通過屬性查找:
查找時還可以加入屬性元素,屬性需要用中括號括起來,注意屬性和標簽屬於同一節點,所以中間不能加空格,否則會無法匹配到。
print(soup.select('a[href="http://example.com/elsie"]'))
(6)獲取內容:
以上的 select 方法返回的結果都是列表形式,可以遍歷形式輸出,然后用 get_text() 方法來獲取它的內容。

soup = BeautifulSoup(html, 'lxml') print type(soup.select('title')) print soup.select('title')[0].get_text() for title in soup.select('title'): print title.get_text()
正則表達式和re模塊:
正則表達式:
通俗理解:按照一定的規則,從某個字符串中匹配出想要的數據。這個規則就是正則表達式。
常用匹配規則:
匹配某個字符串:

text = 'hello' ret = re.match('he',text) print(ret.group()) >> he
點(.)匹配任意的字符:(點(.)不能匹配不到換行符)

text = "ab" ret = re.match('.',text) print(ret.group()) >> a 但是點(.)不能匹配不到換行符。示例代碼如下: text = "ab" ret = re.match('.',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 'group'
\d匹配任意的數字:

text = "123" ret = re.match('\d',text) print(ret.group()) >> 1
\D匹配任意的非數字:(text是等於一個數字,那么就匹配不成功了)

text = "a" ret = re.match('\D',text) print(ret.group()) >> a 而如果text是等於一個數字,那么就匹配不成功了。示例代碼如下: text = "1" ret = re.match('\D',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 'group'
\s匹配的是空白字符(包括:\n,\t,\r和空格):

text = "\t" ret = re.match('\s',text) print(ret.group()) >> 空白
\w匹配的是a-z
和A-Z
以及數字和下划線:(如果要匹配一個其他的字符,那么就匹配不到)

text = "_" ret = re.match('\w',text) print(ret.group()) >> _ 而如果要匹配一個其他的字符,那么就匹配不到。示例代碼如下: text = "+" ret = re.match('\w',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute
\W匹配的是和\w相反的:

text = "+" ret = re.match('\W',text) print(ret.group()) >> + 而如果你的text是一個下划線或者英文字符,那么就匹配不到了。示例代碼如下: text = "_" ret = re.match('\W',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute
[]組合的方式,只要滿足中括號中的某一項都算匹配成功:

text = "0731-88888888" ret = re.match('[\d\-]+',text) print(ret.group()) >> 0731-88888888
之前講到的幾種匹配規則,其實可以使用中括號的形式來進行替代:
匹配多個字符:
*
:可以匹配0或者任意多個字符。
+
:可以匹配1個或者多個字符。最少一個。
?
:匹配的字符可以出現一次或者不出現(0或者1)。
{m}
:匹配m個字符。
{m,n}
:匹配m-n個字符。在這中間的字符都可以匹配到。
^(脫字號):表示以...開始:

text = "hello" ret = re.match('^h',text) print(ret.group()) 如果是在中括號中,那么代表的是取反操作.
$:表示以...結束:

# 匹配163.com的郵箱 text = "xxx@163.com" ret = re.search('\w+@163\.com$',text) print(ret.group()) >> xxx@163.com
|:匹配多個表達式或者字符串:

text = "hello|world" ret = re.search('hello',text) print(ret.group()) >> hello
貪婪模式和非貪婪模式:
貪婪模式:正則表達式會匹配盡量多的字符。默認是貪婪模式。
非貪婪模式:正則表達式會盡量少的匹配字符。

示例代碼如下: text = "0123456" ret = re.match('\d+',text) print(ret.group()) # 因為默認采用貪婪模式,所以會輸出0123456 >> 0123456 可以改成非貪婪模式,那么就只會匹配到0。示例代碼如下: text = "0123456" ret = re.match('\d+?',text) print(ret.group())
轉義字符和原生字符串:
轉移字符:
在正則表達式中,有些字符是有特殊意義的字符。因此如果想要匹配這些字符,那么就必須使用反斜杠進行轉義。
原生字符串:
在正則表達式中,\
是專門用來做轉義的。在Python中\
也是用來做轉義的。因此如果想要在普通的字符串中匹配出\
,那么要給出四個\
。

text = "apple \c" ret = re.search('\\\\c',text) print(ret.group()) 因此要使用原生字符串就可以解決這個問題: text = "apple \c" ret = re.search(r'\\c',text) print(ret.group())
練習:

驗證手機號碼:手機號碼的規則是以1開頭,第二位可以是34587,后面那9位就可以隨意了。示例代碼如下: text = "18570631587" ret = re.match('1[34587]\d{9}',text) print(ret.group()) >> 18570631587 而如果是個不滿足條件的手機號碼。那么就匹配不到了。示例代碼如下: text = "1857063158" ret = re.match('1[34587]\d{9}',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 驗證郵箱:郵箱的規則是郵箱名稱是用數字、數字、下划線組成的,然后是@符號,后面就是域名了。示例代碼如下: text = "hynever@163.com" ret = re.match('\w+@\w+\.[a-zA-Z\.]+',text) print(ret.group()) 驗證URL:URL的規則是前面是http或者https或者是ftp然后再加上一個冒號,再加上一個斜杠,再后面就是可以出現任意非空白字符了。示例代碼如下: text = "http://www.baidu.com/" ret = re.match('(http|https|ftp)://[^\s]+',text) print(ret.group()) 驗證身份證:身份證的規則是,總共有18位,前面17位都是數字,后面一位可以是數字,也可以是小寫的x,也可以是大寫的X。示例代碼如下: text = "3113111890812323X" ret = re.match('\d{17}[\dxX]',text) print(ret.group()) 匹配0-100之間的數字: text = '99' ret = re.match('[1-9]?\d$|100$',text) print(ret.group()) >> 99 而如果text=101,那么就會拋出一個異常。示例代碼如下: text = '101' ret = re.match('[1-9]?\d$|100$',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 'group'
re模塊中常用函數:
match:
從開始的位置進行匹配。如果開始的位置沒有匹配到。就直接失敗了。

text = 'hello' ret = re.match('h',text) print(ret.group()) >> h 如果第一個字母不是h,那么就會失敗。示例代碼如下: text = 'ahello' ret = re.match('h',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 'group' 如果想要匹配換行的數據,那么就要傳入一個flag=re.DOTALL,就可以匹配換行符了。示例代碼如下: text = "abc\nabc" ret = re.match('abc.*abc',text,re.DOTALL) print(ret.group())
search:
在字符串中找滿足條件的字符。如果找到,就返回。說白了,就是只會找到第一個滿足條件的。

text = 'apple price $99 orange price $88' ret = re.search('\d+',text) print(ret.group()) >> 99
分組:
在正則表達式中,可以對過濾到的字符串進行分組。分組使用圓括號的方式。
1.group
:和group(0)
是等價的,返回的是整個滿足條件的字符串。
2.groups
:返回的是里面的子組。索引從1開始。
3.group(1)
:返回的是第一個子組,可以傳入多個。

text = "apple price is $99,orange price is $10" ret = re.search(r".*(\$\d+).*(\$\d+)",text) print(ret.group()) print(ret.group(0)) print(ret.group(1)) print(ret.group(2)) print(ret.groups())
findall:
找出所有滿足條件的,返回的是一個列表。
sub:
用來替換字符串。將匹配到的字符串替換為其他字符串。
split:
使用正則表達式來分割字符串。

text = "hello world ni hao" ret = re.split('\W',text) print(ret) >> ["hello","world","ni","hao"]
compile:
對於一些經常要用到的正則表達式,可以使用compile
進行編譯,后期再使用的時候可以直接拿過來用,執行效率會更快。
而且compile
還可以指定flag=re.VERBOSE
,在寫正則表達式的時候可以做好注釋。

text = "the number is 20.50" r = re.compile(r""" \d+ # 小數點前面的數字 \.? # 小數點 \d* # 小數點后面的數字 """,re.VERBOSE) ret = re.search(r,text) print(ret.group())