Python之lxml的安裝與使用


前言

在爬蟲的學習中,我們爬取網頁信息之后就是對信息項匹配,這個時候一般是使用正則。但是在使用中發現正則寫的不好的時候不能精確匹配(這其實是自己的問題!)所以就找啊找。想到了可以通過標簽來進行精確匹配豈不是比正則要快。所以找到了lxml。

lxml是python的一個解析庫,支持HTML和XML的解析,支持XPath解析方式,而且解析效率非常高

XPath,全稱XML Path Language,即XML路徑語言,它是一門在XML文檔中查找信息的語言,它最初是用來搜尋XML文檔的,但是它同樣適用於HTML文檔的搜索

XPath的選擇功能十分強大,它提供了非常簡明的路徑選擇表達式,另外,它還提供了超過100個內建函數,用於字符串、數值、時間的匹配以及節點、序列的處理等,幾乎所有我們想要定位的節點,都可以用XPath來選擇

XPath於1999年11月16日成為W3C標准,它被設計為供XSLT、XPointer以及其他XML解析軟件使用,更多的文檔可以訪問其官方網站:https://www.w3.org/TR/xpath/

1、python庫lxml的安裝

windows系統下的安裝:


   #pip安裝
   pip3 install lxml
   
   #wheel安裝
   #下載對應系統版本的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml
   pip3 install lxml-4.2.1-cp36-cp36m-win_amd64.whl

linux下安裝:


   yum install -y epel-release libxslt-devel libxml2-devel openssl-devel
   
   pip3 install lxml

驗證安裝:


   $python3
   >>>import lxml

2、XPath常用規則

表達式 描述
nodename 選取此節點的所有子節點
/ 從當前節點選取直接子節點
// 從當前節點選取子孫節點
. 選取當前節點
.. 選取當前節點的父節點
@ 選取屬性
* 通配符,選擇所有元素節點與元素名
@* 選取所有屬性
[@attrib] 選取具有給定屬性的所有元素
[@attrib='value'] 選取給定屬性具有給定值的所有元素
[tag] 選取所有具有指定元素的直接子節點
[tag='text'] 選取所有具有指定元素並且文本內容是text節點

(1)讀取文本解析節點


   from lxml import etree
   
   text='''
  <div>
    <ul>
    <li class="item-0"><a href="link1.html">第一個</a></li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-0"><a href="link5.html">a屬性</a>
    </ul>
    </div>
  '''
   html=etree.HTML(text) #初始化生成一個XPath解析對象
   result=etree.tostring(html,encoding='utf-8') #解析對象輸出代碼
   print(type(html))
   print(type(result))
   print(result.decode('utf-8'))
   
   #etree會修復HTML文本節點
   <class 'lxml.etree._Element'>
   <class 'bytes'>
   <html><body><div>
    <ul>
    <li class="item-0"><a href="link1.html">第一個</a></li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-0"><a href="link5.html">a屬性</a>
    </li></ul>
    </div>
   </body></html>

(2)讀取HTML文件進行解析


   from lxml import etree
   
   html=etree.parse('test.html',etree.HTMLParser()) #指定解析器HTMLParser會根據文件修復HTML文件中缺失的如聲明信息
   result=etree.tostring(html) #解析成字節
   #result=etree.tostringlist(html) #解析成列表
   print(type(html))
   print(type(result))
   print(result)
   
   #
   <class 'lxml.etree._ElementTree'>
   <class 'bytes'>
   b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">\n<html><body><div>&#13;\n <ul>&#13;\n <li class="item-0"><a href="link1.html">first item</a></li>&#13;\n <li class="item-1"><a href="link2.html">second item</a></li>&#13;\n <li class="item-inactive"><a href="link3.html">third item</a></li>&#13;\n <li class="item-1"><a href="link4.html">fourth item</a></li>&#13;\n <li class="item-0"><a href="link5.html">fifth item</a>&#13;\n </li></ul>&#13;\n </div>&#13;\n</body></html>'

(3)獲取所有節點

返回一個列表每個元素都是Element類型,所有節點都包含在其中


   from lxml import etree
   
   html=etree.parse('test',etree.HTMLParser())
   result=html.xpath('//*') #//代表獲取子孫節點,*代表獲取所有
   
   print(type(html))
   print(type(result))
   print(result)
   
   #
   <class 'lxml.etree._ElementTree'>
   <class 'list'>
  [<Element html at 0x754b210048>, <Element body at 0x754b210108>, <Element div at 0x754b210148>, <Element ul at 0x754b210188>, <Element li at 0x754b2101c8>, <Element a at 0x754b210248>, <Element li at 0x754b210288>, <Element a at 0x754b2102c8>, <Element li at 0x754b210308>, <Element a at 0x754b210208>, <Element li at 0x754b210348>, <Element a at 0x754b210388>, <Element li at 0x754b2103c8>, <Element a at 0x754b210408>]

