解析庫就是在爬蟲時自己制定一個規則,幫助我們抓取想要的內容時用的。常用的解析庫有re模塊的正則、beautifulsoup、pyquery等等。正則完全可以幫我們匹配到我們想要住區的內容,但正則比較麻煩,所以這里我們會用beautifulsoup。
beautifulsoup
Beautiful Soup 是一個可以從HTML或XML文件中提取數據的Python庫。它能夠通過你喜歡的轉換器實現慣用的文檔導航、查找、修改文檔的方式。Beautiful Soup會幫我們節省數小時甚至數天的工作時間。Beautiful Soup 3 目前已經停止開發,官網推薦在現在的項目中使用Beautiful Soup 4。
安裝:
pip install beautifulsoup4
Beautiful Soup支持Python標准庫中的HTML解析器,還支持一些第三方的解析器。其中一個是 lxml 。我們平常在使用中推薦使用lxml。另一個可供選擇的解析器是純Python實現的 html5lib , html5lib的解析方式與瀏覽器相同,
pip install lxml pip install html5lib
下表列出了主要的解析器,以及它們的優缺點,官網推薦使用lxml作為解析器,因為效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必須安裝lxml或html5lib, 因為那些Python版本的標准庫中內置的HTML解析方法不夠穩定.
解析器 | 使用方法 | 優勢 | 劣勢 |
---|---|---|---|
Python標准庫 | BeautifulSoup(markup, "html.parser") |
|
|
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") |
|
|
lxml XML 解析器 | BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml") |
|
|
html5lib | BeautifulSoup(markup, "html5lib") |
|
|
中文文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
基本使用
容錯處理:BeautifulSoup文檔的容錯能力指的是在html代碼不完整的情況下,使用該模塊可以識別該錯誤。使用BeautifulSoup解析某些沒寫完整標簽的代碼會自動補全該閉合標簽,得到一個 BeautifulSoup 的對象,並能按照標准的縮進格式的結構輸出。
舉個栗子:

html_doc = """ <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 href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #具有容錯功能,第二個參數是解析器名,這里我們確定用lxml res=soup.prettify() #處理好縮進,結構化顯示 print(res)
遍歷文檔樹操作
遍歷文檔樹:即直接通過標簽名字選擇,特點是選擇速度快,但如果存在多個相同的標簽則只返回第一個
-
1、用法
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.p) #存在多個相同的標簽則只返回第一個
- 2、獲取標簽的名稱 ====> soup.p.name
- 3、獲取標簽的屬性 ====> soup.p.attrs
- 4、獲取標簽的內容 ====> soup.p.string #p下的文本只有一個時,取到,否則為None
- 5、嵌套選擇 ====> soup.body.a.string
- 6、子節點、子孫節點 ====> soup.p.contents soup.p.descendants
- 7、父節點、祖先節點 ====> soup.a.parent soup.a.parents
- 8、兄弟節點 ====>
soup.a.next_sibling #下一個兄弟 soup.a.previous_sibling#上一個兄弟 list(soup.a.next_siblings) #下面的兄弟們=>生成器對象 soup.a.previous_siblings)#上面的兄弟們=>生成器對象
具體操作示例:

#遍歷文檔樹:即直接通過標簽名字選擇,特點是選擇速度快,但如果存在多個相同的標簽則只返回第一個 html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ #1、用法 from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') # soup=BeautifulSoup(open('a.html'),'lxml')#打開一個HTML文件 print(soup.p) #<p class="title" id="my p"><b class="boldest" id="bbb">The Dormouse's story</b></p> # 即使存在多個相同的標簽也只返回第一個 #2、獲取標簽的名稱 print(soup.p.name)#p #3、獲取標簽的屬性 print(soup.p.attrs)#{'id': 'my p', 'class': ['title']} #4、獲取標簽的內容 print(soup.p.string) #The Dormouse's story p標簽中的文本只有一個時,取到,否則為None print(soup.p.strings) #拿到一個生成器對象, 取到p下所有的文本內容 print(soup.p.text) #取到p下所有的文本內容 for line in soup.stripped_strings: #去掉空白 print(line) """ The Dormouse's story The Dormouse's story Once upon a time there were three little sisters; and their names were Elsie , Lacie and Tillie ; they lived at the bottom of a well. ... """ ''' 如果tag包含了多個子節點,tag就無法確定 .string 方法應該調用哪個子節點的內容, .string 的輸出結果是 None,如果只有一個子節點那么就輸出該子節點的文本,比如下面的這種結構,soup.p.string 返回為None,但soup.p.strings就可以找到所有文本 <p id='list-1'> 哈哈哈哈 <a class='sss'> <span> <h1>aaaa</h1> </span> </a> <b>bbbbb</b> </p> ''' #5、嵌套選擇 print(soup.head.title.string)#The Dormouse's story print(soup.body.a.string)#Elsie #6、子節點、子孫節點 print(soup.p.contents) # [<b class="boldest" id="bbb">The Dormouse's story</b>] p下所有子節點 print(soup.p.children) #得到一個迭代器,包含p下所有子節點 for i,child in enumerate(soup.p.children): print(i,child) """ 0 <b class="boldest" id="bbb">The Dormouse's story</b> <generator object descendants at 0x0000005FE37D3150> 0 <b class="boldest" id="bbb">The Dormouse's story</b> 1 The Dormouse's story """ print(soup.p.descendants) #獲取子孫節點,p下所有的標簽都會選擇出來,返回一個對象 for i,child in enumerate(soup.p.descendants): print(i,child) """ 0 <b class="boldest" id="bbb">The Dormouse's story</b> 1 The Dormouse's story """ #7、父節點、祖先節點 print('dddddd',soup.a.parent) #獲取a標簽的父節點 """ <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> """ print(soup.a.parents) #找到a標簽所有的祖先節點,父親的父親,父親的父親的父親...返回一個對象 #8、兄弟節點 print(soup.a.next_sibling) #下一個兄弟 print(soup.a.previous_sibling) #上一個兄弟 print(list(soup.a.next_siblings)) #下面的兄弟們=>生成器對象 print(soup.a.previous_siblings) #上面的兄弟們=>生成器對象
搜索文檔樹操作
搜索文檔樹的方法主要是運用過濾器、find、CSS選擇器等等,這里要注意find和find_all的區別。
- 過濾器的篩選功能相對較弱,但速度較快
- find和find_all是平常用的比較多的方法
- 前端的CSS游刃有余的前端大牛可以選擇使用CSS選擇器
五種過濾器
過濾器即用一種方法來獲得我們爬蟲想要抓取的內容。這里有5種過濾器,分別是字符串、正則、列表、True和自定義方法。下面我們進行詳述
設我們從網站得到了這樣一段html
html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b> </p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """
一、字符串過濾器
字符串過濾器是依靠標簽名進行過濾的
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #這里我們用了find_all,find_all是找到所有的結果,以列表的形式返回。之后會做詳述 print(soup.find_all('b'))#[<b class="boldest" id="bbb">The Dormouse's story</b>] print(soup.find_all('a')) """ [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] """
二、正則表達式
正則表達在任何地方都適用,只要導入re模塊就可以使用正則
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') import re print(soup.find_all(re.compile('^b')))#找到所有b開頭的標簽,結果是找到了body標簽和b標簽。他會將整個標簽包含標簽內容都返回 """ [<body> <p class="title" id="my p"><b class="boldest" id="bbb">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" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> </body>, <b class="boldest" id="bbb">The Dormouse's story</b>] """
三、列表過濾器
列表過濾器的方法是將字符串過濾器中的參數由字符串變成列表,列表里面還是以字符串的形式進行過濾。列表中包含多個字符串,就會從文檔中找到所有符合規則的並返回
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all(['a','b']))#找到文檔中所有的a標簽和b標簽 """ ['<b class="boldest" id="bbb">The Dormouse's story</b>, <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>'] """
四、True過濾器
True過濾器其實是一種范圍很大的過濾器,它的用法是只要滿足某種條件都可以
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all(name=True))#只要是個標簽就ok print(soup.find_all(attrs={"id":True}))#找到所有含有id屬性的標簽 print(soup.find_all(name='p',attrs={"id":True}))#找到所有含有id屬性的p標簽 #找到所有標簽並返回其標簽名 for tag in soup.find_all(True): print(tag.name)
五、自定義方法
自定義方法即自定義的過濾器,有的時候我們沒有合適的過濾器時就可以寫一個函數作為自定義的過濾器,該函數的參數只能是一個。
自定義函數的方法一般不常用,但我們得知道有這個方法,在特殊的情況下我們會用到。
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #自定義函數,找到所有有class屬性但沒有id屬性的p標簽 def has_class_but_no_id(tag): res = (tag.name == 'p' and tag.has_attr("class") and not tag.has_attr('id')) return res print(soup.find_all(has_class_but_no_id))
find和find_all
find()方法和find_all()方法的用法是一樣的,只不過他們搜尋的方式和返回值不一樣
===>find()方法是找到文檔中符合條件的第一個元素,直接返回該結果。元素不存在時返回None
===>find_all()方法是找到文檔中所有符合條件的元素,以列表的形式返回。元素不存在時返回空列表
find( name , attrs={} , recursive=True , text=None , **kwargs ) find_all( name , attrs={} , recursive=True , text=None , limit=None , **kwargs ) #find_all比 find多一個參數:limit,下面會提到
下面我們就來詳細說一下這五個參數
一、name參數
name即標簽名,搜索name的過濾器可以是上述5中過濾器的任何一種
from bs4 import BeautifulSoup import re soup=BeautifulSoup(html_doc,'lxml') #2.1、name: 搜索name參數的值可以使任一類型的過濾器 ,字符串,正則表達式,列表,方法或是 True . print(soup.find_all(name=re.compile('^t')))#[<title>The Dormouse's story</title>] print(soup.find(name=re.compile('^t')))#<title>The Dormouse's story</title>
二、attr參數
attr就是標簽的屬性,所以該查找方式就是靠屬性進行過濾,過濾器也可以是任意一種
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all('p',attrs={'class':'story'}))#所有class屬性中有story的p標簽組成的列表,好長的說。。 print(soup.find('p',attrs={'class':'story'}))#第一個符合條件的p標簽
三、recursive參數
recursive參數默認為True,指的是在搜索某標簽時會自動檢索當前標簽的所有子孫節點。若只想搜索直接子節點,不要孫節點,可以將該參數改為false
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.html.find_all('a'))#列表你懂的 print(soup.html.find('a'))#<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> print(soup.html.find_all('a',recursive=False))#[] print(soup.html.find('a',recursive=False))#None
四、text參數
text即文本,也就是按文本內容搜索。text參數一般不做單獨使用,都是配合着name或者attr用的,作用是進一步縮小搜索的范圍
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #找到文本Elsie,單獨使用沒什么意義,一般配合前面兩個參數使用 print(soup.find_all(text='Elsie'))#['Elsie'] print(soup.find(text='Elsie'))#'Elsie' #找到文本是Elsie的a標簽 print(soup.find_all('a',text='Elsie'))#[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>] print(soup.find('a',text='Elsie'))#<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
五、**kwargs
鍵值對形式的搜索條件,鍵是name或者某個屬性,值是過濾器的形式。支持除自定義形式意外的4種過濾器
from bs4 import BeautifulSoup import re soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all(id=re.compile('my')))#[<p class="title" id="my p"><b class="boldest" id="bbb">The Dormouse's story</b></p>] print(soup.find_all(href=re.compile('lacie'),id=re.compile('\d')))#[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>] print(soup.find_all(id=True)) #查找有id屬性的標簽 ###注意!!!按照類名查找時關鍵字是class_,class_=value,value可以是五種過濾器 print(soup.find_all('a',class_='sister')) #查找類為sister的a標簽 print(soup.find_all('a',class_='sister ssss')) #查找類為sister和sss的a標簽,順序錯誤也匹配不成功 print(soup.find_all(class_=re.compile('^sis'))) #查找類為sister的所有標簽
注:有些特殊的標簽名不能用鍵值對的形式搜索,但支持屬性attr的方式搜索。比如HTML5中的data-****標簽
res = BeautifulSoup('<div data-foo="value">foo!</div>','lxml') #print(res.find_all(data-foo="value"))#報錯:SyntaxError: keyword can't be an expression # 但是可以通過 find_all() 方法的 attrs 參數定義一個字典參數來搜索包含特殊屬性的tag: print(data_soup.find_all(attrs={"data-foo": "value"}))# [<div data-foo="value">foo!</div>]
六、limit參數
limit是限制的意思,如果文檔特別大而我們又不需要所有符合條件的結果的時候會導致搜索很慢。比如我們只要符合條件的前3個a標簽,而文檔中包含200個a標簽,這種情況我們就可以用到limit參數限制返回的結果的數量,效果與SQL中的limit類似。
find_all()中有limit參數而find()中沒有的原因是因為find()本身就只返回第一個結果,不存在限制的條件。
from bs4 import BeautifulSoup import re soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all('a',limit=3))
擴展:
find()和find_all()幾乎是Beautiful Soup中最常用的方法,所以他們具有自己的簡寫方法
from bs4 import BeautifulSoup import re soup=BeautifulSoup(html_doc,'lxml') soup.find_all("a") soup("a")#find_all方法的簡寫版本 soup.find("head").find("title")# <title>The Dormouse's story</title> soup.head.title#find方法的簡寫版本 soup.title.find_all(text=True)#簡寫了find的版本 soup.title(text=True)#find和find_all均簡寫了的版本
CSS選擇器
CSS選擇器的使用方法與CSS定位標簽的方式相似。精髓就是.class 和 #id
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #1、CSS選擇器 print(soup.p.select('.sister')) print(soup.select('.sister span')) print(soup.select('#link1')) print(soup.select('#link1 span')) print(soup.select('#list-2 .element.xxx')) print(soup.select('#list-2')[0].select('.element')) #可以一直select,但其實沒必要,select支持鏈式操作,所以一條select就可以了 # 2、獲取屬性 print(soup.select('#list-2 h1')[0].attrs) # 3、獲取內容 print(soup.select('#list-2 h1')[0].get_text())
當然,BeautifulSoup是一個很成熟的大模塊,不會只具有這幾種方法,但其實上述方法在爬蟲中使用已經游刃有余了。
BeautifulSoup不僅可以搜索文檔樹,還能修改文檔樹,但在爬蟲中用不到修改的功能,所以這里我們就不贅述了。
詳情可研究官網 https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html