Python爬虫技术系列-02HTML解析-BS4
Python爬蟲技術系列-02HTML解析-BS4
- 2 Beautiful Soup解析
 - 2.1 Beautiful Soup概述
 - 2.1.1 Beautiful Soup安裝
 - 2.1.2 Beautiful Soup4庫內置對象
 
- 2.2 BS4 案例
 - 2.2.1 讀取HTML案例
 - 2.2.2 BS4常用語法
 - 1Tag節點
 - 2 遍歷節點
 - 3 搜索方法
 - 1) find_all()
 - 2)find()
 - 3) CSS選擇器
 
- 2.3 BS4綜合案例
 - 2.3.1 需求:爬取三國演義小說的所有章節和內容
 - 2.3.2 爬取小說數據,并排錯
 
2 Beautiful Soup解析
參考連接:
 https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/#
 http://c.biancheng.net/python_spider/bs4.html
2.1 Beautiful Soup概述
2.1.1 Beautiful Soup安裝
Beautiful Soup 簡稱 BS4(其中 4 表示版本號)是一個 Python 第三方庫,它可以從 HTML 或 XML 文檔中快速地提取指定的數據。Beautiful Soup 語法簡單,使用方便,并且容易理解,因此您可以快速地學習并掌握它。本節我們講解 BS4 的基本語法。
 BS4下載安裝
 由于 Bautiful Soup 是第三方庫,因此需要單獨下載,下載方式非常簡單,執行以下命令即可安裝:
由于 BS4 解析頁面時需要依賴文檔解析器,所以還需要安裝 lxml 作為解析庫:
pip install lxml2.1.2 Beautiful Soup4庫內置對象
Beautiful Soup4庫的內置對象:
 Beautiful Soup將復雜HTML文檔轉換成一個復雜的樹形結構,每個節點都是Python對象,對象可以歸納為BeautifulSoup ,Tag , NavigableString , Comment 四種。
 BeautifulSoup 對象為一個文檔的全部內容,可以認為BeautifulSoup 對象是一個大的Tag對象。
 Tag對象與XML或HTML原生文檔中的tag相同。代表html文檔中的標簽,Tag對象可以包含其他多個Tag對象。Tag.name返回標簽名,Tag.string返回標簽中的文本。
 NavigableString對象html文檔中的文本,即Tag中的字符串用NavigableString對象包裝。
 Commern對象是一種特殊的NavigableString對象,用來包裝文檔中注釋和特殊字符串。
2.2 BS4 案例
2.2.1 讀取HTML案例
1.創建 BS4 解析對象第一步,這非常地簡單,語法格式如下所示:
#導入解析包 from bs4 import BeautifulSoup #創建beautifulsoup解析對象 soup = BeautifulSoup(html_doc, 'html.parser')上述代碼中,html_doc 表示要解析的文檔,而 html.parser 表示解析文檔時所用的解析器,此處的解析器也可以是 ‘lxml’ 或者 ‘html5lib’,示例代碼如下所示:
# 第一步 導入依賴庫 from bs4 import BeautifulSoup#coding:utf8 html_doc = """ <html><head><title>"bs4測試"</title></head> <body> <div><span class="cla01">標簽文本</span>div中文本<!--注釋代碼--></div> """# 第二步,加載數據為BeautifulSoup對象: soup = BeautifulSoup(html_doc, 'html.parser') #prettify()用于格式化輸出html/xml文檔 print(soup.prettify())# 第三步,獲取文檔中各個元素: # 利用soup.find('div')獲取div標簽 tag_node = soup.find('div') print(type(tag_node),'\t:',tag_node)# 遍歷div標簽對象,獲取其中的各個對象 for item in tag_node:print(type(item),'\t:',item)輸出結果:
<html><head><title>"bs4測試"</title></head><body><div><span class="cla01">標簽文本</span>div中文本<!--注釋代碼--></div></body> </html> <class 'bs4.element.Tag'> : <div><span class="cla01">標簽文本</span>div中文本<!--注釋代碼--></div> <class 'bs4.element.Tag'> : <span class="cla01">標簽文本</span> <class 'bs4.element.NavigableString'> : div中文本 <class 'bs4.element.Comment'> : 注釋代碼從結果可以看出soup.find(‘div’)返回值為Tag類型,輸出結果為該標簽的全部內容。
 for循環中print(type(item),‘\t:’,item)會輸出div標簽的所有各個對象,該div標簽包含的對象如下:
 一個Tag對象,值為標簽文本;
 一個NavigableString’文本對象,值為div中文本;
 一個Comment’注釋對象,值為注釋代碼。
