在了解爬蟲基礎、請求庫和正則匹配庫以及一個具體豆瓣電影爬蟲實例之后,可能大家還對超長的正則表達式記憶猶新,設想如果想要匹配的條目更加多那表達式長度將會更加恐怖,這顯然不是我們想要的,因此本文介紹的解析庫可以幫助我們更加輕松地提取到特定信息。
一、Xpath庫
1.庫簡介
XPath(XML Path Language)即XML 路徑語言,它是一門在XML文檔中查找信息的語言,但它同樣適用于html 文檔的搜索。所以在做爬蟲時,我們完全可以使用XPath 來做相應的信息抽取。
2.入門測試
需要導入lxml庫(若未安裝推薦用pip install lxml安裝即可),然后使用下面代碼進行簡單測試:
from lxml import etree
text = '''<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first</a></li>
<li class="item-1"><a href="link2.html">second</a>
<li class="item-2"><a href="link3.html">third</a></li>
<li class="item-3"><a href="link4.html">fourth</a></li>
</ul>
</div>'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result.decode('utf-8'))
結果如下:可以看到,etree模塊不僅將缺少的標簽閉合了,而且還加上了html、body節點。
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first</a></li>
<li class="item-1"><a href="link2.html">second</a>
</li><li class="item-2"><a href="link3.html">third</a></li>
<li class="item-3"><a href="link4.html">fourth</a></li>
</ul>
</div></body></html>
3.基本方法
xpath的常用規則及基本方法如下:
- 初始化html
上文中的入門測試即為初始化html。其中etree.parse()是初始化html構造一個XPath解析對象;etree.tostring()是修復html文件中代碼,把缺的頭或尾節點補齊;result.deode('utf-8')修復后的HTML代碼是字節類型,轉化成字符串。
- 獲取所有節點
print(html.xpath('//*')) # 獲取所有的節點
print(html.xpath('//li')) # 獲取所有li節點
- 子節點、子孫節點
print(html.xpath('//li/a')) # 所有li下是所有直接a子節點
print(html.xpath('//ul//a')) # 所有ul下的子孫a節點
- 父節點
# 找到所有a節點中href為links.html的父節點的class值
# .. 來實現查找父節點
print(html.xpath('//a[@href="link1.html"]/../@class'))
- 屬性匹配
# 找到class值為item-0是節點
print(html.xpath('//li[@class="item-0"]'))
- 文本獲取
# 匹配到class值為item-0節點中的a標簽中的文本
print(html.xpath('//li[@class="item-0"]/a/text()'))
- 屬性獲取
print(html.xpath('//li/a/@href')) # 找到li下a中的href屬性值
- 屬性多值匹配
#只要節點屬性class中包含item就能匹配出來
print(html.xpath('//li[contains(@class,"item")]/a/text()'))
二、BeautifulSoup庫
1.庫簡介
BeautifulSoup4和 lxml 一樣,Beautiful Soup 也是一個HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 數據。BeautifulSoup支持Python/ target=_blank class=infotextkey>Python標準庫中的HTML解析器,還支持一些第三方的解析器,如果我們不安裝它,則 Python 會使用 Python默認的解析器,lxml 解析器更加強大,速度更快,推薦使用lxml 解析器。
2.入門測試
假設有這樣一個Html(即從百度網頁源代碼截取一段),具體內容如下:
html = '''
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="content-type" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta content="always" name="referrer" />
<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css" />
<title>百度一下,你就知道 </title>
</head>
<body link="#0000cc">
<div id="wrApper">
<div id="head">
<div class="head_wrapper">
<div id="u1">
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新聞 </a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123 </a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地圖 </a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">視頻 </a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">貼吧 </a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多產品 </a>
</div>
</div>
</div>
</div>
</body>
</html>'''
如果將字符串單獨保存為html文件,則使用谷歌瀏覽器打開后即為:
通過導入bs4庫中的BeautifulSoup子類可以輸入以下命令觀察輸出:
from bs4 import BeautifulSoup
bs = BeautifulSoup(html,"html.parser") # 縮進格式
print(bs.prettify()) # 獲取title標簽的所有內容
print(bs.title) # 獲取title標簽的名稱
print(bs.title.name) # 獲取title標簽的文本內容
print(bs.title.string) # 獲取head標簽的所有內容
print(bs.head) # 獲取第一個div標簽中的所有內容
print(bs.div) # 獲取第一個div標簽的id的值
print(bs.div["id"]) # 獲取第一個a標簽中的所有內容
print(bs.a) # 獲取所有的a標簽中的所有內容
print(bs.find_all("a")) # 獲取id="u1"
print(bs.find(id="u1")) # 獲取所有的a標簽,并遍歷打印a標簽中的href的值
for item in bs.find_all("a"):
print(item.get("href")) # 獲取所有的a標簽,并遍歷打印a標簽的文本值
for item in bs.find_all("a"):
print(item.get_text())
3.基本方法
BeautifulSoup4將復雜HTML文檔轉換成一個復雜的樹形結構,每個節點都是Python對象,所有對象可以歸納為4種:
- Tag:Tag通俗點講就是HTML中的一個個標簽,我們可以利用 soup 加標簽名輕松地獲取這些標簽的內容,這些對象的類型是bs4.element.Tag。但是注意,它查找的是在所有內容中的第一個符合要求的標簽。
# [document] #bs 對象本身比較特殊,它的 name 即為 [document]
print(bs.name)
# head #對于其他內部標簽,輸出的值便為標簽本身的名稱
print(bs.head.name)
# 在這里,我們把 a 標簽的所有屬性打印輸出了出來,得到的類型是一個字典。
print(bs.a.attrs)
#還可以利用get方法,傳入屬性的名稱,二者是等價的
print(bs.a['class']) # 等價 bs.a.get('class')
# 可以對這些屬性和內容等等進行修改
bs.a['class'] = "newClass"
print(bs.a)
# 還可以對這個屬性進行刪除
del bs.a['class']
print(bs.a)
- NavigableString:既然我們已經得到了標簽的內容,那么問題來了,我們要想獲取標簽內部的文字怎么辦呢?很簡單,用 .string 即可,例如:
print(bs.title.string)
print(type(bs.title.string))
- BeautifulSoup:BeautifulSoup對象表示的是一個文檔的內容。大部分時候,可以把它當作 Tag 對象,是一個特殊的 Tag,我們可以分別獲取它的類型,名稱,以及屬性,例如:
print(type(bs.name))
print(bs.name)
print(bs.attrs)
- Comment:Comment 對象是一個特殊類型的 NavigableString 對象,其輸出的內容不包括注釋符號。
print(bs.a)
# 此時不能出現空格和換行符,a標簽如下:
# <a class="mnav" href="http://news.baidu.com" name="tj_trnews"><!--新聞--></a>
print(bs.a.string) # 新聞
print(type(bs.a.string)) # <class 'bs4.element.Comment'
接下來具體講解BeautifulSoup的使用方法。我們可以通過BeautifulSoup遍歷文檔樹:
- .contents:獲取Tag的所有子節點,返回一個list
# tag的.content 屬性可以將tag的子節點以列表的方式輸出
print(bs.head.contents)
# 用列表索引來獲取它的某一個元素
print(bs.head.contents[1])
- .children:獲取Tag的所有子節點,返回一個生成器
for child in bs.body.children:
print(child)
- .descendants:獲取Tag的所有子孫節點
- .strings:如果Tag包含多個字符串,即在子孫節點中有內容,可以用此獲取,而后進行遍歷
- .stripped_strings:與strings用法一致,只不過可以去除掉那些多余的空白內容
- .parent:獲取Tag的父節點
- .parents:遞歸得到父輩元素的所有節點,返回一個生成器
- .previous_sibling:獲取當前Tag的上一個節點,屬性通常是字符串或空白,真實結果是當前標簽與上一個標簽之間的頓號和換行符
- .next_sibling:獲取當前Tag的下一個節點,屬性通常是字符串或空白,真是結果是當前標簽與下一個標簽之間的頓號與換行符
- .previous_siblings:獲取當前Tag的上面所有的兄弟節點,返回一個生成器
- .next_siblings:獲取當前Tag的下面所有的兄弟節點,返回一個生成器
- .previous_element:獲取解析過程中上一個被解析的對象(字符串或tag),可能與previous_sibling相同,但通常是不一樣的
- .next_element:獲取解析過程中下一個被解析的對象(字符串或tag),可能與next_sibling相同,但通常是不一樣的
- .previous_elements:返回一個生成器,可以向前訪問文檔的解析內容
- .next_elements:返回一個生成器,可以向后訪問文檔的解析內容
- .has_attr:判斷Tag是否包含屬性
也可以通過BeautifulSoup進行搜索文檔樹:
- find_all(name, attrs, recursive, text, **kwargs)
在上面的示例中我們簡單介紹了find_all的使用,接下來介紹一下find_all的更多用法-過濾器。這些過濾器貫穿整個搜索API,過濾器可以被用在tag的name中,節點的屬性等。
from bs4 import BeautifulSoup
import re
bs = BeautifulSoup(html,"html.parser")
t_list = bs.find_all(re.compile("a"))
for item in t_list:
print(item)
- find()
find()將返回符合條件的第一個Tag,有時我們只需要或一個Tag時,我們就可以用到find()方法了。當然了,也可以使用find_all()方法,傳入一個limit=1,然后再取出第一個值也是可以的,不過未免繁瑣。
from bs4 import BeautifulSoup
import re
# 返回只有一個結果的列表
t_list = bs.find_all("title",limit=1)
print(t_list)
# 返回唯一值
t = bs.find("title")
print(t)
# 如果沒有找到,則返回None
t = bs.find("abc")
print(t)
- CSS選擇器
BeautifulSoup支持發部分的CSS選擇器,在Tag獲取BeautifulSoup對象的.select()方法中傳入字符串參數,即可使用CSS選擇器的語法找到Tag:
- 通過標簽名查找
print(bs.select('title'))
print(bs.select('a'))
- 通過類名查找
print(bs.select('.mnav'))
- 通過id查找
print(bs.select('#u1'))
- 組合查找
print(bs.select('div .bri'))
- 屬性查找
print(bs.select('a[class="bri"]'))
print(bs.select('a[href="http://tieba.baidu.com"]'))
- 直接子標簽查找
t_list = bs.select("head > title")
print(t_list)
- 兄弟節點標簽查找
t_list = bs.select(".mnav ~ .bri")
print(t_list)
- 獲取內容
t_list = bs.select("title")
print(bs.select('title')[0].get_text())
三、pyquery庫
1.庫簡介
如果覺得Xpath有些難懂,BeautifulSoup有些難記,那pyquery一定適合你。pyquery 可讓你用 jQuery 的語法來對 xml 進行操作。這和 jQuery 十分類似。如果利用 lxml,pyquery 對 xml 和 html 的處理將更快。這個庫不是一個可以和 JAVAScript交互的代碼庫,它只是非常像 jQuery API 而已。
2.入門測試
# html為上文BeautifulSoup測試實例
from pyquery import PyQuery as pq
doc = pq(html)
print(doc('title').text()) # '標題'
print(doc('div').filter('.head_wrapper').text()) # '文字1'
print(doc('a[name=tj_trnews]').text()) # 同上,只是這種方法支持除了id和class之外的屬性篩選
print(doc('div').filter('#u1').find('a').text()) # '列表1第1項 列表1第2項'
print(doc('div#u1 a').text()) # 簡化形式
print(doc('div#u1 > a').text()) # 節點之間用>連接也可以,但是加>只能查找子元素,空格子孫元素
3.基本方法
- 初始化
# 字符串初始化:
from pyquery import PyQuery as pq
doc=pq(html)
print(doc('li'))
# URL初始化
doc=pq(url="https://ww.baidu.com")
print(doc)
a = open('test.html','r',encoding='utf8')
doc=pq(a.read())
print(doc)
- 基本css選擇器
# id 為container,class為list下的所有li
print(doc('.head_wrapper #u1 a'))
- 查找節點
# .find():查找所有子孫節點
items = doc('#u1')
print(items.find('a'))
# .children():查找子節點
items=doc('#u1')
print(items.children('.mnav'))
# 父節點
doc=pq(html)
items=doc('.mnav')
print(items.parent())
print(items.parents())
# 兄弟節點
doc=pq(html)
li=doc('.mnav')
print(li.siblings('.bri'))
- 遍歷
# 用items()函數生成列表生成器進行遍歷
doc=pq(html)
lis=doc('a').items()
for li in lis:
print(li)
- 獲取信息
# 獲取屬性
a=doc('.head_wrapper #u1 .bri')
# attr只會輸出第一個a節點屬性,要用items()遍歷
print(a.attr('href'))
# 獲取文本
# .text()
a=doc('.head_wrapper #u1 .bri')
# text()函數會輸出所有的li文本內容
print(a.text())
# .html()
li=doc('a')
# html()只會輸出第一個li節點內的HTML文本
print(li.html())
- 節點操作
# removeClass addClass
a=doc('.head_wrapper #u1 .bri')
print(a)
a.removeClass('bri') # 移除active的class
print(a)
a.addClass('bri') # 增加active的class
print(a)
# attr text html
a.attr('name','link') # 增加屬性name=link
a.text('changed item') # 改變文本 changed item
a.html('<span>changed item </span>') # 改變HTML
print(a)
# remove()
u1=doc('#u1')
# 刪除wrap中p節點
u1.find('a').remove()
print(u1.text())
Python有關Xpath、BeautifulSoup、pyquery三大解析庫的基本使用方法介紹至此結束