如要獲取li節點,可以使用//后面加上節點名稱,然后調用xpath()方法


   html.xpath('//li') #獲取所有子孫節點的li節點

(4)獲取子節點

通過/或者//即可查找元素的子節點或者子孫節點,如果想選擇li節點的所有直接a節點,可以這樣使用


   result=html.xpath('//li/a') #通過追加/a選擇所有li節點的所有直接a節點,因為//li用於選中所有li節點,/a用於選中li節點的所有直接子節點a

(5)獲取父節點

我們知道通過連續的/或者//可以查找子節點或子孫節點,那么要查找父節點可以使用..來實現也可以使用parent::來獲取父節點

    from lxml import etree
    from lxml.etree import HTMLParser
    text='''
    <div>
     <ul>
     <li class="item-0"><a href="link1.html">第一個</a></li>
     <li class="item-1"><a href="link2.html">second item</a></li>
     </ul>
     </div>
    '''
    
    html=etree.HTML(text,etree.HTMLParser())
    result=html.xpath('//a[@href="link2.html"]/../@class')
    result1=html.xpath('//a[@href="link2.html"]/parent::*/@class')
    print(result)
    print(result1)
    
    
    #
    ['item-1']
    ['item-1']

(6)屬性匹配

在選取的時候,我們還可以用@符號進行屬性過濾。比如,這里如果要選取class為item-1的li節點,可以這樣實現:

    from lxml import etree
    from lxml.etree import HTMLParser
    text='''
    <div>
     <ul>
     <li class="item-0"><a href="link1.html">第一個</a></li>
     <li class="item-1"><a href="link2.html">second item</a></li>
     </ul>
     </div>
    '''
    
    html=etree.HTML(text,etree.HTMLParser())
    result=html.xpath('//li[@class="item-1"]')
    print(result)

(7)文本獲取

我們用XPath中的text()方法獲取節點中的文本

    from lxml import etree
    
    text='''
    <div>
     <ul>
     <li class="item-0"><a href="link1.html">第一個</a></li>
     <li class="item-1"><a href="link2.html">second item</a></li>
     </ul>
     </div>
    '''
    
    html=etree.HTML(text,etree.HTMLParser())
    result=html.xpath('//li[@class="item-1"]/a/text()') #獲取a節點下的內容
    result1=html.xpath('//li[@class="item-1"]//text()') #獲取li下所有子孫節點的內容
    
    print(result)
    print(result1)

(8)屬性獲取

使用@符號即可獲取節點的屬性,如下:獲取所有li節點下所有a節點的href屬性

    result=html.xpath('//li/a/@href') #獲取a的href屬性
    result=html.xpath('//li//@href') #獲取所有li子孫節點的href屬性

(9)屬性多值匹配

如果某個屬性的值有多個時,我們可以使用contains()函數來獲取

    from lxml import etree
    
    text1='''
    <div>
     <ul>
     <li class="aaa item-0"><a href="link1.html">第一個</a></li>
     <li class="bbb item-1"><a href="link2.html">second item</a></li>
     </ul>
     </div>
    '''
    
    html=etree.HTML(text1,etree.HTMLParser())
    result=html.xpath('//li[@class="aaa"]/a/text()')
    result1=html.xpath('//li[contains(@class,"aaa")]/a/text()')
    
    print(result)
    print(result1)
    
    #通過第一種方法沒有取到值,通過contains()就能精確匹配到節點了
    []
    ['第一個']

(10)多屬性匹配

另外我們還可能遇到一種情況,那就是根據多個屬性確定一個節點,這時就需要同時匹配多個屬性,此時可用運用and運算符來連接使用:

    from lxml import etree
    
    text1='''
    <div>
     <ul>
     <li class="aaa" name="item"><a href="link1.html">第一個</a></li>
     <li class="aaa" name="fore"><a href="link2.html">second item</a></li>
     </ul>
     </div>
    '''
    
    html=etree.HTML(text1,etree.HTMLParser())
    result=html.xpath('//li[@class="aaa" and @name="fore"]/a/text()')
    result1=html.xpath('//li[contains(@class,"aaa") and @name="fore"]/a/text()')
    
    
    print(result)
    print(result1)
    
    
    #
    ['second item']
    ['second item']

(11)XPath中的運算符

運算符 | 描述 | 實例 | 返回值 ---|---|---|---

or

