大家好,上次我們實驗了爬取了糗事百科的段子,那么這次我們來嘗試一下爬取百度貼吧的帖子。與上一篇不同的是,這次我們需要用到文件的相關(guān)操作。
本篇目標(biāo)
對百度貼吧的任意帖子進(jìn)行抓取
指定是否只抓取樓主發(fā)帖內(nèi)容
首先,我們先觀察一下百度貼吧的任意一個帖子。
比如: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
熟悉了 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 代碼。
http://wiki.jikexueyuan.com/project/python-crawler-guide/images/09.png" alt="" />
首先,讓我們提取帖子的標(biāo)題。
在瀏覽器中審查元素,或者按 F12,查看頁面源代碼,我們找到標(biāo)題所在的代碼段,可以發(fā)現(xiàn)這個標(biāo)題的 HTML 代碼是
<h1 class="core_title_txt " title="純原創(chuàng)我心中的NBA2014-2015賽季現(xiàn)役50大" style="width: 396px">純原創(chuàng)我心中的NBA2014-2015賽季現(xiàn)役50大</h1>
所以我們想提取
正則表達(dá)式如下
<h1 class="core_title_txt.*?>(.*?)</h1>
所以,我們增加一個獲取頁面標(biāo)題的方法
\#獲取帖子標(biāo)題
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
同樣地,帖子總頁數(shù)我們也可以通過分析頁面中的共?頁來獲取。所以我們的獲取總頁數(shù)的方法如下
\#獲取帖子一共有多少頁
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
審查元素,我們可以看到百度貼吧每一層樓的主要內(nèi)容都在
標(biāo)簽里面,所以我們可以寫如下的正則表達(dá)式<div id="post_content_.*?>(.*?)</div>
相應(yīng)地,獲取頁面所有樓層數(shù)據(jù)的方法可以寫成如下方法
\#獲取每一層樓的內(nèi)容,傳入頁面內(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
好,我們運行一下結(jié)果看一下
http://wiki.jikexueyuan.com/project/python-crawler-guide/images/10.png" alt="" />
真是醉了,還有一大片換行符和圖片符,好口怕!既然這樣,我們就要對這些文本進(jìn)行處理,把各種各樣復(fù)雜的標(biāo)簽給它剔除掉,還原精華內(nèi)容,把文本處理寫成一個方法也可以,不過為了實現(xiàn)更好的代碼架構(gòu)和代碼重用,我們可以考慮把標(biāo)簽等的處理寫作一個類。
那我們就叫它 Tool(工具類吧),里面定義了一個方法,叫 replace,是替換各種標(biāo)簽的。在類中定義了幾個正則表達(dá)式,主要利用了 re.sub 方法對文本進(jìn)行匹配后然后替換。具體的思路已經(jīng)寫到注釋中,大家可以看一下這個類
import re
\#處理頁面標(biāo)簽類
class Tool:
\#去除img標(biāo)簽,7位長空格
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>')
\#把段落開頭換為\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()
在使用時,我們只需要初始化一下這個類,然后調(diào)用 replace 方法即可。
現(xiàn)在整體代碼是如下這樣子的,現(xiàn)在我的代碼是寫到這樣子的
__author__ = 'CQC'
\# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
\#處理頁面標(biāo)簽類
class Tool:
\#去除img標(biāo)簽,7位長空格
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>')
\#把段落開頭換為\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()
\#百度貼吧爬蟲類
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
\#獲取帖子標(biāo)題
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
\#獲取每一層樓的內(nèi)容,傳入頁面內(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)過處理之后應(yīng)該就沒問題了,是不是感覺好酸爽!
http://wiki.jikexueyuan.com/project/python-crawler-guide/images/11.png" alt="" />
至于這個問題,我感覺直接提取樓層沒什么必要呀,因為只看樓主的話,有些樓層的編號是間隔的,所以我們得到的樓層序號是不連續(xù)的,這樣我們保存下來也沒什么用。
所以可以嘗試下面的方法:
這里我們嘗試一下吧,看看效果怎樣
把 getContent 方法修改如下
\#獲取每一層樓的內(nèi)容,傳入頁面內(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
運行一下看看效果
http://wiki.jikexueyuan.com/project/python-crawler-guide/images/12.png" alt="" />
嘿嘿,效果還不錯吧,感覺真酸爽!接下來我們完善一下,然后寫入文件
最后便是寫入文件的過程,過程很簡單,就幾句話的代碼而已,主要是利用了以下兩句
file = open(“tb.txt”,”w”)
file.writelines(obj)
這里不再贅述,稍后直接貼上完善之后的代碼。
現(xiàn)在我們對代碼進(jìn)行優(yōu)化,重構(gòu),在一些地方添加必要的打印信息,整理如下
__author__ = 'CQC'
\# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
\#處理頁面標(biāo)簽類
class Tool:
\#去除img標(biāo)簽,7位長空格
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>')
\#把段落開頭換為\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()
\#百度貼吧爬蟲類
class BDTB:
#初始化,傳入基地址,是否只看樓主的參數(shù)
def __init__(self,baseUrl,seeLZ,floorTag):
\#base鏈接地址
self.baseURL = baseUrl
\#是否只看樓主
self.seeLZ = '?see_lz='+str(seeLZ)
\#HTML標(biāo)簽剔除工具類對象
self.tool = Tool()
\#全局file變量,文件寫入操作對象
self.file = None
\#樓層標(biāo)號,初始為1
self.floor = 1
\#默認(rèn)的標(biāo)題,如果沒有成功獲取到標(biāo)題的話則會用這個標(biāo)題
self.defaultTitle = u"百度貼吧"
\#是否寫入樓分隔符的標(biāo)記
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
\#獲取帖子標(biāo)題
def getTitle(self,page):
\#得到標(biāo)題的正則表達(dá)式
pattern = re.compile('<h1 class="core_title_txt.*?>(.*?)</h1>',re.S)
result = re.search(pattern,page)
if result:
\#如果存在,則返回標(biāo)題
return result.group(1).strip()
else:
return None
\#獲取帖子一共有多少頁
def getPageNum(self,page):
\#獲取帖子頁數(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)容,傳入頁面內(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)簽處理,同時在前后加入換行符
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):
\#向文件寫入每一樓的信息
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)在程序演示如下
http://wiki.jikexueyuan.com/project/python-crawler-guide/images/13.png" alt="" />
完成之后,可以查看一下當(dāng)前目錄下多了一個以該帖子命名的 txt 文件,內(nèi)容便是帖子的所有數(shù)據(jù)。
抓貼吧,就是這么簡單和任性!