爬蟲試手——百度貼吧爬蟲

自學(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)

成都seo排名網(wǎng)站優(yōu)化