天天看點

Python爬蟲實戰(1)——百度貼吧抓取文章并儲存内容和圖檔

最近在網上看了很多的爬蟲腳本,寫的參差不齊,但是其中有很多寫的非常的優秀,代碼品質很高,規範性也很好,很具有代表性,非常值得我們去學習!~

寫好一個python爬蟲需要有以下幾個必備條件:

1、足夠好的代碼規範(等号前後加空格、逗号後加空格等等),結構性封裝性好,重用性高。這需要時間和很多的訓練。

2、在抓取網頁的html源碼後,快速找到自己想要的目标,準确的寫出它的正規表達式。

3、得到目标内容後,準确進行一系列儲存抓取等後續操作。

之前的文章中介紹過url,也就是網址,在我們抓取網頁的過程中也要非常注意網頁的各項細節,而且需要我們不斷的積累經驗,也需要細心的分析:

例:

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

這是我們要抓去的一個百度貼吧文章的url,主要是後面的一小串看的不是很明白吧。下面帶大家細細分析一下:

“http://“                             代表資源傳輸使用http協定。

"tieba.baidu.com”          這是百度貼吧的二級域名,指向貼吧的伺服器。

"/p/3138733512"           這是伺服器中的某一個資源,也就是我們目标文章的定位符。

"?see_lz=1&pn=1"      前半部分指的是是否隻看樓主,後半部分指的是pagenumber頁數第一頁。

這樣我們就完整分析了一個網頁,比如說很多人喜歡看鬥魚:https://www.douyu.com/directory/game/LOL,或者CSDN的編輯頁面:http://write.blog.csdn.net/postedit?type=edit這個網頁應該很好分析吧,以後抓取各種網頁的時候都要學會怎樣去分析一個網頁的url,這樣我們才能寫出更加壯碩的小爬蟲~~。

我們可以将一個網頁拆分成基礎部分和參數部分,比如:

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

我們将它拆為基礎部分:http://tieiba.baidu.com/p/3138733512,參數部分為:?see_lz=1&pn=1

這樣做的目的使我們可以在一定程度上控制想要通路的頁面,通過修改參數就可以達到我們想要的目的。

下面我們來實作第一條:

在Python中我們也可以運用面向對象的方法,定義一個百度貼吧BDTB的類,其中包含初始化函數和各種不同的抓取函數,相當于用這個類給我們的小爬蟲做一身好看的衣服。在寫任何爬蟲的Python程式中,都可以用這種用類包裝的方法,相當于結構化了自己的程式,也使用了面向對象的方法。

我們就先來看看完整的程式:

#coding=utf-8
import urllib2
import re
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

# 處理頁面标簽類
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,lz):
        self.Baseurl = baseurl #基礎部分
        self.Lz = '?see_lz='+str(lz)  #是否隻看樓主參數部分
        self.tool = Tool()    #标簽處理類
        self.file = None
        self.floor = 1#######需要設定為全局變量,在寫入檔案時用來分割樓層。

    def Getpage(self,pagenum):   #函數用來生成完整url,并抓取網頁傳回
        self.Pagenum = '&pn=' + str(pagenum)
        url = self.Baseurl+self.Lz+self.Pagenum  #生成完整url
        request = urllib2.Request(url)
        Mypage = urllib2.urlopen(request)   #使用request,擷取網頁内容
        return Mypage.read().decode('utf-8')  #将編碼方式解碼為utf-8

    def Getpagenum(self,page):  #函數用來抓取網頁中包含的文章最大頁數
        pattern = re.compile('<li class="l_reply_num.*?</span>.*?<span.*?>(.*?)</span>', re.S)
        pagenum = re.search(pattern, page)  #使用search()方法,抓取目标内容
        if pagenum:
            return pagenum.group(1).strip() #如果包含的話就傳回頁數(字元串形式)
        else:
            return None

    def Gettitle(self,page):   #函數用來抓取網頁中包含的文章的标題,用來命名txt檔案
        pattern = re.compile('<h3 class="core_title_txt.*?>(.*?)</h3>',re.S)
        result = re.search(pattern,page)
        if result:
            return result.group(1).strip()#注意search()和findall()的差別注意
        else:
            return None