外部文檔可以通過 open() 的方式打開讀取,語法格式如下:
soup = BeautifulSoup(open('html_doc.html', encoding='utf8'), 'lxml')2.2.2 BS4常用語法
下面對爬蟲中經常用到的 BS4 解析方法做詳細介紹。
Beautiful Soup 將 HTML 文檔轉換成一個樹形結構,該結構有利于快速地遍歷和搜索 HTML 文檔。下面使用樹狀結構來描述一段 HTML 文檔:
<html><head><title>網頁標題</title></head><h1>www.baidu.com</h1><p><b>搜索引擎</b></p></body></html>樹狀圖如下所示:
 
1Tag節點
# 標簽(Tag)是組成 HTML 文檔的基本元素。在 BS4 中,通過標簽名和標簽屬性可以提取出想要的內容。看一組簡單的示例: # 純文本復制 from bs4 import BeautifulSoup html_doc = '<div><p class="Web site url"><span>www.baidu.com</span></p></div>' soup = BeautifulSoup(html_doc, 'html.parser') #獲取整個div標簽的html代碼 print(soup.div) #獲取span標簽 print(soup.div.p.span) #獲取p標簽內容,使用NavigableString類中的string、text、get_text() print(soup.div.p.text) #返回一個字典,里面是多有屬性和值 print(soup.div.p.attrs) #查看返回的數據類型 print(type(soup.div.p)) #根據屬性,獲取標簽的屬性值,返回值為列表 print(soup.div.p['class']) #給class屬性賦值,此時屬性值由列表轉換為字符串 soup.div.p['class']=['Web','Site'] print(soup.div.p)輸出為:
<div><p class="Web site url"><span>www.baidu.com</span></p></div> <span>www.baidu.com</span> www.baidu.com {'class': ['Web', 'site', 'url']} <class 'bs4.element.Tag'> ['Web', 'site', 'url'] <p class="Web Site"><span>www.baidu.com</span></p>2 遍歷節點
# Tag 對象提供了許多遍歷 tag 節點的屬性,比如 contents、children 用來遍歷子節點;parent 與 parents 用來遍歷父節點;而 next_sibling 與 previous_sibling 則用來遍歷兄弟節點 。示例如下: # 純文本復制 #coding:utf8 from bs4 import BeautifulSoup html_doc = ''' <!DOCTYPE html> <html> <body> <div class="useful"><ul><li class="cla-0" id="id-0"><a href="/link1">01</a></li><li class="cla-1"><a href="/link2">02</a></li><li><strong><a href="/link3">03</a></strong></li></ul> </div> </body> </html> ''' soup = BeautifulSoup(html_doc, 'html.parser') body_tag=soup.body print(body_tag) print("# 以列表的形式輸出,所有子節點") print(body_tag.contents) print(r"# Tag 的 children 屬性會生成一個可迭代對象,可以用來遍歷子節點,示例如下") for child in body_tag.children:print(child)輸出為:
<body> <div class="useful"> <ul> <li class="cla-0" id="id-0"><a href="/link1">01</a></li> <li class="cla-1"><a href="/link2">02</a></li> <li><strong><a href="/link3">03</a></strong></li> </ul> </div> </body> # 以列表的形式輸出,所有子節點 ['\n', <div class="useful"> <ul> <li class="cla-0" id="id-0"><a href="/link1">01</a></li> <li class="cla-1"><a href="/link2">02</a></li> <li><strong><a href="/link3">03</a></strong></li> </ul> </div>, '\n'] # Tag 的 children 屬性會生成一個可迭代對象,可以用來遍歷子節點,示例如下<div class="useful"> <ul> <li class="cla-0" id="id-0"><a href="/link1">01</a></li> <li class="cla-1"><a href="/link2">02</a></li> <li><strong><a href="/link3">03</a></strong></li> </ul> </div>3 搜索方法
Beautiful Soup定義了很多搜索方法,本小節著重 find_all(), find() 和 select()幾個。
 find_all()函數可以搜索當前tag的所有tag子節點,并判斷是否符合過濾器的條件。