| 或 | age=19 or age=20 | 如果age等於19或者等於20則返回true反正返回false and | 與 | age>19 and age<21 | 如果age等於20則返回true,否則返回false mod | 取余 | 5 mod 2 | 1 | | 取兩個節點的集合 | //book | //cd | 返回所有擁有book和cd元素的節點集合

  • | 加 | 6+4 | 10

  • | 減 | 6-4 | 2

  • | 乘 | 6*4 | 24 div | 除法 | 8 div 4 | 2 = | 等於 | age=19 | true != | 不等於 | age!=19 | true < | 小於 | age<19 | true <= | 小於或等於 | age<=19 | true

| 大於 | age>19 | true = | 大於或等於 | age>=19 | true

此表參考來源:http://www.w3school.com.cn/xpath/xpath_operators.asp

(12)按序選擇

有時候,我們在選擇的時候某些屬性可能同時匹配多個節點,但我們只想要其中的某個節點,如第二個節點或者最后一個節點,這時可以利用中括號引入索引的方法獲取特定次序的節點:

    from lxml import etree
    
    text1='''
    <div>
     <ul>
      <li class="aaa" name="item"><a href="link1.html">第一個</a></li>
      <li class="aaa" name="item"><a href="link1.html">第二個</a></li>
      <li class="aaa" name="item"><a href="link1.html">第三個</a></li>
      <li class="aaa" name="item"><a href="link1.html">第四個</a></li> 
     </ul>
     </div>
    '''
    
    html=etree.HTML(text1,etree.HTMLParser())
    
    result=html.xpath('//li[contains(@class,"aaa")]/a/text()') #獲取所有li節點下a節點的內容
    result1=html.xpath('//li[1][contains(@class,"aaa")]/a/text()') #獲取第一個
    result2=html.xpath('//li[last()][contains(@class,"aaa")]/a/text()') #獲取最后一個
    result3=html.xpath('//li[position()>2 and position()<4][contains(@class,"aaa")]/a/text()') #獲取第一個
    result4=html.xpath('//li[last()-2][contains(@class,"aaa")]/a/text()') #獲取倒數第三個
    
    
    print(result)
    print(result1)
    print(result2)
    print(result3)
    print(result4)
    
    
    #
    ['第一個', '第二個', '第三個', '第四個']
    ['第一個']
    ['第四個']
    ['第三個']
    ['第二個']

這里使用了last()、position()函數,在XPath中,提供了100多個函數,包括存取、數值、字符串、邏輯、節點、序列等處理功能,它們的具體作用可參考:http://www.w3school.com.cn/xpath/xpath_functions.asp

(13)節點軸選擇

XPath提供了很多節點選擇方法,包括獲取子元素、兄弟元素、父元素、祖先元素等,示例如下:

    from lxml import etree
    
    text1='''
    <div>
     <ul>
      <li class="aaa" name="item"><a href="link1.html">第一個</a></li>
      <li class="aaa" name="item"><a href="link1.html">第二個</a></li>
      <li class="aaa" name="item"><a href="link1.html">第三個</a></li>
      <li class="aaa" name="item"><a href="link1.html">第四個</a></li> 
     </ul>
     </div>
    '''
    
    html=etree.HTML(text1,etree.HTMLParser())
    result=html.xpath('//li[1]/ancestor::*') #獲取所有祖先節點
    result1=html.xpath('//li[1]/ancestor::div') #獲取div祖先節點
    result2=html.xpath('//li[1]/attribute::*') #獲取所有屬性值
    result3=html.xpath('//li[1]/child::*') #獲取所有直接子節點
    result4=html.xpath('//li[1]/descendant::a') #獲取所有子孫節點的a節點
    result5=html.xpath('//li[1]/following::*') #獲取當前子節之后的所有節點
    result6=html.xpath('//li[1]/following-sibling::*') #獲取當前節點的所有同級節點
    
    
    #
    [<Element html at 0x3ca6b960c8>, <Element body at 0x3ca6b96088>, <Element div at 0x3ca6b96188>, <Element ul at 0x3ca6b961c8>]
    [<Element div at 0x3ca6b96188>]
    ['aaa', 'item']
    [<Element a at 0x3ca6b96248>]
    [<Element a at 0x3ca6b96248>]
    [<Element li at 0x3ca6b96308>, <Element a at 0x3ca6b96348>, <Element li at 0x3ca6b96388>, <Element a at 0x3ca6b963c8>, <Element li at 0x3ca6b96408>, <Element a at 0x3ca6b96488>]
    [<Element li at 0x3ca6b96308>, <Element li at 0x3ca6b96388>, <Element li at 0x3ca6b96408>]

以上使用的是XPath軸的用法,更多軸的用法可參考:http://www.w3school.com.cn/xpath/xpath_axes.asp