#多次出現的格式,但是我們隻需要一個,并且不需要儲存成list格式,可以使用search()傳回一個字元串即可

    def Getimg(self,page):   #函數用來抓取網頁中包含的文章中的目标圖檔連結
        result_img = re.findall('<img class="BDE_Image" src="(.*?)" pic_ext=.*?>', page, re.S) #隻要是符合正規表達式的所有圖檔都需要儲存
        result_imgs = []
        for item in result_img:
            content = "\n" + self.tool.replace(item) + "\n"
            result_imgs.append(content.encode('utf-8'))   #換行處理更加清晰
        return result_imgs


    def Getcontent(self,page):    #函數用來抓取網頁中包含的文章中的目标樓層文字内容
        items = re.findall('<div id="post_content_.*?>(.*?)</div>',page, re.S)
        contents = []
        for item in items :
            content = "\n" + self.tool.replace(item) + "\n"   #這裡使用到了便簽的處理類
            contents.append(content.encode('utf-8'))  #換行處理更加清晰
        return contents

    def setfile(self,title):    #函數用來打開建立一個txt檔案用來存儲
        if title is not None:
            self.file = open(title + ".txt", "w+") #沒有這個檔案的話會話就自己建立
        else:
            self.file = open(u"百度貼吧" + ".txt","w+")

    def savefile(self,items):    #函數用來儲存目标樓層文字内容
        for item in items:
            floorline = str(self.floor) + u'樓' + '——————————————————————————————'
            self.file.write(floorline)
            self.file.write(item)
            self.floor += 1

    def saveimg(self,result_img):  #函數用來儲存目标樓層的目标圖檔連結
        for img in result_img:
            self.file.write(img)

    def start(self):    #開始函數,用來再次包裝所有的函數以達到将内容寫進txt檔案的目的
        page1 = self.Getpage(1)
        pagenum = self.Getpagenum(page1)
        title = self.Gettitle(page1)
        self.setfile(title)
        if pagenum == 0:
            print '該連結已失效或為無效連結'
        else:
            print '開始寫入資料'
            for i in range(1,int(pagenum)+1):
                print '正在寫入第'+str(i)+'頁資訊'
                page=self.Getpage(i)
                self.savefile(self.Getcontent(page))
                self.saveimg(self.Getimg(page))
            print '資料寫入完畢'

print u'請輸入文章連結'
baseurl = str(raw_input())
seeLZ = raw_input("是否隻擷取樓主發言,是輸入1,否輸入0\n")
bdtb = BDTB(baseurl,seeLZ)  #類的執行個體化
bdtb.start()
           

咱們先不管一開始的Tool類,看下面的BDTB類,用來包裝了我們所有的重要的函數。每個函數都有詳細的介紹。

其中有幾點需要着重注意的地方:

1、findall()和search()之間的差別和用法。

findall()函數:傳回的是所有滿足其正規表達式的内容,形式為清單。

search()函數:傳回的是第一個滿足條件的比對,形式為字元串或者是元組。

這裡我們就可以看出為什麼程式中有的需要使用findall,有的需要使用search()。例如:

我們要的是一個最大的頁數,但是玩過貼吧都知道在頁首和頁尾都會顯示最大頁數,我們用正規表達式就會比對到兩個地方的内容,如果我們使用findall()函數,傳回的是[5,5],但是我們隻需要一個簡單的5,就夠了,這就需要search()的使用了。

學會在什麼情況下分别使用這兩種函數也是比較重要的

2、result.group(1).strip()

這樣的表達是為了得到我們的目标結果。

group(),傳回的是比對到的一個或者多個子組,如果隻有一個參數,那麼就是字元串。多個參數傳回的就是元組。這裡的group(1)指的就是第一個參數。

strip(),預設删除空白符(包括'\n', '\r', '\t', ' ')。

3、抓去了網頁的html源碼以後還是要仔細的觀察、分析, 找到目标後,寫出合适的正規表達式也是一個重點所在!~

再說我們多出的Tool類,這個類是對很多哦文章分析得來的結果,你可以嘗試單獨輸出一下沒有經過标簽處理類的樓層文字内容,會有很多想不到的标簽出現,比如說空格、換行符、超連結等等,Tool類中給出了很多種情況。

在此我們又要讨論一種情況的發生,就是目标内容的内部包含了我們不想要的東西,比如說:“我喜歡<HVGgvjhhgu>Python”,我們想去除其中的<>内容,就需要用到re子產品的sub()函數處理我們的目标内容。這也算是比較精髓的一種内容啦。

對于這個程式的改寫,也非常容易,隻要替換掉一些正規表達式即可。或者不想處理百度貼吧的文章,想抓去淘寶連結的美圖,也可以通過稍加修改來得到。

本例部分結果如下:

Python爬蟲實戰(1)——百度貼吧抓取文章并儲存内容和圖檔

更新一下抓取其中的主要圖檔的例子,上一次我們已經得到了其中的圖檔連結,下面就是儲存抓取了:

#coding=utf-8
import urllib2
import urllib
import re
import os
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

