lxml刪除節點

場景:

lxml做爬蟲時,有時爲了方便,我們需要刪除節點。在某些網站裏,長文會在其中插入沒用節點以干擾我們爬取的數據。例如,百度知道里的長文。

樣本:

<li>
   <div>
      <a>111</a>
      <a class="error">222</a>
      <a>333</a>
   </div>
   <div>
      <a>aaa</a>
      <a class="error">bbb</a>
      <a>ccc</a>
   </div>
   <div>
      <div>
         <a>python</a>
         <a class="error">not</a>
         <a>the best</a>
      </div>
   </div>
</li>

樣本中,class爲error作爲目標刪除對象。如果直接爬取文本,會得到“python not the best”這樣的語句。一般反爬而言css或js裏會定義class=error不顯示。所以不影響用戶展現,隻影響爬蟲獲取。

正文:

使用lxml刪除節點用到lxml的remover函數。但使用remover有兩個條件。

1、刪除的節點必須是相對路徑!【親自測試這不是必須的。網上其他教程沒自己測試就轉發,真是誤人子弟。】

2、只能刪除子節點!

一、案例1【跨節點刪除,錯誤】:

# -*- encoding:utf-8 -*-
from lxml.etree import HTML

text = """<li>
   <div>
      <a>111</a>
      <a class="error">222</a>
      <a>333</a>
   </div>
   <div>
      <a>aaa</a>
      <a class="error">bbb</a>
      <a>ccc</a>
   </div>
   <div>
      <div>
         <a>python</a>
         <a class="error">not</a>
         <a>the best</a>
      </div>
   </div>
</li>
"""

h = HTML(text)
#統計干擾節點
error_elms = h.xpath('.//a[@class="error"]')
print('存在干擾節點', error_elms)
for elm in error_elms:
    print(elm.text)
#-----刪除節點------
#案例1
h.remove(error_elms[0])
#------------------
#統計干擾節點
error_elms = h.xpath('.//a[@class="error"]')
print('存在干擾節點', error_elms)
for elm in error_elms:
    print(elm.text)

雖然根節點也有remover可調用但是會報出錯誤,提示不是子節點。

存在干擾節點 [<Element a at 0x350fac8>, <Element a at 0x350fe48>, <Element a at 0x350fe88>]
222
bbb
not
【拋出異常】builtins.ValueError: Element is not a child of this node.

原因在於,我們的節點a中間還有個div節點。

 

案例二(絕對路徑刪除節點,成功

# -*- encoding:utf-8 -*-
from lxml.etree import HTML

text = """<li>
   <div>
      <a>111</a>
      <a class="error">222</a>
      <a>333</a>
   </div>
   <div>
      <a>aaa</a>
      <a class="error">bbb</a>
      <a>ccc</a>
   </div>
   <div>
      <div>
         <a>python</a>
         <a class="error">not</a>
         <a>the best</a>
      </div>
   </div>
</li>
"""

h = HTML(text)
#統計干擾節點
error_elms = h.xpath('.//a[@class="error"]')
print('存在干擾節點', error_elms)
for elm in error_elms:
    print(elm.text)
#-----刪除節點------
#案例1
#h.remove(error_elms[0])
#案例2
new_error_elms = h.xpath('//div[1]//a[@class="error"]')
father = h.xpath('//div')[0]
father.remove(error_elms[0])
#------------------
#統計干擾節點
error_elms = h.xpath('.//a[@class="error"]')
print('存在干擾節點', error_elms)
for elm in error_elms:
    print(elm.text)

爲了演示。我用絕對路徑捕獲第一個目標節點(222)到new_error_elms。father就是第一個干擾節點的父節點。結果是能成功刪除的。

驗證了網上說“必須相對路徑”的說法是錯誤的。只要節點樹nodeTree上定位到是父子關係即可。輸出

存在干擾節點 [<Element a at 0x3517148>, <Element a at 0x3517108>, <Element a at 0x3517188>]
222
bbb
not
存在干擾節點 [<Element a at 0x3517108>, <Element a at 0x3517188>]
bbb
not

 

案例三(正確寫法

# -*- encoding:utf-8 -*-
from lxml.etree import HTML

text = """<li>
   <div>
      <a>111</a>
      <a class="error">222</a>
      <a>333</a>
   </div>
   <div>
      <a>aaa</a>
      <a class="error">bbb</a>
      <a>ccc</a>
   </div>
   <div>
      <div>
         <a>python</a>
         <a class="error">not</a>
         <a>the best</a>
      </div>
   </div>
</li>
"""

h = HTML(text)
#統計干擾節點
error_elms = h.xpath('.//a[@class="error"]')
print('存在干擾節點', error_elms)
for elm in error_elms:
    print(elm.text)
#-----刪除節點------
#案例1
#h.remove(error_elms[0])
#案例2
#new_error_elms = h.xpath('//div[1]//a[@class="error"]')
#father = h.xpath('//div')[0]
#father.remove(error_elms[0])
#正確寫法
fathers = h.xpath('//a[@class="error"]/..')
for father in fathers:
    new_error_elms = father.xpath('./a[@class="error"]')
    for error_elm in new_error_elms:
        father.remove(error_elm)
#------------------
#統計干擾節點
error_elms = h.xpath('.//a[@class="error"]')
print('存在干擾節點', error_elms)
for elm in error_elms:
    print(elm.text)

此寫法是筆者認爲比較正確的寫法。除非你要 刪除根節點 或 刪除嵌套目標節點,否則不會報錯。輸出

存在干擾節點 [<Element a at 0x351d048>, <Element a at 0x351d088>, <Element a at 0x351d0c8>]
222
bbb
not
存在干擾節點 []

結語:

本文對於大多數爬蟲作用不大。大多數能用xpath篩選出來。但是捕獲長文、小說之類的就會被這種混淆反爬搞到腦袋嗡嗡的。舉個例子

      <div>
         "I think "
         <a>python</a>
         "is "
         <a class="error">not</a>
         "the "
         <a>best</a>
         ......
      </div>

大家可以試試捕獲div下的長文,當然不要把not捕獲進去哦。

如果要刪除嵌套目標節點。只要忽略異常,基本沒有問題了。因爲 既然“子節點被父節點被刪除了“,“孫節點發現找不到子節點”又有什麼關係呢?

偷個懶,收工。

fathers = h.xpath('//a[@class="error"]/..')
for father in fathers:
    try:
        new_error_elms = father.xpath('./a[@class="error"]')
        for error_elm in new_error_elms:
            father.remove(error_elm)
    except:
        pass

 

轉載請註明出處:https://my.oschina.net/jacky326/blog/4687663

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章