自學(xué)python有一段時(shí)間了,做過(guò)的東西還不多,最近開始研究爬蟲,想自己寫一個(gè)爬百度貼吧的帖子內(nèi)容,然后對(duì)帖子做分詞和詞頻統(tǒng)計(jì),看看這個(gè)吧熱議的關(guān)鍵詞都有哪些。百度了好多資料和視頻,學(xué)到了不少東西,但也生出了一些問(wèn)題:
成都創(chuàng)新互聯(lián)是一家專注于做網(wǎng)站、網(wǎng)站制作與策劃設(shè)計(jì),武陵源網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)十多年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:武陵源等地區(qū)。武陵源做網(wǎng)站價(jià)格咨詢:18980820575
1、http請(qǐng)求用python自帶的urllib,也可以用requests,哪個(gè)更好用?
2、html解析可以用正則表達(dá)式,也可以用xpath,哪個(gè)效率更高?
根據(jù)網(wǎng)上資料的說(shuō)法,requests相對(duì)更好用,因?yàn)楹芏喙δ芤呀?jīng)封裝好了,性能上與urllib也沒什么區(qū)別,而正則表達(dá)式通常要比xpath效率更高。不過(guò)實(shí)踐出真知,分別用兩種方式寫出來(lái)然后對(duì)比一下。爬取的目標(biāo)是我很喜歡的一個(gè)游戲——英雄無(wú)敵3的貼吧,從第10頁(yè)爬到30頁(yè),只爬帖子、回帖以及樓中樓內(nèi)容的文字部分。首先用建議初學(xué)者使用的urllib加正則表達(dá)式寫了一版:
# -*- coding: utf-8 -*-
from urllib import request
import re
import queue
import os
import math
import threading
from time import sleep
import datetime
baseurl="https://tieba.baidu.com" #貼吧頁(yè)面url的通用前綴
q=queue.Queue() #保存帖子鏈接的隊(duì)列
MAX_WAIT=10 #解析線程的最大等待時(shí)間
reg=re.compile('<[^>]*>') #去除html標(biāo)簽的正則表達(dá)式
#封裝的獲取html字符串的函數(shù)
def get_html(url):
response=request.urlopen(url)
html=response.read().decode('utf-8')
return html
#采集url的線程,thnum線程id,startpage開始采集的頁(yè)數(shù),step單個(gè)線程采集頁(yè)數(shù)間隔(與線程個(gè)數(shù)相同),maxpage采集結(jié)束的頁(yè)數(shù),url采集的貼吧的url后綴
class getlinkthread(threading.Thread):
def __init__(self,thnum,startpage,step,maxpage,url):
threading.Thread.__init__(self)
self.thnum=thnum
self.startpage=startpage
self.step=step
self.maxpage=maxpage
self.url=url
def run(self):
mm=math.ceil((self.maxpage-self.startpage)/self.step) #計(jì)算循環(huán)的范圍
for i in range(0,mm):
startnum=self.startpage+self.thnum+i*self.step #開始頁(yè)數(shù)
tempurl=baseurl+self.url+"&pn="+str(startnum*50) #構(gòu)造每一頁(yè)的url
print("Thread %s is getting %s"%(self.thnum,tempurl))
try:
temphtml=get_html(tempurl)
turls = re.findall(r'rel="noreferrer" href="(/p/[0-9]*?)"', temphtml,re.S) #獲取當(dāng)前頁(yè)的所有帖子鏈接
for tu in turls: #入隊(duì)列
q.put(tu)
except:
print("%s get failed"%(tempurl))
pass
sleep(1)
#解析url的線程,thrnum線程id,barname貼吧名,用來(lái)構(gòu)造文件保存路徑
class parselinkthread(threading.Thread):
def __init__(self,thrnum,barname):
threading.Thread.__init__(self)
self.thrnum=thrnum
self.barname=barname
def run(self):
waittime=0
while True:
if q.empty() and waittime<MAX_WAIT: #隊(duì)列為空且等待沒有超過(guò)MAX_WAIT時(shí),繼續(xù)等待
sleep(1)
waittime=waittime+1
print("Thr %s wait for %s secs"%(self.thrnum,waittime))
elif waittime>=MAX_WAIT: #等待超過(guò)MAX_WAIT時(shí),線程退出
print("Thr %s quit"%(self.thrnum))
break
else: #隊(duì)列不為空時(shí),重置等待時(shí)間,從隊(duì)列中取帖子url,進(jìn)行解析
waittime=0
item=q.get()
self.dotask(item)
def dotask(self,item):
print("Thr %s is collecting %s"%(self.thrnum,item))
self.savepost(item,self.barname)
#抓取一頁(yè)的內(nèi)容,包括帖子及樓中樓,入?yún)轫?yè)面url和帖子id,返回值為帖子的內(nèi)容字符串
def getpagestr(self,url,tid):
html=get_html(url)
result1 = re.findall(r'class="d_post_content j_d_post_content ">(.*?)</div>', html,re.S)
result2 = re.findall(r'class="j_lzl_r p_reply" data-field=\'{(.*?)}\'', html,re.S)
pagestr=""
for res in result1:
pagestr=pagestr+reg.sub('',res)+"\n" #先整合帖子內(nèi)容
for res in result2:
if 'null' not in res: #若有樓中樓,即層數(shù)不為null
pid=res.split(",")[0].split(":")[1] #樓中樓id
numreply=int(res.split(",")[1].split(":")[1]) #樓中樓層數(shù)
tpage=math.ceil(numreply/10) #計(jì)算樓中樓頁(yè)數(shù),每頁(yè)10條,用于遍歷樓中樓的每一頁(yè)
for i in range(1,tpage+1):
replyurl="https://tieba.baidu.com/p/comment?tid="+tid+"&pid="+pid+"&pn="+str(i) #構(gòu)造樓中樓url
htmlreply=get_html(replyurl)
replyresult=re.findall(r'<span class="lzl_content_main">(.*?)</span>', htmlreply,re.S) #獲取樓中樓的評(píng)論內(nèi)容
for reply in replyresult:
pagestr=pagestr+reg.sub('',reply)+"\n"
return pagestr
#爬取一個(gè)帖子,入?yún)樘雍缶Yurl,以及貼吧名
def savepost(self,url,barname):
tid=url.replace("/p/","")
filename = "E:/tieba/"+barname+"/"+tid+".txt" #文件保存路徑
if os.path.exists(filename): #判斷是否已經(jīng)爬取過(guò)當(dāng)前帖子
return
print(baseurl+url)
try:
html=get_html(baseurl+url)
findreault = re.findall(r'([0-9]*)</span>頁(yè)', html,re.S) #獲取當(dāng)前帖子頁(yè)數(shù)
numpage=findreault[0]
poststr=self.getpagestr(baseurl+url,tid) #獲取第一頁(yè)
if int(numpage)>1:
for i in range(2,int(numpage)+1):
tempurl=baseurl+url+"?pn="+str(i) #構(gòu)造每一頁(yè)的url,循環(huán)獲取每一頁(yè)
pagestr=self.getpagestr(tempurl,tid)
poststr=poststr+pagestr
with open(filename,'w',encoding="utf-8") as f: #寫文件
f.write(poststr)
except:
print("get %s failed"%(baseurl+url))
pass
if __name__ == '__main__':
starttime = datetime.datetime.now()
testurl="/f?kw=%E8%8B%B1%E9%9B%84%E6%97%A0%E6%95%8C3&fr=index&fp=0&ie=utf-8"
barname="英雄無(wú)敵3"
html=get_html(baseurl+testurl)
numpost=re.findall(r'共有主題數(shù)<span class="red_text">([0-9]*?)</span>個(gè)', html,re.S)[0] #獲取帖子總數(shù)
numpage=math.ceil(int(numpost)/50) #計(jì)算頁(yè)數(shù)
path = "E:/tieba/"+barname
folder=os.path.exists(path)
if not folder:
os.makedirs(path)
for i in range(3): #創(chuàng)建獲取帖子鏈接的線程
t=getlinkthread(i,10,3,30,testurl)
t.start()
for j in range(3): #創(chuàng)建解析帖子鏈接的線程
t1=parselinkthread(j,barname)
t1.start()
t1.join()
endtime = datetime.datetime.now()
print(endtime-starttime)
然后用requests加xpath寫了一版:
# -*- coding: utf-8 -*-
import requests
from lxml import etree
import re
import queue
import os
import math
import threading
import datetime
from time import sleep
baseurl="https://tieba.baidu.com" #貼吧頁(yè)面url的通用前綴
q=queue.Queue() #保存帖子鏈接的隊(duì)列
MAX_WAIT=10 #解析線程的最大等待時(shí)間
reg=re.compile('<[^>]*>') #去除html標(biāo)簽的正則表達(dá)式
#封裝的獲取etree對(duì)象的函數(shù)
def get_url_text(url):
response=requests.get(url)
return etree.HTML(response.text)
#封裝的獲取json對(duì)象的函數(shù)
def get_url_json(url):
response=requests.get(url)
return response.json()
#封裝的通過(guò)xpath解析的函數(shù)
def parse_html(html,xpathstr):
result = html.xpath(xpathstr)
return result
#采集url的線程,thnum線程id,startpage開始采集的頁(yè)數(shù),step單個(gè)線程采集頁(yè)數(shù)間隔(與線程個(gè)數(shù)相同),maxpage采集結(jié)束的頁(yè)數(shù),url采集的貼吧的url后綴
class getlinkthread(threading.Thread):
def __init__(self,thnum,startpage,step,maxpage,url):
threading.Thread.__init__(self)
self.thnum=thnum
self.startpage=startpage
self.step=step
self.maxpage=maxpage
self.url=url
def run(self):
mm=math.ceil((self.maxpage-self.startpage)/self.step) #計(jì)算循環(huán)的范圍
for i in range(0,mm):
startnum=self.startpage+self.thnum+i*self.step #開始頁(yè)數(shù)
tempurl=baseurl+self.url+"&pn="+str(startnum*50) #構(gòu)造每一頁(yè)的url
print("Thread %s is getting %s"%(self.thnum,tempurl))
try:
temphtml=get_url_text(tempurl)
turls = parse_html(temphtml, '//*[@class="threadlist_title pull_left j_th_tit "]/a/@href') #通過(guò)xpath解析,獲取當(dāng)前頁(yè)所有帖子的url后綴
for tu in turls: #入隊(duì)列
q.put(tu)
except:
print("%s get failed"%(tempurl))
pass
sleep(1)
#解析url的線程,thrnum線程id,barname貼吧名,用來(lái)構(gòu)造文件保存路徑
class parselinkthread(threading.Thread):
def __init__(self,thrnum,barname):
threading.Thread.__init__(self)
self.thrnum=thrnum
self.barname=barname
def run(self):
waittime=0
while True:
if q.empty() and waittime<MAX_WAIT: #隊(duì)列為空且等待沒有超過(guò)MAX_WAIT時(shí),繼續(xù)等待
sleep(1)
waittime=waittime+1
print("Thr %s wait for %s secs"%(self.thrnum,waittime))
elif waittime>=MAX_WAIT: #等待超過(guò)MAX_WAIT時(shí),線程退出
print("Thr %s quit"%(self.thrnum))
break
else: #隊(duì)列不為空時(shí),重置等待時(shí)間,從隊(duì)列中取帖子url,進(jìn)行解析
waittime=0
item=q.get()
self.dotask(item)
def dotask(self,item):
print("Thr %s is collecting %s"%(self.thrnum,item))
tid=item.replace("/p/","") #獲取帖子的id,后面構(gòu)造樓中樓url以及保存文件時(shí)用到
filename = "E:/tieba/"+barname+"/"+tid+".txt" #文件保存路徑
if os.path.exists(filename): #判斷是否已經(jīng)爬取過(guò)當(dāng)前帖子
return
print(baseurl+item)
try:
html=get_url_text(baseurl+item)
findreault = parse_html(html, '//*[@id="thread_theme_5"]/div[1]/ul/li[2]/span[2]/text()') #獲取當(dāng)前帖子頁(yè)數(shù)
numpage=int(findreault[0])
poststr=self.getpagestr(baseurl+item,tid,1) #獲取第一頁(yè)的內(nèi)容
if numpage>1:
for i in range(2,numpage+1):
tempurl=baseurl+item+"?pn="+str(i) #構(gòu)造每一頁(yè)的url,循環(huán)獲取每一頁(yè)
pagestr=self.getpagestr(tempurl,tid,i)
poststr=poststr+pagestr
poststr= reg.sub('',poststr) #正則表達(dá)式去除html標(biāo)簽
with open(filename,'w',encoding="utf-8") as f: #寫文件
f.write(poststr)
except:
print("Thr %s get %s failed"%(self.thrnum,baseurl+item))
pass
#抓取一頁(yè)的內(nèi)容,包括帖子及樓中樓,入?yún)轫?yè)面url和帖子id,返回值為帖子的內(nèi)容字符串
def getpagestr(self,url,tid,pagenum):
html=get_url_text(url)
lzlurl=baseurl+"/p/totalComment?tid="+tid+"&pn="+str(pagenum)+"&see_lz=0" #構(gòu)造樓中樓url
jsonstr=get_url_json(lzlurl) #正常一頁(yè)能看到的樓中樓的內(nèi)容返回為json格式,如果有樓中樓層數(shù)大于10的,需要通過(guò)其他格式的url獲取樓中樓10層以后的內(nèi)容
result1 = parse_html(html,'//*[@class="d_post_content j_d_post_content "]/text()') #xpath解析返回樓中樓內(nèi)容
pagestr=""
for res in result1:
pagestr=pagestr+res+"\n" #先整合帖子內(nèi)容
if jsonstr['data']['comment_list']!=[]: #如果某頁(yè)沒有樓中樓,返回是空的list,不加判斷的話會(huì)報(bào)錯(cuò)
for key,val in jsonstr['data']['comment_list'].items(): #循環(huán)獲取每層樓中樓的內(nèi)容,key是樓中樓id,val為包含樓中樓層數(shù)、內(nèi)容等信息的字典
lzlid=key
lzlnum=int(val['comment_num'])
tpage=math.ceil(lzlnum/10) #計(jì)算樓中樓的頁(yè)數(shù)
for cominfo in val['comment_info']:
pagestr=pagestr+cominfo['content']+"\n"
if tpage>1: #樓中樓超過(guò)1頁(yè)時(shí),需要構(gòu)造第二頁(yè)及以后的樓中樓url
for i in range(1,tpage+1):
replyurl="https://tieba.baidu.com/p/comment?tid="+tid+"&pid="+lzlid+"&pn="+str(i) #構(gòu)造樓中樓url
htmlreply=get_url_text(replyurl)
replyresult=parse_html(htmlreply, '/html/body/li/div/span/text()') #獲取樓中樓的評(píng)論內(nèi)容
for reply in replyresult:
pagestr=pagestr+reply+"\n"
return pagestr
if __name__ == '__main__':
starttime = datetime.datetime.now()
testurl="/f?ie=utf-8&kw=%E8%8B%B1%E9%9B%84%E6%97%A0%E6%95%8C3&fr=search"
barname="英雄無(wú)敵3"
html=get_url_text(baseurl+testurl)
findreault = parse_html(html, '//*[@class="th_footer_l"]/span[1]/text()') #獲取當(dāng)前帖子頁(yè)數(shù)
numpost=int(findreault[0])
numpage=math.ceil(int(numpost)/50) #計(jì)算頁(yè)數(shù)
path = "E:/tieba/"+barname
folder=os.path.exists(path)
if not folder:
os.makedirs(path)
for i in range(3): #創(chuàng)建獲取帖子鏈接的線程
t=getlinkthread(i,10,3,30,testurl)
t.start()
for j in range(3): #創(chuàng)建解析帖子鏈接的線程
t1=parselinkthread(j,barname)
t1.start()
t1.join()
endtime = datetime.datetime.now()
print(endtime-starttime)
執(zhí)行的結(jié)果:
方法1:urllib+正則執(zhí)行時(shí)間:0:32:22.223089,爬下來(lái)984個(gè)帖子,失敗9個(gè)帖子
方法2:requests+xpath執(zhí)行時(shí)間:0:21:42.239483,爬下來(lái)993個(gè)帖子,失敗0個(gè)帖子
結(jié)果與經(jīng)驗(yàn)不同!后來(lái)想了一下,可能是因?yàn)閷?duì)樓中樓的爬取方式不同,方法1中對(duì)每一個(gè)樓中樓每一頁(yè)都要請(qǐng)求一次url,因?yàn)楫?dāng)時(shí)不會(huì)用瀏覽器F12工具,樓中樓的url格式是百度查到的。。。在寫方法2時(shí)用F12工具抓到了第一頁(yè)樓中樓的url,就是返回json的那個(gè),這樣如果樓中樓層數(shù)不超過(guò)10的話,每一頁(yè)帖子的樓中樓只需要請(qǐng)求一次,只有超過(guò)10層的樓中樓才需要用方法1中的url進(jìn)行爬取,這樣效率就高了許多。這樣看來(lái),這個(gè)測(cè)試不是很合理。
分享一點(diǎn)經(jīng)驗(yàn):
1、就個(gè)人感覺來(lái)說(shuō),正則比xpath好用,只要找到html中的特定格式就行了,不過(guò)似乎容錯(cuò)差一點(diǎn),方法1失敗的9個(gè)帖子可能就是因?yàn)閭€(gè)別帖子html格式與其他不同導(dǎo)致正則匹配不到;
2、requests比urllib好用,尤其對(duì)于返回json格式的url,字典操作感覺比返回字符串做正則匹配要方便;
3、pip裝lxml的時(shí)候報(bào)錯(cuò),提示Cannot open include file: 'libxml/xpath.h': No such file or directory,以及沒有安裝libxml2,后來(lái)百度到https://www.cnblogs.com/caochuangui/p/5980469.html這個(gè)文章的方法,安裝成功
標(biāo)題名稱:爬蟲試手——百度貼吧爬蟲
文章起源:http://muchs.cn/article28/ihchcp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、Google、ChatGPT、軟件開發(fā)、網(wǎng)站設(shè)計(jì)、品牌網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)