1.前言——網(wǎng)頁(yè)解析器的種類(lèi)現(xiàn)在,我已經(jīng)會(huì)使用 python 模擬瀏覽器進(jìn)行一些 Https 的抓包,發(fā)請(qǐng)求了,那么根據(jù)我們第一篇所說(shuō)的結(jié)構(gòu): 網(wǎng)頁(yè)下載器() -> 網(wǎng)頁(yè)解析器() 的流程,接下來(lái)該說(shuō)網(wǎng)頁(yè)解析器了。 在網(wǎng)頁(yè)解析器中,我們一般有這四個(gè):
我們知道,在發(fā)送請(qǐng)求完之后,服務(wù)器會(huì)返回給我們一堆源代碼。 可以看到很多亂七八糟的東西,明顯這可不是我們想要的,所以我們要提取出有價(jià)值的數(shù)據(jù)?。?! 也就是在服務(wù)器返回給我們的源碼之中我們要進(jìn)行過(guò)濾,匹配或者說(shuō)提取 只拿到我們想要的就好,其它就丟掉(臥槽,無(wú)情…)。 說(shuō)到過(guò)濾,匹配,提取之類(lèi)的操作,很多人就想到了鼎鼎大名的正則表達(dá)式了。 正則表達(dá)式就是定義一些特殊的符號(hào)來(lái)匹配不同的字符。 但是一個(gè)字!真特么難… 再見(jiàn) 2. 正則表達(dá)式介紹哈哈,開(kāi)玩笑,雖然我不喜歡用正則,但是也還是把常用的列出來(lái)! 那么python是怎么使用正則表達(dá)式的呢?就要用python 的 re庫(kù)了。 re模塊re 模塊用正則表達(dá)式最常用的函數(shù)。 是這個(gè)方法: re.match(x,y) 主要傳入兩個(gè)參數(shù):
例如: 想要從content 中拿到一個(gè)數(shù)字 import re content = 'CSDNzoutao has 100 bananas' res = re.match('^CS.*(\d+)\s.*s$',content) print(res.group(1)) 可以運(yùn)行看看。 其次,在python爬蟲(chóng)中比較常用到的組合是:
就是這三個(gè)字符,它表示的就是匹配任意字符。 但是 .*?的 . 代表所有的單個(gè)字符,除了 \n \r 如字符串有換行了怎么辦呢? content = """CSDNzoutao has 100 bananas""" 就需要用到 re 的匹配模式了。 說(shuō)來(lái)也簡(jiǎn)單,直接用 re.S: import re content = """CSDNzoutao has 100 bananas""" res = re.match('^CS .*?(\d+)\s.*s$',content,re.S) print(res.group(1)) 結(jié)果: 再來(lái)說(shuō)說(shuō) re 的另一個(gè)常用到的方法吧:
這個(gè)主要就是把匹配符封裝。 import re content = "CSDNzoutao has 100 bananas" pattern = re.compile('CS .*?(\d+)\s.*s',re.S) res = re.match(pattern,content) print(res.group(1)) compile 就是數(shù)據(jù)之類(lèi)的封裝一下,方便下面調(diào)用。 更多正則請(qǐng)參考正則表達(dá)式詳解。 2.1 requests+re正則 爬蟲(chóng)實(shí)戰(zhàn):好吧,雖然我很不情愿用正則,但是多少也是要寫(xiě)一個(gè)實(shí)戰(zhàn)例子的是吧。 就使用 requests 和 re正則解析器來(lái)寫(xiě)一個(gè)爬蟲(chóng)。 python3+requests + re 爬蟲(chóng)實(shí)戰(zhàn)篇 因?yàn)榍耙黄褪撬?,我就不?xiě)了。 好了 ,關(guān)于 re 模塊和正則表達(dá)式就介紹完啦。 因?yàn)槲覀冊(cè)诂F(xiàn)在的爬蟲(chóng)中,很多不用正則表達(dá)式了,畢竟我也看不明白。所以我們一般使用另外的網(wǎng)頁(yè)解析器。 3.BeautifulSoup庫(kù)的介紹最常用的,就是第三方插件——BeautifulSoup庫(kù) 了。比正則好記?。?/p> 既然是第三方庫(kù),那么要安裝一下這個(gè)庫(kù):
值得一提的是啊,在 beautifulsoup 庫(kù)中,還支持不同的解析器。(很多人被搞懵逼了,記住我說(shuō)的bs是庫(kù)!倉(cāng)庫(kù)里面放不同的解析器,很合理吧?) 比如: 1.解析庫(kù):BeautifulSoupBeautifulSoup號(hào)稱(chēng)Python中最受歡迎的HTML解析庫(kù)之一。 lxml這個(gè)庫(kù)可以用來(lái)解析HTML和XML文檔,以非常底層的實(shí)現(xiàn)而聞名,大部分源碼都是C語(yǔ)言寫(xiě)的,雖然學(xué)習(xí)這東西要花一定的時(shí)間,但是它的處理速度非??臁?/p> HTML parser這是python自帶的解析庫(kù),所以很方便。 2.解析器:其實(shí)上面提到的另外的這兩個(gè)庫(kù),又可以作為BeautifulSoup庫(kù)的解析器。 下面對(duì)BeautifulSoup中的 各種 html 解析器的優(yōu)缺點(diǎn)對(duì)比: Python’s html.parser 默認(rèn)用的是這個(gè) 使用語(yǔ)法: BeautifulSoup(markup,"html.parser") 優(yōu)點(diǎn)
缺點(diǎn)
lxml’s HTML parser 最推薦 使用語(yǔ)法 BeautifulSoup(markup,"lxml") 優(yōu)點(diǎn)
缺點(diǎn)
lxml’s XML parser 最推薦 使用語(yǔ)法 BeautifulSoup(markup, "lxml-xml") 或者 BeautifulSoup(markup,"xml") 優(yōu)點(diǎn)
缺點(diǎn)
html5lib :最不常用 使用語(yǔ)法 BeautifulSoup(markup, "html5lib") 優(yōu)點(diǎn)
缺點(diǎn)
如圖: 所以,我們?cè)谑褂胋eautifulsoup庫(kù)的時(shí)候,必須要指定一個(gè)解析器。而lxml和html.parser都可以考慮拿來(lái)做解析器。 l其中 xml解析器通常更快,如果可以,我建議您安裝并使用lxml以提高速度。 不同的解析器將為其生成不同的BeautifulSoup樹(shù),所以先來(lái)使用一個(gè)例子, 體驗(yàn)一下beautifulsoup 的一些常用的方法。 3.1 BeautifulSoup 常用方法口水話我也懶得說(shuō)了,其他博文估計(jì)講得很清楚,我就舉個(gè)栗子,常用方法已經(jīng)用*** 標(biāo)出。 假設(shè)我們使用網(wǎng)頁(yè)下載器弄回來(lái)的網(wǎng)頁(yè)是這樣的:
html_doc = """ <html> <head> <title>睡鼠的故事</title> </head> <body> <p class="title" id="No0.1"><b>第一章bs示例</b></p> <p class="story">從前有三個(gè)小妹妹,她們的名字是: <a href="http:///elsie" class="sister" id="link1">1張三</a>, <a href="http:///lacie" class="sister" id="link2">2李四</a> and <a href="http:///tillie" class="sister" id="link3">3Jack</a>; and他們住在井底. </p> <div class="story">省略一萬(wàn)字。。。 <ul id="producers"> <li class="producerlist"> <div class="name">香蕉</div> <div class="number">100斤</div> </li> <li class="producerlist"> <div class="name">蘋(píng)果</div> <div class="number">200斤</div> </li> </ul> </div> </body> </html> """
# 2.引入庫(kù) from bs4 import BeautifulSoup # 3.解析該html頁(yè)面 soup = BeautifulSoup(html_doc, 'html.parser') 然后我們要做的就是從這個(gè)對(duì)象直接獲取我們要的內(nèi)容了。
獲取標(biāo)題的內(nèi)容: print(soup.title.string) # 睡鼠的故事 print(soup.head)#告訴它想獲取的tag的name,是標(biāo)簽名,而不是標(biāo)簽的name屬性 #<head> #<title>睡鼠的故事story</title> #</head> 獲取超鏈接 a 標(biāo)簽或超鏈里面的文本內(nèi)容: print(soup.a) # 通過(guò)點(diǎn)取屬性的方式只能獲得當(dāng)前名字的【第一個(gè)a標(biāo)簽】 # <a class="sister" href="http:///elsie" id="link1">1張三</a> print(soup.a.string) # 通過(guò).string獲取a標(biāo)簽中的文本內(nèi)容。 # 1張三 獲取所有超鏈接: print(soup.find_all('a')) # 所有的<a>標(biāo)簽,或是通過(guò)名字得到更多內(nèi)容 # [<a class="sister" href="http:///elsie" id="link1">1張三</a>, <a class="sister" href="http:///lacie" id="link2">2李四</a>, <a class="sister" href="http:///tillie" id="link3">3Jack</a>] 獲取 id 為 link2 的超鏈接: print(soup.find(id="link2")) #<a class="sister" href="http:///lacie" id="link2">2李四</a> 獲取網(wǎng)頁(yè)中所有的內(nèi)容: print(soup.get_text()) # 獲取網(wǎng)頁(yè)中所有的文本內(nèi)容 (*)去除多余空白內(nèi)容: stripped_strings方法: for string in soup.stripped_strings: print(repr(string)) (空格的行會(huì)被忽略掉,段首和段末的空白會(huì)被刪除)
soup.find_all('b') # 查找文檔中所有的<b>標(biāo)簽 import re for tag in soup.find_all(re.compile("^b")): # 還可以配合正則作參數(shù),用 match()調(diào)用 print(tag.name) a2 = soup.find_all(id="link2") print("===========================",a2,type(a2)) def has_six_characters(css_class): return css_class is not None and len(css_class) == 6 print(soup.find_all(class_=has_six_characters)) print(soup.find_all("a", class_="sister")) # 搜索內(nèi)容里面包含“Elsie”的<a>標(biāo)簽: print(soup.find_all("a", string="Elsie")) # 使用 limit 參數(shù)限制返回結(jié)果的數(shù)量 print(soup.find_all("a", limit=2)) # 只想搜索tag的直接子節(jié)點(diǎn),可以使用參數(shù) recursive=False print(soup.html.find_all("title", recursive=False))
唯一的區(qū)別:find_all()方法的返回的值是包含元素的列表,而find()方法直接返回的是一個(gè)結(jié)果。 舉例子: print(soup.find_all('title', limit=1)) # 跟下面一句等效。 print(soup.find('title')) # ** 基于文本內(nèi)容的查找必須用到參數(shù)text p = soup.find("class",class_="name") # None print("==============",p) # 雖說(shuō)不報(bào)錯(cuò),但返回的是空,匹配不到值。 p2 = soup.find("class",attrs={"class":"name"}) # None 匹配不到值。 print("++++++++++++++++++++++",p2) # 層次結(jié)構(gòu) p3 = soup.find('div').find('li').find("div").text # 這樣寫(xiě)才是對(duì)的! print("-----------------",p3) # 香蕉 print(soup.get_text()) # 只要文本內(nèi)容 print(soup.i.get_text()) # find_all_next() 方法返回所有符合條件的節(jié)點(diǎn), # find_next() 方法返回第一個(gè)符合條件的節(jié)點(diǎn): print(soup.a.find_next("p")) 是不是不太好理解? bs也這么想的,所以除了find方法、find_all()之外,如果你對(duì)css比較熟悉,就可以使用select ()方法
print(soup.select("title")) # 逐層查找 print(soup.select("html head title")) # ***找到某個(gè)tag標(biāo)簽下的直接子標(biāo)簽 print(soup.select("p > #link1")) # 通過(guò)CSS的類(lèi)名查找: *** print(soup.select(".sister")) # 通過(guò)id查找: print(soup.select("#link1")) # 同時(shí)用多種CSS選擇器查詢(xún)?cè)? print(soup.select("#link1,#link2")) # 通過(guò)是否存在某個(gè)屬性來(lái)查找: print(soup.select('a[href]')) # 通過(guò)屬性的值來(lái)查找: print(soup.select('a[href$="tillie"]')) # *** 取查找元素的文本值 print(soup.select('a[class="sister"]')[0].text) # 返回查找到的元素的第一個(gè) print(soup.select_one(".sister")) # 格式化輸出: prettify()方法-對(duì)象或節(jié)點(diǎn)都可以調(diào)用。 print(soup.prettify()) print(soup.a.prettify()) 基本上以上就是python爬蟲(chóng)中 BeautifulSoup 常用的方法,有了它,再也不用擔(dān)心不會(huì)正則表達(dá)式了。 整個(gè)例子源碼: #!/usr/bin/python3 # 1.得到一個(gè)html頁(yè)面 html_doc = """ <html> <head> <title>睡鼠的故情</title> </head> <body> <p class="title" id="No0.1"><b>第一章bs示例</b></p> <p class="story">從前有三個(gè)小妹妹,她們的名字是: <a href="http:///elsie" class="sister" id="link1">1張三</a>, <a href="http:///lacie" class="sister" id="link2">2李四</a> and <a href="http:///tillie" class="sister" id="link3">3Jack</a>; and他們住在井底. </p> <div class="story">省略一萬(wàn)字。。。 <ul id="producers"> <li class="producerlist"> <div class="name">香蕉</div> <div class="number">100斤</div> </li> <li class="producerlist"> <div class="name">蘋(píng)果</div> <div class="number">200斤</div> </li> </ul> </div> </body> </html> """ # 2.引入庫(kù) from bs4 import BeautifulSoup # 3.解析該html頁(yè)面 soup = BeautifulSoup(html_doc, 'html.parser') # 一、基本查找元素的方式-可以直接根據(jù)html的標(biāo)簽來(lái)取值。 print(soup.head) # 告訴它你想獲取的tag的name,是標(biāo)簽名字,而不是標(biāo)簽的name屬性!!! print(soup.title) print(soup.body.b) # 獲取<body>標(biāo)簽中的第一個(gè)<b>標(biāo)簽 print(soup.a) # 通過(guò)點(diǎn)取屬性的方式只能獲得當(dāng)前名字的【第一個(gè)a標(biāo)簽】: print(soup.find_all('a')) # 所有的<a>標(biāo)簽,或是通過(guò)名字得到更多內(nèi)容 head_tag = soup.head print('===========',head_tag.contents) # 以列表的方式輸出 #title_tag = head_tag.contents[0] # 字符串沒(méi)有.contents 屬性,因?yàn)樽址疀](méi)有子節(jié)點(diǎn): #print(title_tag.contents) # (*)去除多余空白內(nèi)容: 空格的行會(huì)被忽略掉,段首和段末的空白會(huì)被刪除 for string in soup.stripped_strings: print(repr(string)) # (*)二、[更強(qiáng)的查找元素-過(guò)濾器find_all()方法。是模糊匹配。返回一個(gè)列表list可遍歷,沒(méi)有找到目標(biāo)是返回空列表], soup.find_all('b') # 查找文檔中所有的<b>標(biāo)簽 import re for tag in soup.find_all(re.compile("^b")): # 還可以配合正則作參數(shù),用 match()調(diào)用 print(tag.name) a2 = soup.find_all(id="link2") print("===========================",a2,type(a2)) def has_six_characters(css_class): return css_class is not None and len(css_class) == 6 print(soup.find_all(class_=has_six_characters)) print(soup.find_all("a", class_="sister")) # 搜索內(nèi)容里面包含“Elsie”的<a>標(biāo)簽: print(soup.find_all("a", string="Elsie")) # 使用 limit 參數(shù)限制返回結(jié)果的數(shù)量 print(soup.find_all("a", limit=2)) # 只想搜索tag的直接子節(jié)點(diǎn),可以使用參數(shù) recursive=False print(soup.html.find_all("title", recursive=False)) # 三、find()方法—返回文檔中符合條件的標(biāo)簽,是一個(gè)ResultSet結(jié)果集,找不到目標(biāo)時(shí),返回 None .所以soup.find()后面可以直接接.text或get_text()來(lái)獲得標(biāo)簽中的文本內(nèi)容。 print(soup.find_all('title', limit=1)) print(soup.find('title')) # *** [基于文本內(nèi)容的查找必須用到參數(shù)text] p = soup.find("class",class_="name") # None print("==============",p) # 雖說(shuō)不報(bào)錯(cuò),但返回的是空,匹配不到值。 p2 = soup.find("class",attrs={"class":"name"}) # None 匹配不到值。 print("++++++++++++++++++++++",p2) # 層次 p3 = soup.find('div').find('li').find("div").text # 這樣寫(xiě)才是對(duì)的! print("-----------------",p3) # 香蕉 # 唯一的區(qū)別:find_all()方法的返回結(jié)果是值包含元素的列表,而find()方法直接返回結(jié)果. # find_all_next() 方法返回所有符合條件的節(jié)點(diǎn), # find_next() 方法返回第一個(gè)符合條件的節(jié)點(diǎn): print(soup.a.find_next("p")) # 三、CSS選擇器:.select()方法中傳入字符串參數(shù)-精確定位,返回一個(gè)列表list print(soup.select("title")) # 逐層查找 print(soup.select("html head title")) # ***找到某個(gè)tag標(biāo)簽下的直接子標(biāo)簽 print(soup.select("p > #link1")) # *** 通過(guò)CSS的類(lèi)名查找: print(soup.select(".sister")) # 通過(guò)id查找: print(soup.select("#link1")) # 同時(shí)用多種CSS選擇器查詢(xún)?cè)? print(soup.select("#link1,#link2")) # 通過(guò)是否存在某個(gè)屬性來(lái)查找: print(soup.select('a[href]')) # 通過(guò)屬性的值來(lái)查找: print(soup.select('a[href$="tillie"]')) # *** 取查找元素的文本值 print(soup.select('a[class="sister"]')[0].text) # 返回查找到的元素的第一個(gè) print(soup.select_one(".sister")) # 格式化輸出: prettify()方法-對(duì)象或節(jié)點(diǎn)都可以調(diào)用。 print(soup.prettify()) print(soup.a.prettify()) # ***只想得到tag中包含的文本內(nèi)容,那么可以用get_text()方法,結(jié)果作為Unicode字符串返回 markup = '<a href="http:///">\nI linked to <i></i>\n</a>' soup = BeautifulSoup(markup,"html.parser") print(soup.get_text()) # 只要文本內(nèi)容 print(soup.i.get_text()) 主要學(xué)會(huì)幾個(gè)東西,取標(biāo)簽,取標(biāo)簽里面的文本內(nèi)容,取多個(gè)標(biāo)簽,取單個(gè)標(biāo)簽,以及最常用的find_all() 、find()、 a.[0].text 和.get_text()幾個(gè)方法的使用就可以了。 老規(guī)矩,也整個(gè)爬蟲(chóng)案例看看,對(duì)比一下我們的re模塊和bs哪個(gè)好用些? 3.2 BeautifulSoup + reuqests實(shí)戰(zhàn)最近電影慌,就拿豆瓣電影 Top 250的那個(gè)來(lái)進(jìn)行爬取啦。 BeautifulSoup + reuqests豆瓣電影 Top 250實(shí)戰(zhàn)爬蟲(chóng) 好了,以上就是網(wǎng)頁(yè)解析器的各種解釋和使用。下一篇總結(jié)寫(xiě)B(tài)eautifulSoup 爬蟲(chóng)的關(guān)鍵做法,怎么定位標(biāo)簽和提取想要的內(nèi)容?參考地址: https://blog.csdn.net/ITBigGod/article/details/102859854 當(dāng)然你也可以直接去看 BS4的官方文檔。。 bs的英文文檔: https://www./software/BeautifulSoup/bs4/doc/#installing-a-parser 解析器之間的區(qū)別: https://www./software/BeautifulSoup/bs4/doc/#differences-between-parsers |
|
來(lái)自: 天使之翼 ` > 《待分類(lèi)》