芝麻HTTP:Python爬蟲實戰之爬取百度貼吧帖子

本篇目標

1.對百度貼吧的任意帖子進行抓取正則表達式

2.指定是否只抓取樓主發帖內容瀏覽器

3.將抓取到的內容分析並保存到文件服務器

1.URL格式的肯定

首先,咱們先觀察一下百度貼吧的任意一個帖子。架構

好比:http://tieba.baidu.com/p/3138733512?see_lz=1&pn=1app

http://  表明資源傳輸使用http協議
 tieba.baidu.com 是百度的二級域名,指向百度貼吧的服務器。
 /p/3138733512 是服務器某個資源,即這個帖子的地址定位符
 see_lz和pn是該URL的兩個參數,分別表明了只看樓主和帖子頁碼,等於1表示該條件爲真

因此咱們能夠把URL分爲兩部分,一部分爲基礎部分,一部分爲參數部分。工具

例如,上面的URL咱們劃分基礎部分是 http://tieba.baidu.com/p/3138733512,參數部分是 ?see_lz=1&pn=1post

2.頁面的抓取

熟悉了URL的格式,那就讓咱們用urllib2庫來試着抓取頁面內容吧。上一篇糗事百科咱們最後改爲了面向對象的編碼方式,此次咱們直接嘗試一下,定義一個類名叫BDTB(百度貼吧),一個初始化方法,一個獲取頁面的方法。測試

其中,有些帖子咱們想指定給程序是否要只看樓主,因此咱們把只看樓主的參數初始化放在類的初始化上,即init方法。另外,獲取頁面的方法咱們須要知道一個參數就是帖子頁碼,因此這個參數的指定咱們放在該方法中。優化

綜上,咱們初步構建出基礎代碼以下:ui

__author__ = 'CQC'
# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
 
#百度貼吧爬蟲類
class BDTB:
 
    #初始化,傳入基地址,是否只看樓主的參數
    def __init__(self,baseUrl,seeLZ):
        self.baseURL = baseUrl
        self.seeLZ = '?see_lz='+str(seeLZ)
 
    #傳入頁碼,獲取該頁帖子的代碼
    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"鏈接百度貼吧失敗,錯誤緣由",e.reason
                return None
 
baseURL = 'http://tieba.baidu.com/p/3138733512'
bdtb = BDTB(baseURL,1)
bdtb.getPage(1)

運行代碼,咱們能夠看到屏幕上打印出了這個帖子第一頁樓主發言的全部內容,形式爲HTML代碼。

20150219162232

3.提取相關信息

1)提取帖子標題

首先,讓咱們提取帖子的標題。

在瀏覽器中審查元素,或者按F12,查看頁面源代碼,咱們找到標題所在的代碼段,能夠發現這個標題的HTML代碼是

<h1 class="core_title_txt  " title="純原創我心中的NBA2014-2015賽季現役50大" style="width: 396px">純原創我心中的NBA2014-2015賽季現役50大</h1>

因此咱們想提取<h1>標籤中的內容,同時還要指定這個class肯定惟一,由於h1標籤實在太多啦。

正則表達式以下

<h1 class="core_title_txt.*?>(.*?)</h1>

因此,咱們增長一個獲取頁面標題的方法

#獲取帖子標題
def getTitle(self):
    page = self.getPage(1)
    pattern = re.compile('<h1 class="core_title_txt.*?>(.*?)</h1>',re.S)
    result = re.search(pattern,page)
    if result:
        #print result.group(1)  #測試輸出
        return result.group(1).strip()
    else:
        return None

2)提取帖子頁數

一樣地,帖子總頁數咱們也能夠經過分析頁面中的共?頁來獲取。因此咱們的獲取總頁數的方法以下

#獲取帖子一共有多少頁
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)  #測試輸出
        return result.group(1).strip()
    else:
        return None

3)提取正文內容

審查元素,咱們能夠看到百度貼吧每一層樓的主要內容都在<div id=」post_content_xxxx」></div>標籤裏面,因此咱們能夠寫以下的正則表達式

<div id="post_content_.*?>(.*?)</div>

相應地,獲取頁面全部樓層數據的方法能夠寫成以下方法

#獲取每一層樓的內容,傳入頁面內容
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

好,咱們運行一下結果看一下

20150219235120