(14)案例應用:抓取TIOBE指數前20名排行開發語言

    #!/usr/bin/env python
    #coding:utf-8
    import requests
    from requests.exceptions import RequestException
    from lxml import etree
    from lxml.etree import ParseError
    import json
    
    def one_to_page(html):
     headers={
     'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36'
     }
     try:
     response=requests.get(html,headers=headers)
     body=response.text #獲取網頁內容
     except RequestException as e:
     print('request is error!',e)
     try:
     html=etree.HTML(body,etree.HTMLParser()) #解析HTML文本內容
     result=html.xpath('//table[contains(@class,"table-top20")]/tbody/tr//text()') #獲取列表數據
     pos = 0
     for i in range(20):
      if i == 0:
      yield result[i:5]
      else:
      yield result[pos:pos+5] #返回排名生成器數據
      pos+=5
     except ParseError as e:
      print(e.position)
    
    
    def write_file(data): #將數據重新組合成字典寫入文件並輸出
     for i in data:
     sul={
      '2018年6月排行':i[0],
      '2017年6排行':i[1],
      '開發語言':i[2],
      '評級':i[3],
      '變化率':i[4]
     }
     with open('test.txt','a',encoding='utf-8') as f:
      f.write(json.dumps(sul,ensure_ascii=False) + '\n') #必須格式化數據
      f.close()
     print(sul)
     return None
    
    
    def main():
     url='https://www.tiobe.com/tiobe-index/'
     data=one_to_page(url)
     revaule=write_file(data)
     if revaule == None:
     print('ok')
     
     
     
     
    if __name__ == '__main__':
     main()
    
    
    
    #
    {'2018年6月排行': '1', '2017年6排行': '1', '開發語言': 'Java', '評級': '15.368%', '變化率': '+0.88%'}
    {'2018年6月排行': '2', '2017年6排行': '2', '開發語言': 'C', '評級': '14.936%', '變化率': '+8.09%'}
    {'2018年6月排行': '3', '2017年6排行': '3', '開發語言': 'C++', '評級': '8.337%', '變化率': '+2.61%'}
    {'2018年6月排行': '4', '2017年6排行': '4', '開發語言': 'Python', '評級': '5.761%', '變化率': '+1.43%'}
    {'2018年6月排行': '5', '2017年6排行': '5', '開發語言': 'C#', '評級': '4.314%', '變化率': '+0.78%'}
    {'2018年6月排行': '6', '2017年6排行': '6', '開發語言': 'Visual Basic .NET', '評級': '3.762%', '變化率': '+0.65%'}
    {'2018年6月排行': '7', '2017年6排行': '8', '開發語言': 'PHP', '評級': '2.881%', '變化率': '+0.11%'}
    {'2018年6月排行': '8', '2017年6排行': '7', '開發語言': 'JavaScript', '評級': '2.495%', '變化率': '-0.53%'}
    {'2018年6月排行': '9', '2017年6排行': '-', '開發語言': 'SQL', '評級': '2.339%', '變化率': '+2.34%'}
    {'2018年6月排行': '10', '2017年6排行': '14', '開發語言': 'R', '評級': '1.452%', '變化率': '-0.70%'}
    {'2018年6月排行': '11', '2017年6排行': '11', '開發語言': 'Ruby', '評級': '1.253%', '變化率': '-0.97%'}
    {'2018年6月排行': '12', '2017年6排行': '18', '開發語言': 'Objective-C', '評級': '1.181%', '變化率': '-0.78%'}
    {'2018年6月排行': '13', '2017年6排行': '16', '開發語言': 'Visual Basic', '評級': '1.154%', '變化率': '-0.86%'}
    {'2018年6月排行': '14', '2017年6排行': '9', '開發語言': 'Perl', '評級': '1.147%', '變化率': '-1.16%'}
    {'2018年6月排行': '15', '2017年6排行': '12', '開發語言': 'Swift', '評級': '1.145%', '變化率': '-1.06%'}
    {'2018年6月排行': '16', '2017年6排行': '10', '開發語言': 'Assembly language', '評級': '0.915%', '變化率': '-1.34%'}
    {'2018年6月排行': '17', '2017年6排行': '17', '開發語言': 'MATLAB', '評級': '0.894%', '變化率': '-1.10%'}
    {'2018年6月排行': '18', '2017年6排行': '15', '開發語言': 'Go', '評級': '0.879%', '變化率': '-1.17%'}
    {'2018年6月排行': '19', '2017年6排行': '13', '開發語言': 'Delphi/Object Pascal', '評級': '0.875%', '變化率': '-1.28%'}
    {'2018年6月排行': '20', '2017年6排行': '20', '開發語言': 'PL/SQL', '評級': '0.848%', '變化率': '-0.72%'}

XPath的更多用法參考:http://www.w3school.com.cn/xpath/index.asp

python lxml庫的更多用法參考:http://lxml.de/

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM