【前情回顧】如何靈活的解析網頁,提取我們想要的資料,是猿人們寫爬蟲時非常關心和需要解決的問題。從Python眾多的可利用工具中,我們選擇了lxml,它的好我們知道,它的妙待我們探討。前面我們已經從html字串轉換成HtmlElement物件,接下來我們就探討該如何操作這個HtmlElement物件。
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'
以上就是我們從網頁提取內容時用到的主要屬性和方法。下一節,我們將以例項講解具體提取資料的過程。
我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***