Python爬蟲利器之Beautiful Soup的用法

大家好,上次我們實驗了爬取了糗事百科的段子,那么這次我們來嘗試一下爬取百度貼吧的帖子。與上一篇不同的是,這次我們需要用到文件的相關(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)注博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Beautiful Soup
    Muddy薅叔閱讀 191評論 0 0
  • 經(jīng)過多次嘗試,模擬登錄淘寶終于成功了,實在是不容易,淘寶的登錄加密和驗證太復雜了,煞費苦心,在此寫出來和大家一起分...
    追不到的那縷風閱讀 1,728評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,967評論 19 139
  • 我一直給你蘋果 而你給我的 永遠是干澀的蘋果核 你看 你一直那么自私
    xyzii閱讀 140評論 0 1
  • 我已經(jīng)做好準備和你告別,我已經(jīng)下定決心自己去改變,我準備卸下行囊能走多遠就走多遠,也許并非玩笑,說什么今生最好不再相見。
    6蟲閱讀 244評論 0 1