場景:
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