# 處理頁面标簽類
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,lz):
        self.Baseurl = baseurl #基礎部分
        self.Lz = '?see_lz='+str(lz)  #是否隻看樓主參數部分
        self.tool = Tool()    #标簽處理類
        self.file = None
        self.floor = 1#######需要設定為全局變量,在寫入檔案時用來分割樓層。
        self.name = 1

    def Getpage(self,pagenum):   #函數用來生成完整url,并抓取網頁傳回
        self.Pagenum = '&pn=' + str(pagenum)
        url = self.Baseurl+self.Lz+self.Pagenum  #生成完整url
        request = urllib2.Request(url)
        Mypage = urllib2.urlopen(request)   #使用request,擷取網頁内容
        return Mypage.read().decode('utf-8')  #将編碼方式解碼為utf-8

    def Getpagenum(self,page):  #函數用來抓取網頁中包含的文章最大頁數
        pattern = re.compile('<li class="l_reply_num.*?</span>.*?<span.*?>(.*?)</span>', re.S)
        pagenum = re.search(pattern, page)  #使用search()方法,抓取目标内容
        if pagenum:
            return pagenum.group(1).strip() #如果包含的話就傳回頁數(字元串形式)
        else:
            return None

    def Gettitle(self,page):   #函數用來抓取網頁中包含的文章的标題,用來命名txt檔案
        pattern = re.compile('<h3 class="core_title_txt.*?>(.*?)</h3>',re.S)
        result = re.search(pattern,page)
        if result:
            return result.group(1).strip()#注意search()和findall()的差別注意
        else:
            return None
#多次出現的格式,但是我們隻需要一個,并且不需要儲存成list格式,可以使用search()傳回一個字元串即可

    def Getimg(self,page):   #函數用來抓取網頁中包含的文章中的目标圖檔連結
        result_img = re.findall('<img class="BDE_Image" src="(.*?)" pic_ext=.*?>', page, re.S) #隻要是符合正規表達式的所有圖檔都需要儲存
        result_imgs = []
        for item in result_img:
            content = "\n" + self.tool.replace(item) + "\n"
            result_imgs.append(content.encode('utf-8'))   #換行處理更加清晰
        return result_imgs

    def Getcontent(self,page):    #函數用來抓取網頁中包含的文章中的目标樓層文字内容
        items = re.findall('<div id="post_content_.*?>(.*?)</div>',page, re.S)
        contents = []
        for item in items :
            content = "\n" + self.tool.replace(item) + "\n"
            contents.append(content.encode('utf-8'))  #換行處理更加清晰
        return contents

    def setfile(self,title):    #函數用來打開建立一個txt檔案用來存儲
        if title is not None:
            self.file = open(title + ".txt", "w+") #沒有這個檔案的話會話就自己建立
        else:
            self.file = open(u"百度貼吧" + ".txt","w+")

    def savefile(self,items):    #函數用來儲存目标樓層文字内容
        for item in items:
            floorline = str(self.floor) + u'樓' + '——————————————————————————————'
            self.file.write(floorline)
            self.file.write(item)
            self.floor += 1

    def saveimg(self,result_img):  #函數用來儲存目标樓層的目标圖檔連結
        for img in result_img:
            self.file.write(img)

    def saveImg(self, imageURL, fileName):
        u = urllib.urlopen(imageURL)
        data = u.read()
        f = open(fileName, 'wb')
        f.write(data)
        f.close()

    def saveImgs(self, images): # 儲存多張寫真圖檔
        print u"發現共有", len(images), u"張照片"
        for imageURL in images:
            splitPath = imageURL.split('.')
            fTail = splitPath.pop()
            if len(fTail) > 3:
                fTail = "jpg"
            fileName = str(self.name) + "." + fTail
            self.saveImg(imageURL, fileName)
            self.name += 1

    def mkdir(self, path):# 建立新目錄
        isExists = os.path.exists(path)# 判斷結果
        if not isExists:
            # 如果不存在則建立目錄
            print u"偷偷建立了名字叫做", path, u'的檔案夾'
            # 建立目錄操作函數
            os.makedirs(path)
            return True
        else:
            # 如果目錄存在則不建立,并提示目錄已存在
            print u"名為", path, '的檔案夾已經建立成功'
            return False

    def start(self):    #開始函數,用來再次包裝所有的函數以達到将内容寫進txt檔案的目的
        page1 = self.Getpage(1)
        pagenum = self.Getpagenum(page1)
        title = self.Gettitle(page1)
        self.setfile(title)
        if pagenum == 0:
            print '該連結已失效或為無效連結'
        else:
            print '開始寫入資料'
            for i in range(1,int(pagenum)+1):
                print '正在寫入第'+str(i)+'頁資訊'
                page = self.Getpage(i)
                self.mkdir(str(i))
                self.savefile(self.Getcontent(page))
                self.saveImgs(self.Getimg(page))
            print '資料寫入完畢'

print u'請輸入文章連結'
baseurl = str(raw_input())
seeLZ = raw_input("是否隻擷取樓主發言,是輸入1,否輸入0\n")
bdtb = BDTB(baseurl,seeLZ)  #類的執行個體化
bdtb.start()