1. 什么是lxml
lxml是干什么的?簡單的說來,lxml是幫助我們解析HTML、XML文件,快速定位,搜索、獲取特定內容的Python庫。我們知道,對於純文本的HTML文件的查找可以使用正則表達式
、BeautifulSoup
等完成。lxml也是對網頁內容解析的一個庫。
那么為什么要用lxml呢?據聽說他比較快。我沒有用來做過大項目,對解析速度理解不是很深刻。我用lxml只是因為它似乎比BeautifulSoup好用。
2. 初次使用
- 安裝
sudo pip3 install lxml
- 初次使用
# 導入lxml
from lxml import etree
# html字符串
html_str = """
<html>
<head>
<title>demo</title>
</head>
<body>
<p>1111111</p>
</body>
</html>
"""
# 利用html_str創建一個節點樹對象
html = etree.HTML(html_str)
type(html) # 輸出結果為:lxml.etree._Element
- 首次解析HTML
不用理會下面代碼中出現的新的方法和各種解析的技巧。先看一下lxml如何快速方便的解析html.
# 我們現在要獲得上面的html文件中的p標簽的內容
p_str = html.xpath('//body/p/text()') # 返回結果為一個列表:['1111111']
上面的例子,給出一個lxml如何解析HTML文件的實例。后文中眾多的知識點,只不過是講解更多的xpath解析方法技巧。
3. xpath
我們一直再講lxml,這里突然出現xpath是干什么的?lxml的主要功能是解析HTML,他是利用什么語法來解析HTML的呢?就是利用xpath,因此,我們需要了解如何使用xpath。
xpath將html文檔看做一個有眾多的節點按照特定級別組織的節點樹,對於其中內容的解析,又三種主要的措施:
- 標簽定位
- 序列定位
- 軸定位
很抱歉,我們又引入了新的概念。但現在我們解釋這些概念是不明智的,還是先看一下如何使用。
3.2 標簽定位
為了說明xpath各種定位語法,我們下面利用如下的HTML來完成講解。
from lxml import etree
html_str = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>The Document's story</title>
</head>
<body>
<div class="table1">
<ul class="one" id="id1">
<tr>tr1</tr>
<tr>tr2</tr>
<tr>tr3</tr>
<tr>tr4</tr>
</ul>
<ol class="two" id="id2">
<td>td1</td>
<td>td2</td>
<td>td3</td>
<td>td4</td>
</ol>
</div>
<div class="table2">
<a href="www.table2_1.com"><span>table2_span</span></a>
<a href="www.table2_2.com">
<p><h2>TABLE2</h2></p>
</a>
<a href="www.table2_3.com" id="id3">
<ul class="table_one" id="id4">
<tr>tr1_a</tr>
<tr>tr2_a</tr>
<tr>tr3_a</tr>
<tr>tr4_a</tr>
</ul>
</a>
</div>
</body>
</html>
"""
html = etree.HTML(html_str)
html.xpath('//*') # 請將'//*'替換成下面表中實例列的表達式以觀察各表達式的含義和作用。
先給出一張表。下表中給出了標簽定位的表達式和對其作用的描述。下表中,實例列中的表達式的全部表達如下:如第一行中的命令為'//div'
,則全部的表達式為html.xpath('//div')
。注意,這里的html
是一個lxml.etree._Element對象。(我們用HTML表示HTML文件或者其對應的字符串。)
表達式 | 描述 | 實例 | 解釋 |
---|---|---|---|
nodename | 選取此節點的所有子節點 | '//div' | 找到html樹中的所有div標簽 |
/ | 從根節點選取 | '/head/title' | 從根節點找到head->title |
// | 選取任意位置的某個節點 | '//' | html中所有p標簽 |
. | 選取當前節點 | '.' | 返回當前節點對象 |
.. | 選取當前節點的父節點 | '/html/head/..' | 返回head的父節點html |
@ | 選取屬性 | '//div[@class="one"]' | 返回具有屬性class,並且class的值為"one"的節點 |
* | 通配符 | '//div/*' | 返回所有滿足條件的節點 |
| | 一次選擇多個路徑 | '/html/head | //div' | 返回head節點或者div節點 |
@* | 選取所有屬性 | '//div[@*]' | 返回所有具有屬性的div對象 |
3.3 序列定位
通過上面的學習,我們知道html.xpath()返回的是一個包含節點樹對象的列表,對於列表中的元素,我們可以按照列表的索引進行查找,但是,如果想在xpath里面進行選擇,就需要使用序列定位。
下面的代碼承接上文。在給出一張表。
謂語 | 描述 | 實例 | 解釋 |
---|---|---|---|
[1] | 第一個元素 | '//div[1]' | 返回第一個div對象 |
[last()] | 最后一個元素 | '//div[last()]' | 返回最后一個div對象 |
[last()-1] | 倒數第二個元素 | '//div/ul[1]/tr[last()-1]' | 返回所有div對象中第一個ul對象下面的倒數第二個tr對象 |
[position()❤️] | 最前面的兩個元素 | '//tr[position()❤️]' | 返回前兩個tr對象 |
[@lang] | 所有擁有屬性lang的元素 | '//div[@class]' | 返回具有calss屬性的div |
[@lang='en'] | 所有lang='en'的元素 | '//div[@class="en"]' | 返回class屬性值為en的div對象 |
3.4 軸定位
同上。
軸名稱 | 描述 | 實例 | 解釋 |
---|---|---|---|
child | 當前節點的所有子元素 | '//div[1]/child:😗' | 第一個div的所有子節點 |
parent | 當前節點的父節點 | '//div[1]/parent:😗' | 第一個div的父節點 |
ancestor | 當前節點的所有先輩 | '//div[1]/ancestor:😗' | 第一個div的所有祖先節點 |
ancestor-or-self | 當前節點及其所有先輩 | '//div[1]/ancestor-or-self:😗' | .. |
descendant | 當前節點的所有后代 | '//div[1]/descendant::ul' | 第一個div的子孫節點中的ul對象 |
descendant-or-self | 當前節點及其所有后代 | '//body/descendant-or-self' | .. |
preceding | 文檔中當前節點開始標記之前的所有節點 | '//body/preceding:😗' | .. |
following | 文檔中當前節點結束標記之后的所有節點 | '//body/following:😗' | .. |
preceding-sibling | 當前節點之前的所有同級節點 | '//div/preceding-sibling:😗' | .. |
following-sibling | 當前節點之后的所有同級節點 | '//div/following-sibling:😗' | .. |
self | 當前節點 | '//div[0]/self:😗' | 返回當前節點 |
attribute | 當前節點的所有屬性 | '//div/attribute:😗' | 返回所有屬性值 |
namespace | 當前節點的所有命名空間 | '//div/namespace:😗' | 返回命名空間 |
4. 實例
我們已經學了很多的xpath語法。現在可以完成一個小小的綜合練習,用以鞏固我們的學習。
我們的需求:到tiobe
上面抓取最后歡迎的語言top20,並對這些語言的使用情況最簡單的可視化。
上代碼,如有需要請看代碼中的注釋。
網頁內容定位如下:
# 導入所需要的庫
import urllib.request as urlrequest
from lxml import etree
# 獲取html
url = r'https://www.tiobe.com/tiobe-index/'
page = urlrequest.urlopen(url).read()
# 創建lxml對象
html = etree.HTML(page)
# 解析HTML,篩選數據
df = html.xpath('//table[contains(@class, "table-top20")]/tbody/tr//text()')
# 數據寫入數據庫
import pandas as pd
tmp = []
for i in range(0, len(df), 5):
tmp.append(df[i: i+5])
df = pd.DataFrame(tmp)
# 查看數據
df.head(2)
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 1 | 1 | Java | 16.028% | -0.85% |
1 | 2 | 2 | C | 15.154% | +0.19% |
原始數據存在一些問題:
- 數據列名稱含義不明,要給列添加有意義的名稱['Aug 2019', 'Aug 2018', 'Name', 'Rating', 'Change']
- 最后兩列為字符串,需要轉換成float格式
# 數據處理
df.columns = ['Aug 2019', 'Aug 2018', 'Name', 'Rating', 'Change']
# 處理最后兩列數據
df['Rating'] = [float(istr.replace('%', '')) for istr in df['Rating']]
df['Change'] = [float(istr.replace('%', '')) for istr in df['Change']]
# 再次查看數據
df.head()
Aug 2019 | Aug 2018 | Name | Rating | Change | |
---|---|---|---|---|---|
0 | 1 | 1 | Java | 16.028 | -0.85 |
1 | 2 | 2 | C | 15.154 | 0.19 |
2 | 3 | 4 | Python | 10.020 | 3.03 |
3 | 4 | 3 | C++ | 6.057 | -1.41 |
4 | 5 | 6 | C# | 3.842 | 0.30 |
# 繪制圖像
import matplotlib.pyplot as plt
plt.pie(df['Rating'], labels=df['Name'])
plt.show()