Python 爬蟲網頁解析工具lxml.html(二)

王平發表於2018-12-05

【前情回顧】如何靈活的解析網頁,提取我們想要的資料,是猿人們寫爬蟲時非常關心和需要解決的問題。從Python眾多的可利用工具中,我們選擇了lxml,它的好我們知道,它的妙待我們探討。前面我們已經從html字串轉換成HtmlElement物件,接下來我們就探討該如何操作這個HtmlElement物件。

lxml.html解析html網頁2

lxml.html 的 HtmlElement 物件的各種屬性和方法

這個HtmlElement物件有各種方法,我們重點討論跟解析網頁相關的函式,而修改這個物件的方法若與提取內容相關也一併介紹,介紹過程結合下面這段html程式碼以便更好說明問題:

<div class="post" id="123">
    <p class="para">abc<a href="/to-go">link</a></p>
</div>

.attrib 屬性和 .get()方法

前者是html tag的屬性集合,以字典表示;後者是取得某個屬性的值,相當於字典的.get()方法。看示例:

In [35]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p></div>')

In [37]: doc.attrib
Out[37]: {'class': 'post', 'id': '123'}

In [38]: doc.get('class')
Out[38]: 'post'

.drop_tag()方法

移除該html tag,但保留它的子節點和文字併合併到該tag的父節點。

In [46]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p></div>')
In [47]: doc.find('.//p').drop_tag()

In [48]: lxml.html.tostring(doc)
Out[48]: b'<div class="post" id="123">abc<a href="/to-go">link</a></div>'

.drop_tree()方法

移除該節及其子節點和文字,而它後面的文字(tail text)合併到前面一個節點或父節點。

In [50]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p></div>')
In [51]: doc.find('.//p').drop_tree()

In [52]: lxml.html.tostring(doc)
Out[52]: b'<div class="post" id="123"></div>'

看過我們【猿人學】爬蟲教程的小猿們可能還記得我們在大規模非同步新聞爬蟲:網頁正文的提取 一文中就使用了drop_tree()方法去除不屬於正文的節點。

.find(path), .findall(path), .findtext(path)方法

透過path(Xpath)或tag查詢特定節點,前者返回找到的第一個,第二個返回找到的全部HTMLElement,第三個返回找到的第一個的節點的文字(.text)

In [55]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p></div>')

In [56]: doc.find('p')
Out[56]: <Element p at 0x7fc40a4dd6d8>

In [57]: doc.find('.//a')
Out[57]: <Element a at 0x7fc409fee4a8>

In [58]: doc.findall('p')
Out[58]: [<Element p at 0x7fc40a4dd6d8>]

In [76]: doc.findtext('.//a')
Out[76]: 'link'

.find_class(class_name)方法

透過class名稱查詢所有含有class_name的元素,返回HtmlElement的列表

In [70]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p><p class="para p2"></p></div>')

In [71]: doc.find_class('para')
Out[71]: [<Element p at 0x7fc40a3ff278>, <Element p at 0x7fc40a3ffc78>]

.get_element_by_id(id) 方法

得到第一個id為輸入id的節點。如果有多個相同id的節點(按道理講,一個HTML文件裡面的id是唯一的)只返回第一個。

In [79]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p></div>')

In [80]: doc.get_element_by_id('123')
Out[80]: <Element div at 0x7fc409fda2c8>

.getchildren()、getparent() 方法

顧名思義,獲取 孩子節點和父節點。需要注意的是,還是可以有多個(返回list),父親只有一個。

In [83]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p></div>')

In [84]: doc.getchildren()
Out[84]: [<Element p at 0x7fc410836b38>]

In [85]: doc.getparent()
Out[85]: <Element body at 0x7fc40a3ff9a8>
# 注意:輸入的本沒有body,div已經是最上層節點,它的父節點就是body了

.getnext() .getprevious() 方法

獲取後一個或前一個節點,如果沒有則返回None。

In [109]: doc = lxml.html.fromstring('<div><p>abc</p><p>xyz</p></div>')
In [110]: doc.getnext()

In [111]: doc.find('p').getnext()
Out[111]: <Element p at 0x7fc409fdad68>

In [112]: doc.find('p').getprevious()

.getiterator()、.iter() 方法

從該節點開始,按文件順序(深度優先)遍歷所有子節點。可以指定只遍歷某些tag。

In [127]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p></div>')
In [128]: for itr in doc.getiterator():
     ...:     print(itr.tag)
     ...: 
div
p
a
In [129]: for itr in doc.iter():
     ...:     print(itr.tag)
     ...: 
div
p
a

.iterchildren() 方法

只遍歷子節點。

.iterancestors() .iterdescendants()方法

前者遍歷前輩(從父親節點開始),後者遍歷後輩(從子輩開始),都跳過該節點。

In [134]: doc = lxml.html.fromstring('<div class="post" id="123"><p class="para">abc<a href="/to-go">link</a></p></div>')

In [135]: a = doc.find('.//a')

In [136]: for itr in doc.iterancestors():
     ...:     print(itr.tag)
     ...: 
body
html

In [137]: for itr in a.iterancestors():
     ...:     print(itr.tag)
     ...: 
p
div
body
html

In [138]: for itr in doc.iterdescendants():
     ...:     print(itr.tag)
     ...: 
p
a

.iterfind(path) 方法

遍歷所有符合path的子節點,類似於findall()

.make_links_absolute(base_url)

很多網頁的連結都是類似href=”/path/a.html”沒有寫全網址,這個方法的作用就是補全網址。

.tag 屬性

該節點的html tag 名稱

.text .tail 屬性

都是該節點的文字內容,不同的是一個在tag內,一個在尾部:

<p>text</p>tail

再看下面的程式碼

In [173]: doc = lxml.html.fromstring('<div><p class="para">abc<a href="/to-go">link</a>worod</p>apple</div>')

In [174]: p = doc.find('p')

In [175]: p.text
Out[175]: 'abc'

In [176]: p.tail
Out[176]: 'apple'

.text_content() 方法

返回給節點及其子節點包含的所有文字

In [178]: doc.text_content()
Out[178]: 'abclinkworodapple'

以上就是我們從網頁提取內容時用到的主要屬性和方法。下一節,我們將以例項講解具體提取資料的過程。

猿人學banner宣傳圖

我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。

***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***

相關文章