name 參數對應tag名稱,如soup.find_all(“div”)表示查找所有div標簽。
 attrs表示屬性值過濾器。如soup.find_all(class_=“cla”)表示查找class屬性值為cla的所有元素。其它的屬性過濾器還可以為id="main"等。
 recursive為True會遞歸查詢,為False只檢索直系節點。
 text:用來搜文檔中的字符串內容,該參數可以接受字符串 、正則表達式 、列表、True。
 limit:由于 find_all() 會返回所有的搜索結果,這樣會影響執行效率,通過 limit 參數可以限制返回結果的數量
find()函數是find_all()的一種特例,僅返回一個值。
 select()函數用于通過css選擇器進行文檔的篩選。
find_all() 與 find() 是解析 HTML 文檔的常用方法,它們可以在 HTML 文檔中按照一定的條件(相當于過濾器)查找所需內容。find() 與 find_all() 的語法格式相似,希望大家在學習的時候,可以舉一反三。
 BS4 庫中定義了許多用于搜索的方法,find() 與 find_all() 是最為關鍵的兩個方法,其余方法的參數和使用與其類似。
1) find_all()
find_all() 方法用來搜索當前 tag 的所有子節點,并判斷這些節點是否符合過濾條件,find_all() 使用示例如下:
from bs4 import BeautifulSoup import re html_doc = ''' <!DOCTYPE html> <html> <body> <p class="vip">加入我們閱讀所有教程</p> <a href="https://www.baidu.com" id="link4">百度一下</a> <a href="https://www.sos.com">soso一下</a> <div class="useful"><ul><li class="cla-0" id="id-0"><a href="/link1">01</a></li><li class="cla-1"><a href="/link2">02</a></li><li><strong><a href="/link3">03</a></strong></li></ul></div> </body> </html> ''' soup = BeautifulSoup(html_doc, 'html.parser')# 查詢全部li標簽: print("---result00---") result00 = soup.find_all('li') # 查詢全部li標簽 print(result00)# 查詢符合條件的第1個標簽: print("---result01---") result01 = soup.find_all('li',limit=1) # 查詢符合條件的第1個標簽 print(result01)# 結合屬性過濾,查詢符合條件的標簽: print("---result02---") result02 = soup.find_all('li', class_="cla-0") # 結合屬性過濾,查詢符合條件的標簽 print(result02)# 結合多個屬性過濾,查詢符合條件的標簽: print("---result03---") result03 = soup.find_all('li', class_="cla-0",id="id-0") # 結合多個屬性過濾,查詢符合條件的標簽 print(result03)#列表行書查找tag標簽 print("---result04---") print(soup.find_all(['p','a']))#正則表達式匹配id屬性值 print("---result05---") print(soup.find_all('a',id=re.compile(r'.\d'))) print(soup.find_all(id=True))#True可以匹配任何值,下面代碼會查找所有tag,并返回相應的tag名稱 print("---result06---") for tag in soup.find_all(True):print(tag.name,end=" ") print(" ") #輸出所有以b開始的tag標簽 print("---result07---") for tag in soup.find_all(re.compile("^d")):print(tag.name)# BS4 為了簡化代碼,為 find_all() 提供了一種簡化寫法,如下所示: print("---result08---") #簡化前 print(soup.find_all("p")) #簡化后 print(soup("p"))輸出為:
---result00--- [<li class="cla-0" id="id-0"><a href="/link1">01</a></li>, <li class="cla-1"><a href="/link2">02</a></li>, <li><strong><a href="/link3">03</a></strong></li>] ---result01--- [<li class="cla-0" id="id-0"><a href="/link1">01</a></li>] ---result02--- [<li class="cla-0" id="id-0"><a href="/link1">01</a></li>] ---result03--- [<li class="cla-0" id="id-0"><a href="/link1">01</a></li>] ---result04--- [<p class="vip">加入我們閱讀所有教程</p>, <a href="https://www.baidu.com" id="link4">百度一下</a>, <a href="https://www.sos.com">soso一下</a>, <a href="/link1">01</a>, <a href="/link2">02</a>, <a href="/link3">03</a>] ---result05--- [<a href="https://www.baidu.com" id="link4">百度一下</a>] [<a href="https://www.baidu.com" id="link4">百度一下</a>, <li class="cla-0" id="id-0"><a href="/link1">01</a></li>] ---result06--- html body p a a div ul li a li a li strong a ---result07--- div ---result08--- [<p class="vip">加入我們閱讀所有教程</p>] [<p class="vip">加入我們閱讀所有教程</p>]2)find()
ind() 方法與 find_all() 類似,不同之處在于 find_all() 會將文檔中所有符合條件的結果返回,而 find() 僅返回一個符合條件的結果,所以 find() 方法沒有limit參數。使用示例如下:
from bs4 import BeautifulSoup import re html_doc = ''' <!DOCTYPE html> <html> <body> <p class="vip">加入我們閱讀所有教程</p> <a href="https://www.baidu.com" id="link4">百度一下</a> <a href="https://www.sos.com">soso一下</a> <div class="useful"><ul><li class="cla-0" id="id-0"><a href="/link1">01</a></li><li class="cla-1"><a href="/link2">02</a></li><li><strong><a href="/link3">03</a></strong></li></ul></div> </body> </html> ''' soup = BeautifulSoup(html_doc, 'html.parser')print("---result101---") result101 = soup.find('li') # 查詢單個標簽,與find_all("li", limit=1)一致 # 從結果可以看出,返回值為單個標簽,并且沒有被列表所包裝。 print(result101)print("---result102---") #根據屬性值正則匹配 print(soup.find(class_=re.compile('0'))) #attrs參數值 print(soup.find(attrs={'class':'vip'}))# 使用 find() 時,如果沒有找到查詢標簽會返回 None,而 find_all() 方法返回空列表。示例如下: print("---result103---") print(soup.find('bdi')) print(soup.find_all('audio'))# BS4 也為 find()提供了簡化寫法,如下所示: print("---result104---") #簡化寫法 print(soup.body.a) #上面代碼等價于 print(soup.find("body").find("a"))# 獲得文本,并添加分隔符,去掉兩端空格: print("---result105---") result105 = soup.find('ul').get_text("----", strip=True) print(result105)輸出如下:
---result101--- <li class="cla-0" id="id-0"><a href="/link1">01</a></li> ---result102--- <li class="cla-0" id="id-0"><a href="/link1">01</a></li> <p class="vip">加入我們閱讀所有教程</p> ---result103--- None [] ---result104--- <a href="https://www.baidu.com" id="link4">百度一下</a> <a href="https://www.baidu.com" id="link4">百度一下</a>---result105--- 01----02----033) CSS選擇器
BS4 支持大部分的 CSS 選擇器,比如常見的標簽選擇器、類選擇器、id 選擇器,以及層級選擇器。Beautiful Soup 提供了一個 select() 方法,通過向該方法中添加選擇器,就可以在 HTML 文檔中搜索到與之對應的內容。應用示例如下:
#coding:utf8 html_doc = """ <!DOCTYPE html> <html> <head> <title>"bs4測試案例網站"</title> </head> <body> <p class="vip">加入我們閱讀所有教程</p> <a href="https://www.baidu.com" id="link4">百度一下</a> <a href="https://www.sos.com">soso一下</a> <div class="useful"><ul><li class="cla-0" id="id-0"><a href="/link1">01</a></li><li class="cla-1"><a href="/link2">02</a></li><li><strong><a href="/link3">03</a></strong></li></ul></div> </body> </html> """ from bs4 import BeautifulSoupsoup = BeautifulSoup(html_doc, 'html.parser') #根據元素標簽查找 print("---result201---") print(soup.select('title'))#根據屬性選擇器查找 print("---result202---") print(soup.select('a[href]'))#根據class類查找 print("---result203---") print(soup.select('.vip'))#后代節點查找 print("---result204---") print(soup.select('html head title'))#查找兄弟節點 print("---result205---") print(soup.select('p + a'))#根據id選擇p標簽的兄弟節點 print("---result206---") print(soup.select('p ~ #link4'))#nth-of-type(n)選擇器,用于匹配同類型中的第n個同級兄弟元素 print("---result207---") print(soup.select('p ~ a:nth-of-type(1)'))#查找子節點 print("---result208---") print(soup.select('ul > li')) print(soup.select('ul > .cla-1'))輸出如下:
---result201--- [<title>"bs4測試案例網站"</title>] ---result202--- [<a href="https://www.baidu.com" id="link4">百度一下</a>, <a href="https://www.sos.com">soso一下</a>, <a href="/link1">01</a>, <a href="/link2">02</a>, <a href="/link3">03</a>] ---result203--- [<p class="vip">加入我們閱讀所有教程</p>] ---result204--- [<title>"bs4測試案例網站"</title>] ---result205--- [<a href="https://www.baidu.com" id="link4">百度一下</a>] ---result206--- [<a href="https://www.baidu.com" id="link4">百度一下</a>] ---result207--- [<a href="https://www.baidu.com" id="link4">百度一下</a>] ---result208--- [<li class="cla-0" id="id-0"><a href="/link1">01</a></li>, <li class="cla-1"><a href="/link2">02</a></li>, <li><strong><a href="/link3">03</a></strong></li>] [<li class="cla-1"><a href="/link2">02</a></li>]2.3 BS4綜合案例
2.3.1 需求:爬取三國演義小說的所有章節和內容
import requests from bs4 import BeautifulSoup #需求:爬取三國演義小說的所有章節和內容 if __name__ == '__main__':#UA偽裝:將對應的User-Agent封裝到一個字典中headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'}#對首頁的頁面進行爬取url = 'https://www.shicimingju.com/book/sanguoyanyi.html' # page_text = requests.get(url=url,headers=headers).textpage_text = requests.get(url=url,headers=headers).content#在首頁中解析出章節的標題和詳情頁的url#1、實例化BeautifulSoup對象,需要將頁面源碼數據加載到該對象中soup = BeautifulSoup(page_text,'html.parser')#解析章節標題和詳情頁的urlli_list = soup.select('.book-mulu > ul > li')fp = open('./sanguo.txt','w',encoding='utf-8')num = 0for li in li_list:num += 1if num >5:breaktitle = li.a.string#詳情頁面的urldetail_url = 'http://www.shicimingju.com'+li.a['href']#對詳情頁發起請求,解析出章節內容detail_page_text = requests.get(url=detail_url,headers=headers).content#解析出相關章節內容detail_soup = BeautifulSoup(detail_page_text,'html.parser')div_tag = detail_soup.find('div',class_='chapter_content')#解析到了章節的內容content = div_tag.textfp.write(title+':'+content+'\n')print(title,'successful!')輸出為:
第一回·宴桃園豪杰三結義 斬黃巾英雄首立功 successful! 第二回·張翼德怒鞭督郵 何國舅謀誅宦豎 successful! 第三回·議溫明董卓叱丁原 饋金珠李肅說呂布 successful! 第四回·廢漢帝陳留踐位 謀董賊孟德獻刀 successful! 第五回·發矯詔諸鎮應曹公 破關兵三英戰呂布 successful! ...2.3.2 爬取小說數據,并排錯
from multiprocessing import get_context from turtle import title import requests from bs4 import BeautifulSoup import lxml if __name__ == '__main__':url = 'https://b.faloo.com/1190629.html'headers={'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'}page_text = requests.get(url= url,headers=headers).contentsoup = BeautifulSoup(page_text,'lxml')li_list = soup.select('.DivTd')fp = open('./siheyuan.txt','w',encoding='utf-8')num = 0for DivTd in li_list:num += 1if num >5:breaktitle = DivTd.a.stringnew_url = 'https:' + DivTd.a['href']new_page_text = requests.get(url= new_url,headers=headers).contentnew_soup = BeautifulSoup(new_page_text,'lxml')#content = new_soup.find('div',class_='noveContent')content = new_soup.select('.noveContent')for noveContent in content:work = noveContent.p.stringfp.write(title+'\n'+str(content)+'\n')print(title+'------抓取完成')輸出為:
001.新人報道------抓取完成 002.你咋不跟領導干一架呢------抓取完成 003.確定過眼神,就是要抓的人------抓取完成 004.領導,他又抓一個------抓取完成 005.搓搓這小子銳氣------抓取完成 006.反扒隊,你就是江晨?------抓取完成 007.莫伸手,伸手必被抓------抓取完成 008.賊王氣得嗷嗷大哭------抓取完成 009.抓到耗子就是好貓------抓取完成 010.到底是哪里露出了馬腳------抓取完成 011.就你們賊多?------抓取完成 012.近身格斗,不帶怕的------抓取完成 013.分賊不均------抓取完成 014.這是指導工作去了------抓取完成 015.三千罪犯,我全都要------抓取完成 016.我懷疑你送人頭------抓取完成 017.上個廁所就抓到一個?------抓取完成 018.這待遇,要饞哭了------抓取完成 019.又是要搞事情的節奏啊------抓取完成 020.師父給你定個小目標------抓取完成 021.先讓你跑個紅綠燈------抓取完成 022.這貨是個人肉掃描機------抓取完成 023.這還帶買一送一的?------抓取完成 024.這乞丐有問題?------抓取完成 025.抓捕體驗極差------抓取完成 026.給我整不會了------抓取完成 027.這排場,真闊氣------抓取完成 028.利刃-重案組------抓取完成 029.我能受這委屈?------抓取完成 030.這年輕人不講武德------抓取完成 031.年紀不大,譜子不小------抓取完成 032.神秘的狀元巷------抓取完成 033.當我掛白開的?------抓取完成 034.有些人慌了呀------抓取完成 035.你好,開門查水表------抓取完成 036.隊友太秀,求安慰------抓取完成 037.組隊刷副本------抓取完成 038.出了名的老實人------抓取完成 039.我天生就結巴------抓取完成 040.秀還是你秀------抓取完成 041.這就叫專業------抓取完成 042.垃圾桶的藝術------抓取完成 043.這就開張了?------抓取完成 044.抱大腿的覺悟(第五更)------抓取完成 045.時代變了?(第六章)------抓取完成 046.你敢拆我空調?(第七更)------抓取完成 047.三個硬茬子------抓取完成 048.朋友,露個面吧------抓取完成 049.你敢臉探草叢?------抓取完成 050.高效流水線------抓取完成 051.抓超載了(第五更)------抓取完成 052.傷害了多少人(加更一章)------抓取完成 053.還有論車的?(加更第二章)------抓取完成 054.來了一條收桿魚------抓取完成 055.各位,等我回來------抓取完成 056.實在關不下了------抓取完成 057.槍來------抓取完成 058.我攤牌了,不裝了------抓取完成 059.把那孩子帶回來------抓取完成 060.讓你拐個空氣------抓取完成 061.槍聲就是命令------抓取完成 062.開槍聽個響------抓取完成 063.能跑贏我的,只有年齡(第五更)------抓取完成 064.他又來了------抓取完成 065.活生生攆我兩個小時------抓取完成 066.我能讓你出院?------抓取完成 067.戰前晉升------抓取完成 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-45-4e73a887f2ec> in <module>()14 fp = open('./siheyuan.txt','w',encoding='utf-8')15 for DivTd in li_list: ---> 16 title = DivTd.a.string17 new_url = 'https:' + DivTd.a['href']18 new_page_text = requests.get(url= new_url,headers=headers).contentAttributeError: 'NoneType' object has no attribute 'string'總結
以上是生活随笔為你收集整理的Python爬虫技术系列-02HTML解析-BS4的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: C++ AOE网络
 - 下一篇: 微信小程序获取openid(用户唯一身份