真是醉了,還有一大片換行符和圖片符,好口怕!既然這樣,咱們就要對這些文本進行處理,把各類各樣複雜的標籤給它剔除掉,還原精華內容,把文本處理寫成一個方法也能夠,不過爲了實現更好的代碼架構和代碼重用,咱們能夠考慮把標籤等的處理寫做一個類。

那咱們就叫它Tool(工具類吧),裏面定義了一個方法,叫replace,是替換各類標籤的。在類中定義了幾個正則表達式,主要利用了re.sub方法對文本進行匹配後而後替換。具體的思路已經寫到註釋中,你們能夠看一下這個類

import re
 
#處理頁面標籤類
class Tool:
    #去除img標籤,7位長空格
    removeImg = re.compile('<img.*?>| {7}|')
    #刪除超連接標籤
    removeAddr = re.compile('<a.*?>|</a>')
    #把換行的標籤換爲\n
    replaceLine = re.compile('<tr>|<div>|</div>|</p>')
    #將表格製表<td>替換爲\t
    replaceTD= re.compile('<td>')
    #把段落開頭換爲\n加空兩格
    replacePara = re.compile('<p.*?>')
    #將換行符或雙換行符替換爲\n
    replaceBR = re.compile('<br><br>|<br>')
    #將其他標籤剔除
    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()將先後多餘內容刪除
        return x.strip()

在使用時,咱們只須要初始化一下這個類,而後調用replace方法便可。

如今總體代碼是以下這樣子的,如今個人代碼是寫到這樣子的

__author__ = 'CQC'
# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
 
#處理頁面標籤類
class Tool:
    #去除img標籤,7位長空格
    removeImg = re.compile('<img.*?>| {7}|')
    #刪除超連接標籤
    removeAddr = re.compile('<a.*?>|</a>')
    #把換行的標籤換爲\n
    replaceLine = re.compile('<tr>|<div>|</div>|</p>')
    #將表格製表<td>替換爲\t
    replaceTD= re.compile('<td>')
    #把段落開頭換爲\n加空兩格
    replacePara = re.compile('<p.*?>')
    #將換行符或雙換行符替換爲\n
    replaceBR = re.compile('<br><br>|<br>')
    #將其他標籤剔除
    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()將先後多餘內容刪除
        return x.strip()
 
 
#百度貼吧爬蟲類
class BDTB:
 
    #初始化,傳入基地址,是否只看樓主的參數
    def __init__(self,baseUrl,seeLZ):
        self.baseURL = baseUrl
        self.seeLZ = '?see_lz='+str(seeLZ)
        self.tool = Tool()
    #傳入頁碼,獲取該頁帖子的代碼
    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"鏈接百度貼吧失敗,錯誤緣由",e.reason
                return None
 
    #獲取帖子標題
    def getTitle(self):
        page = self.getPage(1)
        pattern = re.compile('<h1 class="core_title_txt.*?>(.*?)</h1>',re.S)
        result = re.search(pattern,page)
        if result:
            #print result.group(1)  #測試輸出
            return result.group(1).strip()
        else:
            return None
 
    #獲取帖子一共有多少頁
    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)  #測試輸出
            return result.group(1).strip()
        else:
            return None
 
    #獲取每一層樓的內容,傳入頁面內容
    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))

咱們嘗試一下,從新再看一下效果,這下通過處理以後應該就沒問題了,是否是感受好酸爽!

20150220000103

4)替換樓層

至於這個問題,我感受直接提取樓層沒什麼必要呀,由於只看樓主的話,有些樓層的編號是間隔的,因此咱們獲得的樓層序號是不連續的,這樣咱們保存下來也沒什麼用。

因此能夠嘗試下面的方法:

1.每打印輸出一段樓層,寫入一行橫線來間隔,或者換行符也好。

2.試着從新編一個樓層,按照順序,設置一個變量,每打印出一個結果變量加一,打印出這個變量當作樓層。

這裏咱們嘗試一下吧,看看效果怎樣

把getContent方法修改以下

#獲取每一層樓的內容,傳入頁面內容
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

運行一下看看效果

20150220000947

嘿嘿,效果還不錯吧,感受真酸爽!接下來咱們完善一下,而後寫入文件

4.寫入文件

最後即是寫入文件的過程,過程很簡單,就幾句話的代碼而已,主要是利用瞭如下兩句

file = open(「tb.txt」,」w」)

file.writelines(obj)

這裏再也不贅述,稍後直接貼上完善以後的代碼。

5.完善代碼

如今咱們對代碼進行優化,重構,在一些地方添加必要的打印信息,整理以下

