- Python爬蟲入門(1):綜述
- Python爬蟲入門(2):爬蟲基礎瞭解
- Python爬蟲入門(3):Urllib庫的基本使用
- Python爬蟲入門(4):Urllib庫的高階用法
- Python爬蟲入門(5):URLError異常處理
- Python爬蟲入門(6):Cookie的使用
- Python爬蟲入門(7):正規表示式
- Python爬蟲入門(8):Beautiful Soup的用法
- Python爬蟲實戰(1):爬取糗事百科段子
- Python爬蟲實戰(2):百度貼吧帖子
- Python爬蟲實戰(3):計算大學本學期績點
- Python爬蟲實戰(3):計算大學本學期績點
- Python爬蟲實戰(5):模擬登入淘寶並獲取所有訂單
大家好,上次我們實驗了爬取了糗事百科的段子,那麼這次我們來嘗試一下爬取百度貼吧的帖子。與上一篇不同的是,這次我們需要用到檔案的相關操作。
本篇目標
1.對百度貼吧的任意帖子進行抓取
2.指定是否只抓取樓主發帖內容
3.將抓取到的內容分析並儲存到檔案
1.URL格式的確定
首先,我們先觀察一下百度貼吧的任意一個帖子。
比如:http://tieba.baidu.com/p/3138733512?see_lz=1&pn=1,這是一個關於NBA50大的盤點,分析一下這個地址。
1 2 3 4 |
http:// 代表資源傳輸使用http協議 tieba.baidu.com 是百度的二級域名,指向百度貼吧的伺服器。 /p/3138733512 是伺服器某個資源,即這個帖子的地址定位符 see_lz和pn是該URL的兩個引數,分別代表了只看樓主和帖子頁碼,等於1表示該條件為真 |
所以我們可以把URL分為兩部分,一部分為基礎部分,一部分為引數部分。
例如,上面的URL我們劃分基礎部分是 http://tieba.baidu.com/p/3138733512,引數部分是 ?see_lz=1&pn=1
2.頁面的抓取
熟悉了URL的格式,那就讓我們用urllib2庫來試著抓取頁面內容吧。上一篇糗事百科我們最後改成了物件導向的編碼方式,這次我們直接嘗試一下,定義一個類名叫BDTB(百度貼吧),一個初始化方法,一個獲取頁面的方法。
其中,有些帖子我們想指定給程式是否要只看樓主,所以我們把只看樓主的引數初始化放在類的初始化上,即init方法。另外,獲取頁面的方法我們需要知道一個引數就是帖子頁碼,所以這個引數的指定我們放在該方法中。
綜上,我們初步構建出基礎程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
__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程式碼。
3.提取相關資訊
1)提取帖子標題
首先,讓我們提取帖子的標題。
在瀏覽器中審查元素,或者按F12,檢視頁面原始碼,我們找到標題所在的程式碼段,可以發現這個標題的HTML程式碼是
1 |
<h1 title="純原創我心中的NBA2014-2015賽季現役50大" style="width: 396px">純原創我心中的NBA2014-2015賽季現役50大</h1> |
所以我們想提取<h1>標籤中的內容,同時還要指定這個class確定唯一,因為h1標籤實在太多啦。
正規表示式如下
1 |
<h1 class="core_title_txt.*?>(.*?)</h1> |
所以,我們增加一個獲取頁面標題的方法
1 2 3 4 5 6 7 8 9 10 |
#獲取帖子標題 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)提取帖子頁數
同樣地,帖子總頁數我們也可以通過分析頁面中的共?頁來獲取。所以我們的獲取總頁數的方法如下
1 2 3 4 5 6 7 8 9 10 |
#獲取帖子一共有多少頁 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>標籤裡面,所以我們可以寫如下的正規表示式
1 |
<div id="post_content_.*?>(.*?)</div> |
相應地,獲取頁面所有樓層資料的方法可以寫成如下方法
1 2 3 4 5 6 |
#獲取每一層樓的內容,傳入頁面內容 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 |
好,我們執行一下結果看一下
真是醉了,還有一大片換行符和圖片符,好口怕!既然這樣,我們就要對這些文字進行處理,把各種各樣複雜的標籤給它剔除掉,還原精華內容,把文字處理寫成一個方法也可以,不過為了實現更好的程式碼架構和程式碼重用,我們可以考慮把標籤等的處理寫作一個類。
那我們就叫它Tool(工具類吧),裡面定義了一個方法,叫replace,是替換各種標籤的。在類中定義了幾個正規表示式,主要利用了re.sub方法對文字進行匹配後然後替換。具體的思路已經寫到註釋中,大家可以看一下這個類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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方法即可。
現在整體程式碼是如下這樣子的,現在我的程式碼是寫到這樣子的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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() |
我們嘗試一下,重新再看一下效果,這下經過處理之後應該就沒問題了,是不是感覺好酸爽!
4)替換樓層
至於這個問題,我感覺直接提取樓層沒什麼必要呀,因為只看樓主的話,有些樓層的編號是間隔的,所以我們得到的樓層序號是不連續的,這樣我們儲存下來也沒什麼用。
所以可以嘗試下面的方法:
1.每列印輸出一段樓層,寫入一行橫線來間隔,或者換行符也好。
2.試著重新編一個樓層,按照順序,設定一個變數,每列印出一個結果變數加一,列印出這個變數當做樓層。
這裡我們嘗試一下吧,看看效果怎樣
把getContent方法修改如下
1 2 3 4 5 6 7 8 9 |
#獲取每一層樓的內容,傳入頁面內容 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 |
執行一下看看效果
嘿嘿,效果還不錯吧,感覺真酸爽!接下來我們完善一下,然後寫入檔案
4.寫入檔案
最後便是寫入檔案的過程,過程很簡單,就幾句話的程式碼而已,主要是利用了以下兩句
file = open(“tb.txt”,”w”)
file.writelines(obj)
這裡不再贅述,稍後直接貼上完善之後的程式碼。
5.完善程式碼
現在我們對程式碼進行優化,重構,在一些地方新增必要的列印資訊,整理如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
__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() |
現在程式演示如下
完成之後,可以檢視一下當前目錄下多了一個以該帖子命名的txt檔案,內容便是帖子的所有資料。
抓貼吧,就是這麼簡單和任性!
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式