1. Beautiful Soup (bs4 )
Beautiful Soup 是一個(gè)可以從HTML或XML文件中提取數(shù)據(jù)的Python庫(kù).它能夠通過(guò)你喜歡的轉(zhuǎn)換器實(shí)現(xiàn)慣用的文檔導(dǎo)航,查找,修改文檔的方式.Beautiful Soup會(huì)幫你節(jié)省數(shù)小時(shí)甚至數(shù)天的工作時(shí)間.
在測(cè)試學(xué)習(xí)使用bs4的過(guò)程中,需要反復(fù)用到xml文檔。這里就采用bs4官方文檔中提供的一個(gè)html代碼。這是愛(ài)麗絲夢(mèng)游仙境中的一段內(nèi)容。
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" id="link1">Elsie</a>,
<a class="sister" id="link2">Lacie</a> and
<a class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
包含一個(gè)簡(jiǎn)單的頁(yè)面。
下面使用python做簡(jiǎn)單測(cè)試。首先是格式化輸出:
# 構(gòu)造BeautifulSoup 對(duì)象,其中html_doc為前文的愛(ài)麗絲夢(mèng)游仙境頁(yè)面
page = BeautifulSoup(html_doc, 'html.parser')
# 格式化輸出
print(page.prettify())
控制臺(tái)的輸出結(jié)果為:
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
<body>
<p class="title">
<b>
The Dormouse's story
</b>
</p>
<p class="story">
Once upon a time there were three little sisters; and their names were
<a class="sister" id="link1">
Elsie
</a>
,
<a class="sister" id="link2">
Lacie
</a>
and
<a class="sister" id="link3">
Tillie
</a>
;
and they lived at the bottom of a well.
</p>
<p class="story">
...
</p>
</body>
</html>
顯然這是一個(gè)規(guī)范的html文件。使用起來(lái)實(shí)在是太方便了~再探索一下其他的功能吧。
bs4能夠很輕松地獲取到文檔信息,瀏覽結(jié)構(gòu)化信息。例如,獲取頁(yè)面的title,只需要
page.title.name
需要注意的是,如果使用CSS選擇器,應(yīng)該使用select方法,如:
#查找class為sister的
print('class為sister的(CSS方式查找的):', page.select('.sister'))
#查找id為link2的
print('id為link2的(CSS方式查找的:)', page.select('#link2'))
完整的測(cè)試代碼:
# 構(gòu)造BeautifulSoup 對(duì)象,其中html_doc為前文的愛(ài)麗絲夢(mèng)游仙境頁(yè)面
page = BeautifulSoup(html_doc, 'html.parser')
# 格式化輸出
# print(page.prettify())
# 獲取title相關(guān)信息
print('page.title : ', page.title)
print('page.title.name : ', page.title.name)
print('page.title.text : ', page.title.text)
print('page.title.parent.name : ', page.title.parent.name)
# 獲取a標(biāo)簽
print('單個(gè)a標(biāo)簽:', page.a)
print('所有a標(biāo)簽: ', page.find_all('a'))
# 以css的方式查找 使用select
print('class為sister的(CSS方式查找的):', page.select('.sister'))
print('id為link2的(CSS方式查找的:)', page.select('#link2'))
輸出結(jié)果為:
page.title : <title>The Dormouse's story</title>
page.title.name : title
page.title.text : The Dormouse's story
page.title.parent.name : head
單個(gè)a標(biāo)簽: <a class="sister" id="link1">Elsie</a>
所有a標(biāo)簽: [<a class="sister" id="link1">Elsie</a>, <a class="sister" id="link2">Lacie</a>, <a class="sister" id="link3">Tillie</a>]
class為sister的(CSS方式查找的): [<a class="sister" id="link1">Elsie</a>, <a class="sister" id="link2">Lacie</a>, <a class="sister" id="link3">Tillie</a>]
id為link2的(CSS方式查找的:) [<a class="sister" id="link2">Lacie</a>]
如果要獲取標(biāo)簽中的某一部分,可以使用get方法。例如:獲取頁(yè)面中所有a標(biāo)簽的鏈接內(nèi)容。
for line in page.find_all('a'):
print(line.get('href'))
輸出為:
http://example.com/elsie
http://example.com/lacie
http://example.com/tillie
如果要獲取頁(yè)面中所有的文字信息,只需要一行代碼:
text = page.get_text()
這是bs4一些最基礎(chǔ)的用法。遇到問(wèn)題就查一下官方文檔:bs4.4.0官方文檔
2. lxml
lxml是一個(gè)Python庫(kù),使用它可以輕松處理XML和HTML文件,還可以用于web爬取。市面上有很多現(xiàn)成的XML解析器,但是為了獲得更好的結(jié)果,開(kāi)發(fā)人員有時(shí)更愿意編寫(xiě)自己的XML和HTML解析器。這時(shí)lxml庫(kù)就派上用場(chǎng)了。這個(gè)庫(kù)的主要優(yōu)點(diǎn)是易于使用,在解析大型文檔時(shí)速度非常快,歸檔的也非常好,并且提供了簡(jiǎn)單的轉(zhuǎn)換方法來(lái)將數(shù)據(jù)轉(zhuǎn)換為Python數(shù)據(jù)類(lèi)型,從而使文件操作更容易。
2.1 Xpath語(yǔ)言
節(jié)點(diǎn):元素、屬性、文本、命名空間、文檔節(jié)點(diǎn)等。
節(jié)點(diǎn)關(guān)系:父、字、同胞、先輩、后代等。
| 表達(dá)式 | 描述 |
|---|---|
| nodename | 選此節(jié)點(diǎn)的所有子節(jié)點(diǎn) |
| // | 從任意子節(jié)點(diǎn)選擇 |
| / | 從根節(jié)點(diǎn)選擇(這里是相對(duì)的) |
| . | 從當(dāng)前節(jié)點(diǎn)選擇 |
| .. | 從上一級(jí)節(jié)點(diǎn)選擇 |
| @ | 取屬性 |
2.2 lxml的使用
首先需要導(dǎo)入lxml庫(kù)
from lxml import etree
from bs4_sample import html_doc
構(gòu)造etree
et = etree.HTML(html_doc)
需要使用單個(gè)屬性獲取所需內(nèi)容
- 變量.tag——標(biāo)簽名——字符串
- 變量.attrib——節(jié)點(diǎn)標(biāo)簽a的屬性——字典
- 變量.text——標(biāo)簽文本——字符串
通過(guò)例子體會(huì):
et = etree.HTML(html_doc)
print(et)
for tag in et:
print("TAG : ", et.tag)
for attrib in et:
print("ATTRIB : ", et.attrib)
for text in et:
print("TEXT : ", et.text)
輸出結(jié)果為:
TAG : html
TAG : html
ATTRIB : {}
ATTRIB : {}
TEXT : None
TEXT : None
也就是說(shuō),et變量實(shí)際上是這個(gè)html頁(yè)面的根節(jié)點(diǎn)。若執(zhí)行
print(et)
輸出的是
<Element html at 0x1a5b5135b40>
也就是html,也即該頁(yè)面的最外層:

測(cè)試代碼:
for i in range(len(et)):
print(et[i].tag)
輸出結(jié)果為:
head
body
觀察頁(yè)面的結(jié)構(gòu),發(fā)現(xiàn)html下兩個(gè)同胞節(jié)點(diǎn)為head 和body。如圖:

獲取各個(gè)子節(jié)點(diǎn)的信息,以類(lèi)似于多維數(shù)組的方式,通過(guò)代碼實(shí)現(xiàn)遍歷:
for i in range(len(et)):
print(et[i].tag)
for j in range(len(et[i])):
print(" "+et[i][j].tag)
輸出結(jié)果為:
head
title
body
p
p
p
與頁(yè)面標(biāo)簽結(jié)構(gòu)相符。這樣就基本理解了etree.HTML(html_doc)語(yǔ)句所生成對(duì)象的結(jié)構(gòu)。
下面來(lái)做幾件爬蟲(chóng)可能需要的工作。
- 檢查元素是都有子元素
- 檢查節(jié)點(diǎn)是都為一個(gè)elements
- 檢查一個(gè)元素是否有父元素
- 檢查同胞元素
- 尋找元素
在python代碼中依次做這些測(cè)試。比較簡(jiǎn)單,基本方法總結(jié)見(jiàn)表:
| 功能 | 方法 |
|---|---|
| 檢查是否存在子元素 | len(et)>0 |
| 檢查是否為element | etree.iselement(et[i])) |
| 檢查是否存在父元素 | getparent() 注意:是方法 不是getparent屬性! |
| 檢查同胞元素 | getprevious()和getnext()方法 |
| 尋找元素 | find()方法 |
代碼比較簡(jiǎn)單,測(cè)試結(jié)果寫(xiě)在注釋中:
# 1.檢查元素是都有子元素 ——檢查長(zhǎng)度
# 1.1 檢查根節(jié)點(diǎn)是否有子元素
if len(et) > 0:
print("根節(jié)點(diǎn)有%s個(gè)子元素" % len(et))
else:
print("根節(jié)點(diǎn)沒(méi)有子元素!")
# 1.2檢查根節(jié)點(diǎn)的子節(jié)點(diǎn)是否有子元素
for i in range(len(et)):
if len(et[i]) > 0:
print("%s標(biāo)簽有子節(jié)點(diǎn),第一個(gè)子節(jié)點(diǎn)為:%s" % (et[i].tag, et[i][0].tag))
else:
print("%s標(biāo)簽沒(méi)有子節(jié)點(diǎn)!" % et[i].tag)
# 此部分的輸出結(jié)果為:
# 根節(jié)點(diǎn)有2個(gè)子元素
# head標(biāo)簽有子節(jié)點(diǎn),第一個(gè)子節(jié)點(diǎn)為:title
# body標(biāo)簽有子節(jié)點(diǎn),第一個(gè)子節(jié)點(diǎn)為:p
# 2.檢查是否為elements etree.iselement(et[i]))
for i in range(len(et)):
print("%s是否為element : %s" % (et[i].tag, etree.iselement(et[i])))
# 此部分輸出結(jié)果為:
# head是否為element : True
# body是否為element : True
# 3.檢查是否存在父元素——getparent() 注意:是方法 不是getparent屬性!
print(et.getparent())
for i in range(len(et)):
if et[i].getparent():
print("%s存在父元素!" % et[i].tag)
else:
print("%s不存在父元素!" % et[i].tag)
# 此部分輸出結(jié)果:
# None
# head存在父元素!
# body存在父元素!
# 4.檢查同胞——getprevious()和getnext()方法
# 代碼略
# 5.尋找元素
print(et[1].find('p'))
print(et[1].find('p').tag)
# 此部分輸出結(jié)果:
# <Element p at 0x1b688db5c40>
# p
2.3 lxml的xpath方法
取出所有的a標(biāo)簽中的href鏈接:et.xpath('//a/@href')
x = et.xpath('//a/@href')
for i in range(len(x)):
print(x[i])
輸出結(jié)果為:
http://example.com/elsie
http://example.com/lacie
http://example.com/tillie