__author__ = 'CQC'
# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
 
#處理頁面標籤類
class Tool:
    #去除img標籤,7位長空格
    removeImg = re.compile('<img.*?>| {7}|')
    #刪除超連接標籤
    removeAddr = re.compile('<a.*?>|</a>')
    #把換行的標籤換爲\n
    replaceLine = re.compile('<tr>|<div>|</div>|</p>')
    #將表格製表<td>替換爲\t
    replaceTD= re.compile('<td>')
    #把段落開頭換爲\n加空兩格
    replacePara = re.compile('<p.*?>')
    #將換行符或雙換行符替換爲\n
    replaceBR = re.compile('<br><br>|<br>')
    #將其他標籤剔除
    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()將先後多餘內容刪除
        return x.strip()
 
 
#百度貼吧爬蟲類
class BDTB:
 
    #初始化,傳入基地址,是否只看樓主的參數
    def __init__(self,baseUrl,seeLZ,floorTag):
        #base連接地址
        self.baseURL = baseUrl
        #是否只看樓主
        self.seeLZ = '?see_lz='+str(seeLZ)
        #HTML標籤剔除工具類對象
        self.tool = Tool()
        #全局file變量,文件寫入操做對象
        self.file = None
        #樓層標號,初始爲1
        self.floor = 1
        #默認的標題,若是沒有成功獲取到標題的話則會用這個標題
        self.defaultTitle = u"百度貼吧"
        #是否寫入樓分隔符的標記
        self.floorTag = floorTag
 
    #傳入頁碼,獲取該頁帖子的代碼
    def getPage(self,pageNum):
        try:
            #構建URL
            url = self.baseURL+ self.seeLZ + '&pn=' + str(pageNum)
            request = urllib2.Request(url)
            response = urllib2.urlopen(request)
            #返回UTF-8格式編碼內容
            return response.read().decode('utf-8')
        #沒法鏈接,報錯
        except urllib2.URLError, e:
            if hasattr(e,"reason"):
                print u"鏈接百度貼吧失敗,錯誤緣由",e.reason
                return None
 
    #獲取帖子標題
    def getTitle(self,page):
        #獲得標題的正則表達式
        pattern = re.compile('<h1 class="core_title_txt.*?>(.*?)</h1>',re.S)
        result = re.search(pattern,page)
        if result:
            #若是存在,則返回標題
            return result.group(1).strip()
        else:
            return None
 
    #獲取帖子一共有多少頁
    def getPageNum(self,page):
        #獲取帖子頁數的正則表達式
        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
 
    #獲取每一層樓的內容,傳入頁面內容
    def getContent(self,page):
        #匹配全部樓層的內容
        pattern = re.compile('<div id="post_content_.*?>(.*?)</div>',re.S)
        items = re.findall(pattern,page)
        contents = []
        for item in items:
            #將文本進行去除標籤處理,同時在先後加入換行符
            content = "\n"+self.tool.replace(item)+"\n"
            contents.append(content.encode('utf-8'))
        return contents
 
    def setFileTitle(self,title):
        #若是標題不是爲None,即成功獲取到標題
        if title is not None:
            self.file = open(title + ".txt","w+")
        else:
            self.file = open(self.defaultTitle + ".txt","w+")
 
    def writeData(self,contents):
        #向文件寫入每一樓的信息
        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已失效,請重試"
            return
        try:
            print "該帖子共有" + str(pageNum) + "頁"
            for i in range(1,int(pageNum)+1):
                print "正在寫入第" + str(i) + "頁數據"
                page = self.getPage(i)
                contents = self.getContent(page)
                self.writeData(contents)
        #出現寫入異常
        except IOError,e:
            print "寫入異常,緣由" + e.message
        finally:
            print "寫入任務完成"
 
 
 
print u"請輸入帖子代號"
baseURL = 'http://tieba.baidu.com/p/' + str(raw_input(u'http://tieba.baidu.com/p/'))
seeLZ = raw_input("是否只獲取樓主發言,是輸入1,否輸入0\n")
floorTag = raw_input("是否寫入樓層信息,是輸入1,否輸入0\n")
bdtb = BDTB(baseURL,seeLZ,floorTag)
bdtb.start()

 

如今程序演示以下

20150220012351

完成以後,能夠查看一下當前目錄下多了一個以該帖子命名的txt文件,內容即是帖子的全部數據。

抓貼吧,就是這麼簡單和任性!

相關文章
相關標籤/搜索