在爬取糗事百科的段子后,我又在知乎上找了一個爬取百度貼吧帖子的實例,為了鞏固提升已掌握的爬蟲知識,於是我打算自己也做一個。
實現目標:1,爬取樓主所發的帖子
2,顯示所爬去的樓層以及帖子題目
3,將爬取的內容寫入到文件里,並實現動態顯示爬取進度
實現工具:python的requests庫和正則表達式以及bs4庫
首先我們爬取的帖子網址為:https://tieba.baidu.com/p/3138733512?see_lz=1&pn=1,該網址是只看樓主的帖子的網址,因此該網站的源代碼內容均為樓主所發貼的內容,爬取起來也比較方便。我們發現需要爬取的帖子一共有5頁,我們可以通過for循環來進行對每一頁信息的爬取。
接下來我們來整體構建爬取的思路:
1,爬取該網頁的源代碼
2,用正則表達式提取所需內容
3,用正則匹配對所取內容進行精准修改以達到我們想要的內容
4,把內容寫入到文件並顯示寫入進度
下面來介紹每一步的具體實現:
首先是獲取源代碼,這個已經比較簡單了,大多數獲取源代碼的方式都可以用這段代碼來實現:
def getHTMLText(url): try: user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = {'User-Agent': user_agent} r = requests.get(url,headers = headers) r.raise_for_status() r.encoding = r.apparent_encoding return r.text except: return ""
其中的user_agent配置可以在網頁的源代碼中找到,其目的是將爬蟲進行偽裝成用戶以此來獲取更好的爬取體驗
接下來我們要通過正則表達式來獲取我們需要的“標題”,“帖子主要內容”以及“樓層”信息
通過分析源代碼我們發現“標題”在
<title>......</title>
中可以找到,“帖子主要內容”在
<div id="post_content_\d*" class="d_post_content j_d_post_content ">......</div>
中可以找到,“樓層”信息可以在
<span class="tail-info">......</span><span class="tail-info">
中找到。其中“.......”表示所要提取內容,我們分別用兩個函數來實現對此的提取
def printTitle(html): try: soup = BeautifulSoup(html, "html.parser") titleTag = soup.find_all('title') patten = re.compile(r'<title>(.*?)</title>', re.S) title = re.findall(patten, str(titleTag)) return title except: return ""
def fillUnivlist(lis,li,html): try: patten = re.compile(r'<div id="post_content_\d*" class="d_post_content j_d_post_content ">(.*?)</div>', re.S) nbaInfo = re.findall(patten, str(html)) pattenFloor = re.compile(r'<span class="tail-info">(\d*樓)</span><span class="tail-info">', re.S) floorText = re.findall(pattenFloor, str(html)) number = len(nbaInfo) for i in range(number): Info = textTools.remove(nbaInfo[i]) Info1 = textTools.remove(floorText[i]) lis.append(Info1) li.append(Info) except: return ""
我們對每個方法都用try except 來保證其強健性。
但是我們發現我們對所提取的帖子內容有很多多余的成分:
<img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=cb6ab1f8708b4710ce2ffdc4f3ccc3b2/06381f30e924b899d8ca30e16c061d950b7bf671.jpg" pic_ext="jpeg" pic_type="0" width="339" height="510"><br><br><br><br>50 驚喜新人王 <a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivS12AkS11WcjnMQsTddE2yXZInIi4k8KEu5449mWp1SxBADVCHPuUFSTGH+WZuV+ecUBG6CY6mAz/Zq1mzxbFxzAG+4Cm4FSU0=" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'邁卡威\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">邁卡威</a><br>上賽季數據<br>籃板 6.2 助攻 6.3 搶斷 1.9 蓋帽 0.6 失誤 3.5 犯規 3 得分 16.7<br><br><br> 新賽季第50位,我給上賽季的新人王<a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivS12AkS11WcjnMQsTddE2yXZInIi4k8KEu5449mWp1SxBADVCHPuUFSTGH+WZuV+ecUBG6CY6mAz/Zq1mzxbFxzAG+4Cm4FSU0=" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'邁卡威\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">邁卡威</a>。 上賽季邁卡威在徹底重建的<a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivTbCBRGuF91e6cwvXwi+nOsUCFQWyjKvntqT9uy6c+e1s3eo9XM+kBUaJGaqtq7WOznXcLnooXruQBvuApuBUlN" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'76人\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">76人</a>中迅速掌握了球隊,一開始就三雙搞定了熱火贏得了萬千眼球。后來也屢屢有經驗的表現,新秀賽季就拿過三雙的球員不多,邁卡威現在可以說在76人站穩了腳跟。<br> 作為上賽季弱隊的老大,<a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivS12AkS11WcjnMQsTddE2yXZInIi4k8KEu5449mWp1SxBADVCHPuUFSTGH+WZuV+ecUBG6CY6mAz/Zq1mzxbFxzAG+4Cm4FSU0=" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'邁卡威\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">邁卡威</a>刷出了不錯的數據,但我們靜下心來看一看他,還是發現他有很多問題。首先,投籃偏弱剛剛40%的命中率和慘淡的26%的三分命中率肯定是不合格的!加之身體瘦弱,個字高大橫移速度一般,防守端並沒有數據表現得這么好!作為控衛失誤偏多,離巨星還是有一定的差距,小子你是一飛沖天,還是迅速隕落,就看你的努力了!<br> 說完缺點,來說說優點,作為后衛籃板球非常突出,高大的身形能較好的影響對方的出手,也能發現己方的空位球員。突破雖然速度一般,但節奏感不錯,大局觀也在平均水准之上。提醒瘦而高大,不會投籃,突破節奏好,大局觀不錯!這在幾年前說出來是誰?沒錯斷腿前的<a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivT5ggWFC92MLwFHpDNBmn4rETPyFf5XUHwripOOA15C4U+GRIwDgEI46b99l0XyUM/jR49NyMTc/6qmUGNB+hoByExmB9N/65I=" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'利文斯頓\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">利文斯頓</a>! <br> 就球隊地位而言,<a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivS12AkS11WcjnMQsTddE2yXZInIi4k8KEu5449mWp1SxBADVCHPuUFSTGH+WZuV+ecUBG6CY6mAz/Zq1mzxbFxzAG+4Cm4FSU0=" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'邁卡威\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">邁卡威</a>現在是絕對的老大,球你想怎么玩就怎么玩,數據你想怎么刷就怎么刷!去年的潛力新人<a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivTKm3O5uii9sKBrDcAE8/xDK4qTjgNeQuFPhkSMA4BCOOm/fZdF8lDP40ePTcjE3P+qplBjQfoaAchMZgfTf+uS" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'諾爾\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">諾爾</a>是<a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivQdeSiO+EjvouPd1sAEaAOyK4qTjgNeQuFPhkSMA4BCOOm/fZdF8lDP40ePTcjE3P+qplBjQfoaAchMZgfTf+uS" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'藍領\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">藍領</a>,其他人都可以清退,恩比德還受傷不能打,<a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivTbCBRGuF91e6cwvXwi+nOsUCFQWyjKvntqT9uy6c+e1s3eo9XM+kBUaJGaqtq7WOznXcLnooXruQBvuApuBUlN" class="ps_cb" target="_blank" onclick="$.stats.track(0, \'nlp_ps_word\',{obj_name:\'76人\'});$.stats.track(\'Pb_content_wordner\',\'ps_callback_statics\')">76人</a>隊的戰績怎么樣,就看你了!但是等到諾爾成熟(假如不是水貨),恩比德傷愈(他技術上不可能水,只是看傷病了)你就有一隊很好的內線組合了!你能把他們帶成什么成績,這時候就是考驗你邁卡威除了刷數據還有什么能力的時候了。'
這段提取的信息里有着大量多余的信息,因此需要我們進行細分,基本思路為將多余的信息用正則匹配出來,然后利用正則的替換方法把這些多余的內容替換為空格或者換行
在這里,我們來構建一個處理信息的類
class Tools: removeImg = re.compile('<img.*?>') removBr = re.compile('<br>') removeHef = re.compile('<a href.*?>') removeA = re.compile('</a>') removeClass = re.compile('<a class.*?>|<aclass.*?>') removeNull = re.compile(' ') def remove(self,te): te = re.sub(self.removeImg,'',te) te = re. sub(self.removBr,'\n',te) te = re.sub(self.removeHef,'',te) te = re.sub(self.removeA,'',te) te = re.sub(self.removeClass,'',te) te = re.sub(self.removeNull, '', te) return te
將剛才亂碼的信息經過這個類的處理后,我們可以得到下列信息:
50驚喜新人王邁卡威 上賽季數據 籃板6.2助攻6.3搶斷1.9蓋帽0.6失誤3.5犯規3得分16.7 新賽季第50位,我給上賽季的新人王邁卡威。上賽季邁卡威在徹底重建的76人中迅速掌握了球隊,一開始就三雙搞定了熱火贏得了萬千眼球。后來也屢屢有經驗的表現,新秀賽季就拿過三雙的球員不多,邁卡威現在可以說在76人站穩了腳跟。 作為上賽季弱隊的老大,邁卡威刷出了不錯的數據,但我們靜下心來看一看他,還是發現他有很多問題。首先,投籃偏弱剛剛40%的命中率和慘淡的26%的三分命中率肯定是不合格的!加之身體瘦弱,個字高大橫移速度一般,防守端並沒有數據表現得這么好!作為控衛失誤偏多,離巨星還是有一定的差距,小子你是一飛沖天,還是迅速隕落,就看你的努力了! 說完缺點,來說說優點,作為后衛籃板球非常突出,高大的身形能較好的影響對方的出手,也能發現己方的空位球員。突破雖然速度一般,但節奏感不錯,大局觀也在平均水准之上。提醒瘦而高大,不會投籃,突破節奏好,大局觀不錯!這在幾年前說出來是誰?沒錯斷腿前的利文斯頓! 就球隊地位而言,邁卡威現在是絕對的老大,球你想怎么玩就怎么玩,數據你想怎么刷就怎么刷!去年的潛力新人諾爾是藍領,其他人都可以清退,恩比德還受傷不能打,76人隊的戰績怎么樣,就看你了!但是等到諾爾成熟(假如不是水貨),恩比德傷愈(他技術上不可能水,只是看傷病了)你就有一隊很好的內線組合了!你能把他們帶成什么成績,這時候就是考驗你邁卡威除了刷數據還有什么能力的時候了。
這樣的表達效果就可以讓我清晰看到提取到的信息,所以這個類是成功的。接下來我們只需要將提取的信息輸出就行。
我們先寫一個寫入標題信息和主體內容的方法,因為標題只在第一個網頁上所以可以單獨寫一個方法
def writeText(titleText,fpath): try: with open(fpath, 'a', encoding='utf-8') as f: f.write(str(titleText) + '\n') f.write('\n') f.close() except: return ""
def writeUnivlist(lis,li,fpath,num): with open(fpath, 'a', encoding='utf-8') as f: for i in range(num): f.write(str(lis[i])+'\n') f.write('*'*50 + '\n') f.write(str(li[i]) + '\n') f.write('*' * 50 + '\n') f.close()
接下來我們只需要寫一個執行的主函數即可,我們定義一下所要寫入文件的路徑,然后先寫入文件的標題
count = 0 url = 'https://tieba.baidu.com/p/3138733512?see_lz=1&pn=1' output_file = 'D:/StockInfo.txt' html = getHTMLText(url) titleText = printTitle(html) writeText(titleText, output_file)
接下來利用for循環來實現對每個網頁的信息的輸入,並打印寫入文件的進度
for i in range(5): i = i + 1 lis = [] li = [] url = 'https://tieba.baidu.com/p/3138733512?see_lz=1&pn=' + str(i) html = getHTMLText(url) fillUnivlist(lis, li, html) writeUnivlist(lis, li, output_file, len(lis)) count = count + 1 print("\r當前進度: {:.2f}%".format(count * 100 / 5), end="")
以上就是爬取百度貼吧的帖子的所以內容,最后我認為如果我們將這些函數方法封裝成一個類,效果會更好。
以下是全部代碼
import requests from bs4 import BeautifulSoup import re class Tools: removeImg = re.compile('<img.*?>') removBr = re.compile('<br>') removeHef = re.compile('<a href.*?>') removeA = re.compile('</a>') removeClass = re.compile('<a class.*?>|<aclass.*?>') removeNull = re.compile(' ') def remove(self,te): te = re.sub(self.removeImg,'',te) te = re. sub(self.removBr,'\n',te) te = re.sub(self.removeHef,'',te) te = re.sub(self.removeA,'',te) te = re.sub(self.removeClass,'',te) te = re.sub(self.removeNull, '', te) return te textTools = Tools() def getHTMLText(url): try: user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = {'User-Agent': user_agent} r = requests.get(url,headers = headers) r.raise_for_status() r.encoding = r.apparent_encoding return r.text except: return "" def printTitle(html): try: soup = BeautifulSoup(html, "html.parser") titleTag = soup.find_all('title') patten = re.compile(r'<title>(.*?)</title>', re.S) title = re.findall(patten, str(titleTag)) return title except: return "" def fillUnivlist(lis,li,html): try: patten = re.compile(r'<div id="post_content_\d*" class="d_post_content j_d_post_content ">(.*?)</div>', re.S) nbaInfo = re.findall(patten, str(html)) pattenFloor = re.compile(r'<span class="tail-info">(\d*樓)</span><span class="tail-info">', re.S) floorText = re.findall(pattenFloor, str(html)) number = len(nbaInfo) for i in range(number): Info = textTools.remove(nbaInfo[i]) Info1 = textTools.remove(floorText[i]) lis.append(Info1) li.append(Info) except: return "" def writeText(titleText,fpath): try: with open(fpath, 'a', encoding='utf-8') as f: f.write(str(titleText) + '\n') f.write('\n') f.close() except: return "" def writeUnivlist(lis,li,fpath,num): with open(fpath, 'a', encoding='utf-8') as f: for i in range(num): f.write(str(lis[i])+'\n') f.write('*'*50 + '\n') f.write(str(li[i]) + '\n') f.write('*' * 50 + '\n') f.close() def main(): count = 0 url = 'https://tieba.baidu.com/p/3138733512?see_lz=1&pn=1' output_file = 'D:/StockInfo.txt' html = getHTMLText(url) titleText = printTitle(html) writeText(titleText, output_file) for i in range(5): i = i + 1 lis = [] li = [] url = 'https://tieba.baidu.com/p/3138733512?see_lz=1&pn=' + str(i) html = getHTMLText(url) fillUnivlist(lis, li, html) writeUnivlist(lis, li, output_file, len(lis)) count = count + 1 print("\r當前進度: {:.2f}%".format(count * 100 / 5), end="") main()
這個還有很多完善的地方,希望大家多多指教
-------來自一個熱愛自學編程的小白