大家好,上次我們實驗了爬取了糗事百科的段子,那么這次我們來嘗試一下爬取百度貼吧的帖子。與上一篇不同的是,這次我們需要用到文件的相關(guān)操作。
本篇目標
1.對百度貼吧的任意帖子進行抓取
2.指定是否只抓取樓主發(fā)帖內(nèi)容
3.將抓取到的內(nèi)容分析并保存到文件
1.URL格式的確定
首先,我們先觀察一下百度貼吧的任意一個帖子。
比如:http://tieba.baidu.com/p/3138733512?see_lz=1&pn=1,這是一個關(guān)于NBA50大的盤點,分析一下這個地址。
http://??代表資源傳輸使用http協(xié)議
tieba.baidu.com?是百度的二級域名,指向百度貼吧的服務(wù)器。
/p/3138733512?是服務(wù)器某個資源,即這個帖子的地址定位符
see_lz和pn是該URL的兩個參數(shù),分別代表了只看樓主和帖子頁碼,等于1表示該條件為真
所以我們可以把URL分為兩部分,一部分為基礎(chǔ)部分,一部分為參數(shù)部分。
例如,上面的URL我們劃分基礎(chǔ)部分是http://tieba.baidu.com/p/3138733512,參數(shù)部分是?see_lz=1&pn=1
2.頁面的抓取
熟悉了URL的格式,那就讓我們用urllib2庫來試著抓取頁面內(nèi)容吧。上一篇糗事百科我們最后改成了面向?qū)ο蟮木幋a方式,這次我們直接嘗試一下,定義一個類名叫BDTB(百度貼吧),一個初始化方法,一個獲取頁面的方法。
其中,有些帖子我們想指定給程序是否要只看樓主,所以我們把只看樓主的參數(shù)初始化放在類的初始化上,即init方法。另外,獲取頁面的方法我們需要知道一個參數(shù)就是帖子頁碼,所以這個參數(shù)的指定我們放在該方法中。
綜上,我們初步構(gòu)建出基礎(chǔ)代碼如下:
__author__?=?'CQC'
#?-*-?coding:utf-8?-*-
import?urllib
import?urllib2
import?re
#百度貼吧爬蟲類
class?BDTB:
#初始化,傳入基地址,是否只看樓主的參數(shù)
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)
運行代碼,我們可以看到屏幕上打印出了這個帖子第一頁樓主發(fā)言的所有內(nèi)容,形式為HTML代碼。
3.提取相關(guān)信息
1)提取帖子標題
首先,讓我們提取帖子的標題。
在瀏覽器中審查元素,或者按F12,查看頁面源代碼,我們找到標題所在的代碼段,可以發(fā)現(xiàn)這個標題的HTML代碼是
純原創(chuàng)我心中的NBA2014-2015賽季現(xiàn)役50大
所以我們想提取
標簽中的內(nèi)容,同時還要指定這個class確定唯一,因為h1標簽實在太多啦。
正則表達式如下
(.*?)
所以,我們增加一個獲取頁面標題的方法
#獲取帖子標題
def?getTitle(self):
page?=?self.getPage(1)
pattern?=?re.compile('(.*?)',re.S)
result?=?re.search(pattern,page)
if?result:
#print?result.group(1)??#測試輸出
return?result.group(1).strip()
else:
return?None
2)提取帖子頁數(shù)
同樣地,帖子總頁數(shù)我們也可以通過分析頁面中的共?頁來獲取。所以我們的獲取總頁數(shù)的方法如下
#獲取帖子一共有多少頁
def?getPageNum(self):
page?=?self.getPage(1)
pattern?=?re.compile('.*?(.*?)',re.S)
result?=?re.search(pattern,page)
if?result:
#print?result.group(1)??#測試輸出
return?result.group(1).strip()
else:
return?None
3)提取正文內(nèi)容
審查元素,我們可以看到百度貼吧每一層樓的主要內(nèi)容都在
標簽里面,所以我們可以寫如下的正則表達式(.*?)
相應(yīng)地,獲取頁面所有樓層數(shù)據(jù)的方法可以寫成如下方法
#獲取每一層樓的內(nèi)容,傳入頁面內(nèi)容
def?getContent(self,page):
pattern?=?re.compile('(.*?)',re.S)
items?=?re.findall(pattern,page)
for?item?in?items:
print?item
好,我們運行一下結(jié)果看一下
真是醉了,還有一大片換行符和圖片符,好口怕!既然這樣,我們就要對這些文本進行處理,把各種各樣復雜的標簽給它剔除掉,還原精華內(nèi)容,把文本處理寫成一個方法也可以,不過為了實現(xiàn)更好的代碼架構(gòu)和代碼重用,我們可以考慮把標簽等的處理寫作一個類。
那我們就叫它Tool(工具類吧),里面定義了一個方法,叫replace,是替換各種標簽的。在類中定義了幾個正則表達式,主要利用了re.sub方法對文本進行匹配后然后替換。具體的思路已經(jīng)寫到注釋中,大家可以看一下這個類
import?re
#處理頁面標簽類
class?Tool:
#去除img標簽,7位長空格
removeImg?=?re.compile('|?{7}|')
#刪除超鏈接標簽
removeAddr?=?re.compile('|')
#把換行的標簽換為\n
替換為\t
')
#把段落開頭換為\n加空兩格
replacePara?=?re.compile('')
#將換行符或雙換行符替換為\n
replaceBR?=?re.compile('
|
')
#將其余標簽剔除
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()
在使用時,我們只需要初始化一下這個類,然后調(diào)用replace方法即可。
現(xiàn)在整體代碼是如下這樣子的,現(xiàn)在我的代碼是寫到這樣子的
__author__?=?'CQC'
#?-*-?coding:utf-8?-*-
import?urllib
import?urllib2
import?re
#處理頁面標簽類
class?Tool:
#去除img標簽,7位長空格
removeImg?=?re.compile('|?{7}|')
#刪除超鏈接標簽
removeAddr?=?re.compile('|')
#把換行的標簽換為\n
替換為\t
')
#把段落開頭換為\n加空兩格
replacePara?=?re.compile('')
#將換行符或雙換行符替換為\n
replaceBR?=?re.compile('
|
')
#將其余標簽剔除
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()
#百度貼吧爬蟲類
class?BDTB:
#初始化,傳入基地址,是否只看樓主的參數(shù)
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('(.*?)',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('.*?(.*?)',re.S)
result?=?re.search(pattern,page)
if?result:
#print?result.group(1)??#測試輸出
return?result.group(1).strip()
else:
return?None
#獲取每一層樓的內(nèi)容,傳入頁面內(nèi)容
def?getContent(self,page):
pattern?=?re.compile('(.*?)',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)過處理之后應(yīng)該就沒問題了,是不是感覺好酸爽!
4)替換樓層
至于這個問題,我感覺直接提取樓層沒什么必要呀,因為只看樓主的話,有些樓層的編號是間隔的,所以我們得到的樓層序號是不連續(xù)的,這樣我們保存下來也沒什么用。
所以可以嘗試下面的方法:
1.每打印輸出一段樓層,寫入一行橫線來間隔,或者換行符也好。
2.試著重新編一個樓層,按照順序,設(shè)置一個變量,每打印出一個結(jié)果變量加一,打印出這個變量當做樓層。
這里我們嘗試一下吧,看看效果怎樣
把getContent方法修改如下
#獲取每一層樓的內(nèi)容,傳入頁面內(nèi)容
def?getContent(self,page):
pattern?=?re.compile('(.*?)',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.完善代碼
現(xiàn)在我們對代碼進行優(yōu)化,重構(gòu),在一些地方添加必要的打印信息,整理如下
__author__?=?'CQC'
#?-*-?coding:utf-8?-*-
import?urllib
import?urllib2
import?re
#處理頁面標簽類
class?Tool:
#去除img標簽,7位長空格
removeImg?=?re.compile('|?{7}|')
#刪除超鏈接標簽
removeAddr?=?re.compile('|')
#把換行的標簽換為\n
替換為\t
')
#把段落開頭換為\n加空兩格
replacePara?=?re.compile('')
#將換行符或雙換行符替換為\n
replaceBR?=?re.compile('
|
')
#將其余標簽剔除
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()
#百度貼吧爬蟲類
class?BDTB:
#初始化,傳入基地址,是否只看樓主的參數(shù)
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:
#構(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')
#無法連接,報錯
except?urllib2.URLError,?e:
if?hasattr(e,"reason"):
print?u"連接百度貼吧失敗,錯誤原因",e.reason
return?None
#獲取帖子標題
def?getTitle(self,page):
#得到標題的正則表達式
pattern?=?re.compile('(.*?)',re.S)
result?=?re.search(pattern,page)
if?result:
#如果存在,則返回標題
return?result.group(1).strip()
else:
return?None
#獲取帖子一共有多少頁
def?getPageNum(self,page):
#獲取帖子頁數(shù)的正則表達式
pattern?=?re.compile('.*?(.*?)',re.S)
result?=?re.search(pattern,page)
if?result:
return?result.group(1).strip()
else:
return?None
#獲取每一層樓的內(nèi)容,傳入頁面內(nèi)容
def?getContent(self,page):
#匹配所有樓層的內(nèi)容
pattern?=?re.compile('(.*?)',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)?+?"頁數(shù)據(jù)"
page?=?self.getPage(i)
contents?=?self.getContent(page)
self.writeData(contents)
#出現(xiàn)寫入異常
except?IOError,e:
print?"寫入異常,原因"?+?e.message
finally:
print?"寫入任務(wù)完成"
print?u"請輸入帖子代號"
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("是否寫入樓層信息,是輸入1,否輸入0\n")
bdtb?=?BDTB(baseURL,seeLZ,floorTag)
bdtb.start()
現(xiàn)在程序演示如下
完成之后,可以查看一下當前目錄下多了一個以該帖子命名的txt文件,內(nèi)容便是帖子的所有數(shù)據(jù)。
抓貼吧,就是這么簡單和任性!
更多web知識和爬蟲知識可以關(guān)注博客