您好,登錄后才能下訂單哦!
這篇文章主要介紹Python2爬蟲(chóng)中爬取百度貼吧帖子的案例,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
前言
親愛(ài)的們,教程比較舊了,百度貼吧頁(yè)面可能改版,可能代碼不好使,八成是正則表達(dá)式那兒匹配不到了,請(qǐng)更改一下正則,當(dāng)然最主要的還是幫助大家理解思路。
本篇目標(biāo)
1.對(duì)百度貼吧的任意帖子進(jìn)行抓取
2.指定是否只抓取樓主發(fā)帖內(nèi)容
3.將抓取到的內(nèi)容分析并保存到文件
1.URL格式的確定
首先,我們先觀察一下百度貼吧的任意一個(gè)帖子。
比如:http://tieba.baidu.com/p/3138733512?see_lz=1&pn=1,這是一個(gè)關(guān)于NBA50大的盤(pán)點(diǎn),分析一下這個(gè)地址。
http:// 代表資源傳輸使用http協(xié)議 tieba.baidu.com 是百度的二級(jí)域名,指向百度貼吧的服務(wù)器。 /p/3138733512 是服務(wù)器某個(gè)資源,即這個(gè)帖子的地址定位符 see_lz和pn是該URL的兩個(gè)參數(shù),分別代表了只看樓主和帖子頁(yè)碼,等于1表示該條件為真
所以我們可以把URL分為兩部分,一部分為基礎(chǔ)部分,一部分為參數(shù)部分。
例如,上面的URL我們劃分基礎(chǔ)部分是 http://tieba.baidu.com/p/3138733512,參數(shù)部分是 ?see_lz=1&pn=1
2.頁(yè)面的抓取
熟悉了URL的格式,那就讓我們用urllib2庫(kù)來(lái)試著抓取頁(yè)面內(nèi)容吧。上一篇糗事百科我們最后改成了面向?qū)ο蟮木幋a方式,這次我們直接嘗試一下,定義一個(gè)類(lèi)名叫BDTB(百度貼吧),一個(gè)初始化方法,一個(gè)獲取頁(yè)面的方法。
其中,有些帖子我們想指定給程序是否要只看樓主,所以我們把只看樓主的參數(shù)初始化放在類(lèi)的初始化上,即init方法。另外,獲取頁(yè)面的方法我們需要知道一個(gè)參數(shù)就是帖子頁(yè)碼,所以這個(gè)參數(shù)的指定我們放在該方法中。
綜上,我們初步構(gòu)建出基礎(chǔ)代碼如下:
__author__ = 'CQC' # -*- coding:utf-8 -*- import urllib import urllib2 import re #百度貼吧爬蟲(chóng)類(lèi) class BDTB: #初始化,傳入基地址,是否只看樓主的參數(shù) def __init__(self,baseUrl,seeLZ): self.baseURL = baseUrl self.seeLZ = '?see_lz='+str(seeLZ) #傳入頁(yè)碼,獲取該頁(yè)帖子的代碼 def getPage(self,pageNum): try: url = self.baseURL+ self.seeLZ + '&pn=' + str(pageNum) request = urllib2.Request(url) response = urllib2.urlopen(request) print response.read() return response except urllib2.URLError, e: if hasattr(e,"reason"): print u"連接百度貼吧失敗,錯(cuò)誤原因",e.reason return None baseURL = 'http://tieba.baidu.com/p/3138733512' bdtb = BDTB(baseURL,1) bdtb.getPage(1)
運(yùn)行代碼,我們可以看到屏幕上打印出了這個(gè)帖子第一頁(yè)樓主發(fā)言的所有內(nèi)容,形式為HTML代碼。
3.提取相關(guān)信息
1)提取帖子標(biāo)題
首先,讓我們提取帖子的標(biāo)題。
在瀏覽器中審查元素,或者按F12,查看頁(yè)面源代碼,我們找到標(biāo)題所在的代碼段,可以發(fā)現(xiàn)這個(gè)標(biāo)題的HTML代碼是:
<h2 class="core_title_txt " title="純?cè)瓌?chuàng)我心中的NBA2014-2015賽季現(xiàn)役50大" style="width: 396px">純?cè)瓌?chuàng)我心中的 NBA2014-2015賽季現(xiàn)役50大</h2>
所以我們想提取<h2>標(biāo)簽中的內(nèi)容,同時(shí)還要指定這個(gè)class確定唯一,因?yàn)閔2標(biāo)簽實(shí)在太多啦。
正則表達(dá)式如下:
<h2 class="core_title_txt.*?>(.*?)</h2>
所以,我們?cè)黾右粋€(gè)獲取頁(yè)面標(biāo)題的方法
#獲取帖子標(biāo)題 def getTitle(self): page = self.getPage(1) pattern = re.compile('<h2 class="core_title_txt.*?>(.*?)</h2>',re.S) result = re.search(pattern,page) if result: #print result.group(1) #測(cè)試輸出 return result.group(1).strip() else: return None
2)提取帖子頁(yè)數(shù)
同樣地,帖子總頁(yè)數(shù)我們也可以通過(guò)分析頁(yè)面中的共?頁(yè)來(lái)獲取。所以我們的獲取總頁(yè)數(shù)的方法如下:
#獲取帖子一共有多少頁(yè) def getPageNum(self): page = self.getPage(1) pattern = re.compile('<li class="l_reply_num.*?</span>.*?<span.*?>(.*?)</span>',re.S) result = re.search(pattern,page) if result: #print result.group(1) #測(cè)試輸出 return result.group(1).strip() else: return None
3)提取正文內(nèi)容
審查元素,我們可以看到百度貼吧每一層樓的主要內(nèi)容都在<div id=”post_content_xxxx”></div>標(biāo)簽里面,所以我們可以寫(xiě)如下的正則表達(dá)式
<div id="post_content_.*?>(.*?)</div>
相應(yīng)地,獲取頁(yè)面所有樓層數(shù)據(jù)的方法可以寫(xiě)成如下方法:
#獲取每一層樓的內(nèi)容,傳入頁(yè)面內(nèi)容 def getContent(self,page): pattern = re.compile('<div id="post_content_.*?>(.*?)</div>',re.S) items = re.findall(pattern,page) for item in items: print item
好,我們運(yùn)行一下結(jié)果看一下:
真是醉了,還有一大片換行符和圖片符,好口怕!既然這樣,我們就要對(duì)這些文本進(jìn)行處理,把各種各樣復(fù)雜的標(biāo)簽給它剔除掉,還原精華內(nèi)容,把文本處理寫(xiě)成一個(gè)方法也可以,不過(guò)為了實(shí)現(xiàn)更好的代碼架構(gòu)和代碼重用,我們可以考慮把標(biāo)簽等的處理寫(xiě)作一個(gè)類(lèi)。
那我們就叫它Tool(工具類(lèi)吧),里面定義了一個(gè)方法,叫replace,是替換各種標(biāo)簽的。在類(lèi)中定義了幾個(gè)正則表達(dá)式,主要利用了re.sub方法對(duì)文本進(jìn)行匹配后然后替換。具體的思路已經(jīng)寫(xiě)到注釋中,大家可以看一下這個(gè)類(lèi)。
import re #處理頁(yè)面標(biāo)簽類(lèi) class Tool: #去除img標(biāo)簽,7位長(zhǎng)空格 removeImg = re.compile('<img.*?>| {7}|') #刪除超鏈接標(biāo)簽 removeAddr = re.compile('<a.*?>|</a>') #把換行的標(biāo)簽換為\n replaceLine = re.compile('<tr>|<div>|</div>|</p>') #將表格制表<td>替換為\t replaceTD= re.compile('<td>') #把段落開(kāi)頭換為\n加空兩格 replacePara = re.compile('<p.*?>') #將換行符或雙換行符替換為\n replaceBR = re.compile('<br><br>|<br>') #將其余標(biāo)簽剔除 removeExtraTag = re.compile('<.*?>') def replace(self,x): x = re.sub(self.removeImg,"",x) x = re.sub(self.removeAddr,"",x) x = re.sub(self.replaceLine,"\n",x) x = re.sub(self.replaceTD,"\t",x) x = re.sub(self.replacePara,"\n ",x) x = re.sub(self.replaceBR,"\n",x) x = re.sub(self.removeExtraTag,"",x) #strip()將前后多余內(nèi)容刪除 return x.strip()
在使用時(shí),我們只需要初始化一下這個(gè)類(lèi),然后調(diào)用replace方法即可。
現(xiàn)在整體代碼是如下這樣子的,現(xiàn)在我的代碼是寫(xiě)到這樣子的。
__author__ = 'CQC' # -*- coding:utf-8 -*- import urllib import urllib2 import re #處理頁(yè)面標(biāo)簽類(lèi) class Tool: #去除img標(biāo)簽,7位長(zhǎng)空格 removeImg = re.compile('<img.*?>| {7}|') #刪除超鏈接標(biāo)簽 removeAddr = re.compile('<a.*?>|</a>') #把換行的標(biāo)簽換為\n replaceLine = re.compile('<tr>|<div>|</div>|</p>') #將表格制表<td>替換為\t replaceTD= re.compile('<td>') #把段落開(kāi)頭換為\n加空兩格 replacePara = re.compile('<p.*?>') #將換行符或雙換行符替換為\n replaceBR = re.compile('<br><br>|<br>') #將其余標(biāo)簽剔除 removeExtraTag = re.compile('<.*?>') def replace(self,x): x = re.sub(self.removeImg,"",x) x = re.sub(self.removeAddr,"",x) x = re.sub(self.replaceLine,"\n",x) x = re.sub(self.replaceTD,"\t",x) x = re.sub(self.replacePara,"\n ",x) x = re.sub(self.replaceBR,"\n",x) x = re.sub(self.removeExtraTag,"",x) #strip()將前后多余內(nèi)容刪除 return x.strip() #百度貼吧爬蟲(chóng)類(lèi) class BDTB: #初始化,傳入基地址,是否只看樓主的參數(shù) def __init__(self,baseUrl,seeLZ): self.baseURL = baseUrl self.seeLZ = '?see_lz='+str(seeLZ) self.tool = Tool() #傳入頁(yè)碼,獲取該頁(yè)帖子的代碼 def getPage(self,pageNum): try: url = self.baseURL+ self.seeLZ + '&pn=' + str(pageNum) request = urllib2.Request(url) response = urllib2.urlopen(request) return response.read().decode('utf-8') except urllib2.URLError, e: if hasattr(e,"reason"): print u"連接百度貼吧失敗,錯(cuò)誤原因",e.reason return None #獲取帖子標(biāo)題 def getTitle(self): page = self.getPage(1) pattern = re.compile('<h2 class="core_title_txt.*?>(.*?)</h2>',re.S) result = re.search(pattern,page) if result: #print result.group(1) #測(cè)試輸出 return result.group(1).strip() else: return None #獲取帖子一共有多少頁(yè) def getPageNum(self): page = self.getPage(1) pattern = re.compile('<li class="l_reply_num.*?</span>.*?<span.*?>(.*?)</span>',re.S) result = re.search(pattern,page) if result: #print result.group(1) #測(cè)試輸出 return result.group(1).strip() else: return None #獲取每一層樓的內(nèi)容,傳入頁(yè)面內(nèi)容 def getContent(self,page): pattern = re.compile('<div id="post_content_.*?>(.*?)</div>',re.S) items = re.findall(pattern,page) #for item in items: # print item print self.tool.replace(items[1]) baseURL = 'http://tieba.baidu.com/p/3138733512' bdtb = BDTB(baseURL,1) bdtb.getContent(bdtb.getPage(1))
我們嘗試一下,重新再看一下效果,這下經(jīng)過(guò)處理之后應(yīng)該就沒(méi)問(wèn)題了,是不是感覺(jué)好酸爽!
4)替換樓層
至于這個(gè)問(wèn)題,我感覺(jué)直接提取樓層沒(méi)什么必要呀,因?yàn)橹豢礃侵鞯脑?,有些樓層的編?hào)是間隔的,所以我們得到的樓層序號(hào)是不連續(xù)的,這樣我們保存下來(lái)也沒(méi)什么用。
所以可以嘗試下面的方法:
1.每打印輸出一段樓層,寫(xiě)入一行橫線來(lái)間隔,或者換行符也好。
2.試著重新編一個(gè)樓層,按照順序,設(shè)置一個(gè)變量,每打印出一個(gè)結(jié)果變量加一,打印出這個(gè)變量當(dāng)做樓層。
這里我們嘗試一下吧,看看效果怎樣。
把getContent方法修改如下:
#獲取每一層樓的內(nèi)容,傳入頁(yè)面內(nèi)容 def getContent(self,page): pattern = re.compile('<div id="post_content_.*?>(.*?)</div>',re.S) items = re.findall(pattern,page) floor = 1 for item in items: print floor,u"樓--------------------------------------------------------------------------------------- ---------------------------------------------\n" print self.tool.replace(item) floor += 1
運(yùn)行一下看看效果:
嘿嘿,效果還不錯(cuò)吧,感覺(jué)真酸爽!接下來(lái)我們完善一下,然后寫(xiě)入文件。
4.寫(xiě)入文件
最后便是寫(xiě)入文件的過(guò)程,過(guò)程很簡(jiǎn)單,就幾句話的代碼而已,主要是利用了以下兩句:
file = open(“tb.txt”,”w”) file.writelines(obj)
這里不再贅述,稍后直接貼上完善之后的代碼。
5.完善代碼
現(xiàn)在我們對(duì)代碼進(jìn)行優(yōu)化,重構(gòu),在一些地方添加必要的打印信息,整理如下:
__author__ = 'CQC' # -*- coding:utf-8 -*- import urllib import urllib2 import re #處理頁(yè)面標(biāo)簽類(lèi) class Tool: #去除img標(biāo)簽,7位長(zhǎng)空格 removeImg = re.compile('<img.*?>| {7}|') #刪除超鏈接標(biāo)簽 removeAddr = re.compile('<a.*?>|</a>') #把換行的標(biāo)簽換為\n replaceLine = re.compile('<tr>|<div>|</div>|</p>') #將表格制表<td>替換為\t replaceTD= re.compile('<td>') #把段落開(kāi)頭換為\n加空兩格 replacePara = re.compile('<p.*?>') #將換行符或雙換行符替換為\n replaceBR = re.compile('<br><br>|<br>') #將其余標(biāo)簽剔除 removeExtraTag = re.compile('<.*?>') def replace(self,x): x = re.sub(self.removeImg,"",x) x = re.sub(self.removeAddr,"",x) x = re.sub(self.replaceLine,"\n",x) x = re.sub(self.replaceTD,"\t",x) x = re.sub(self.replacePara,"\n ",x) x = re.sub(self.replaceBR,"\n",x) x = re.sub(self.removeExtraTag,"",x) #strip()將前后多余內(nèi)容刪除 return x.strip() #百度貼吧爬蟲(chóng)類(lèi) class BDTB: #初始化,傳入基地址,是否只看樓主的參數(shù) def __init__(self,baseUrl,seeLZ,floorTag): #base鏈接地址 self.baseURL = baseUrl #是否只看樓主 self.seeLZ = '?see_lz='+str(seeLZ) #HTML標(biāo)簽剔除工具類(lèi)對(duì)象 self.tool = Tool() #全局file變量,文件寫(xiě)入操作對(duì)象 self.file = None #樓層標(biāo)號(hào),初始為1 self.floor = 1 #默認(rèn)的標(biāo)題,如果沒(méi)有成功獲取到標(biāo)題的話則會(huì)用這個(gè)標(biāo)題 self.defaultTitle = u"百度貼吧" #是否寫(xiě)入樓分隔符的標(biāo)記 self.floorTag = floorTag #傳入頁(yè)碼,獲取該頁(yè)帖子的代碼 def getPage(self,pageNum): try: #構(gòu)建URL url = self.baseURL+ self.seeLZ + '&pn=' + str(pageNum) request = urllib2.Request(url) response = urllib2.urlopen(request) #返回UTF-8格式編碼內(nèi)容 return response.read().decode('utf-8') #無(wú)法連接,報(bào)錯(cuò) except urllib2.URLError, e: if hasattr(e,"reason"): print u"連接百度貼吧失敗,錯(cuò)誤原因",e.reason return None #獲取帖子標(biāo)題 def getTitle(self,page): #得到標(biāo)題的正則表達(dá)式 pattern = re.compile('<h2 class="core_title_txt.*?>(.*?)</h2>',re.S) result = re.search(pattern,page) if result: #如果存在,則返回標(biāo)題 return result.group(1).strip() else: return None #獲取帖子一共有多少頁(yè) def getPageNum(self,page): #獲取帖子頁(yè)數(shù)的正則表達(dá)式 pattern = re.compile('<li class="l_reply_num.*?</span>.*?<span.*?>(.*?)</span>',re.S) result = re.search(pattern,page) if result: return result.group(1).strip() else: return None #獲取每一層樓的內(nèi)容,傳入頁(yè)面內(nèi)容 def getContent(self,page): #匹配所有樓層的內(nèi)容 pattern = re.compile('<div id="post_content_.*?>(.*?)</div>',re.S) items = re.findall(pattern,page) contents = [] for item in items: #將文本進(jìn)行去除標(biāo)簽處理,同時(shí)在前后加入換行符 content = "\n"+self.tool.replace(item)+"\n" contents.append(content.encode('utf-8')) return contents def setFileTitle(self,title): #如果標(biāo)題不是為None,即成功獲取到標(biāo)題 if title is not None: self.file = open(title + ".txt","w+") else: self.file = open(self.defaultTitle + ".txt","w+") def writeData(self,contents): #向文件寫(xiě)入每一樓的信息 for item in contents: if self.floorTag == '1': #樓之間的分隔符 floorLine = "\n" + str(self.floor) + u"------------------------------------------------- ----------------------------------------\n" self.file.write(floorLine) self.file.write(item) self.floor += 1 def start(self): indexPage = self.getPage(1) pageNum = self.getPageNum(indexPage) title = self.getTitle(indexPage) self.setFileTitle(title) if pageNum == None: print "URL已失效,請(qǐng)重試" return try: print "該帖子共有" + str(pageNum) + "頁(yè)" for i in range(1,int(pageNum)+1): print "正在寫(xiě)入第" + str(i) + "頁(yè)數(shù)據(jù)" page = self.getPage(i) contents = self.getContent(page) self.writeData(contents) #出現(xiàn)寫(xiě)入異常 except IOError,e: print "寫(xiě)入異常,原因" + e.message finally: print "寫(xiě)入任務(wù)完成" print u"請(qǐng)輸入帖子代號(hào)" baseURL = 'http://tieba.baidu.com/p/' + str(raw_input(u'http://tieba.baidu.com/p/')) seeLZ = raw_input("是否只獲取樓主發(fā)言,是輸入1,否輸入0\n") floorTag = raw_input("是否寫(xiě)入樓層信息,是輸入1,否輸入0\n") bdtb = BDTB(baseURL,seeLZ,floorTag) bdtb.start()
現(xiàn)在程序演示如下:
完成之后,可以查看一下當(dāng)前目錄下多了一個(gè)以該帖子命名的txt文件,內(nèi)容便是帖子的所有數(shù)據(jù)。
抓貼吧,就是這么簡(jiǎn)單和任性!
以上是Python2爬蟲(chóng)中爬取百度貼吧帖子的案例的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。