爬虫试手——百度贴吧爬虫

author author     2022-12-19     686

关键词:

自学python有一段时间了,做过的东西还不多,最近开始研究爬虫,想自己写一个爬百度贴吧的帖子内容,然后对帖子做分词和词频统计,看看这个吧热议的关键词都有哪些。百度了好多资料和视频,学到了不少东西,但也生出了一些问题:

1、http请求用python自带的urllib,也可以用requests,哪个更好用?

2、html解析可以用正则表达式,也可以用xpath,哪个效率更高?

根据网上资料的说法,requests相对更好用,因为很多功能已经封装好了,性能上与urllib也没什么区别,而正则表达式通常要比xpath效率更高。不过实践出真知,分别用两种方式写出来然后对比一下。爬取的目标是我很喜欢的一个游戏——英雄无敌3的贴吧,从第10页爬到30页,只爬帖子、回帖以及楼中楼内容的文字部分。首先用建议初学者使用的urllib加正则表达式写了一版:

# -*- 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" #贴吧页面url的通用前缀

q=queue.Queue() #保存帖子链接的队列

MAX_WAIT=10 #解析线程的最大等待时间

reg=re.compile('<[^>]*>') #去除html标签的正则表达式


#封装的获取html字符串的函数

def get_html(url):

    response=request.urlopen(url)

    html=response.read().decode('utf-8')

    return html


#采集url的线程,thnum线程id,startpage开始采集的页数,step单个线程采集页数间隔(与线程个数相同),maxpage采集结束的页数,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) #计算循环的范围

        for i in range(0,mm):

            startnum=self.startpage+self.thnum+i*self.step #开始页数

            tempurl=baseurl+self.url+"&pn="+str(startnum*50) #构造每一页的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) #获取当前页的所有帖子链接

                for tu in turls: #入队列

                    q.put(tu)

            except:

                print("%s get failed"%(tempurl))

                pass

            sleep(1)


#解析url的线程,thrnum线程id,barname贴吧名,用来构造文件保存路径

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: #队列为空且等待没有超过MAX_WAIT时,继续等待

                sleep(1)

                waittime=waittime+1

                print("Thr %s wait for %s secs"%(self.thrnum,waittime))

            elif waittime>=MAX_WAIT: #等待超过MAX_WAIT时,线程退出

                print("Thr %s quit"%(self.thrnum))

                break

            else: #队列不为空时,重置等待时间,从队列中取帖子url,进行解析

                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)

    #抓取一页的内容,包括帖子及楼中楼,入参为页面url和帖子id,返回值为帖子的内容字符串

    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)+" "  #先整合帖子内容

        for res in result2:

            if 'null' not in res:  #若有楼中楼,即层数不为null

                pid=res.split(",")[0].split(":")[1]  #楼中楼id

                numreply=int(res.split(",")[1].split(":")[1])  #楼中楼层数

                tpage=math.ceil(numreply/10) #计算楼中楼页数,每页10条,用于遍历楼中楼的每一页

                for i in range(1,tpage+1):

                    replyurl="https://tieba.baidu.com/p/comment?tid="+tid+"&pid="+pid+"&pn="+str(i) #构造楼中楼url

                    htmlreply=get_html(replyurl)

                    replyresult=re.findall(r'<span class="lzl_content_main">(.*?)</span>', htmlreply,re.S) #获取楼中楼的评论内容

                    for reply in replyresult:

                        pagestr=pagestr+reg.sub('',reply)+" "

        return pagestr

    #爬取一个帖子,入参为帖子后缀url,以及贴吧名

    def savepost(self,url,barname):

        tid=url.replace("/p/","")

        filename = "E:/tieba/"+barname+"/"+tid+".txt" #文件保存路径

        if os.path.exists(filename): #判断是否已经爬取过当前帖子

            return

        print(baseurl+url)

        try:

            html=get_html(baseurl+url)

            findreault = re.findall(r'([0-9]*)</span>页', html,re.S) #获取当前帖子页数

            numpage=findreault[0]

            poststr=self.getpagestr(baseurl+url,tid) #获取第一页

            if int(numpage)>1:

                for i in range(2,int(numpage)+1): 

                    tempurl=baseurl+url+"?pn="+str(i) #构造每一页的url,循环获取每一页

                    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="英雄无敌3"

    html=get_html(baseurl+testurl)

    numpost=re.findall(r'共有主题数<span class="red_text">([0-9]*?)</span>个', html,re.S)[0] #获取帖子总数

    numpage=math.ceil(int(numpost)/50) #计算页数

    path = "E:/tieba/"+barname

    folder=os.path.exists(path)

    if not folder:

        os.makedirs(path)

    for i in range(3): #创建获取帖子链接的线程

        t=getlinkthread(i,10,3,30,testurl)

        t.start()

    for j in range(3): #创建解析帖子链接的线程

        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" #贴吧页面url的通用前缀

q=queue.Queue() #保存帖子链接的队列

MAX_WAIT=10 #解析线程的最大等待时间

reg=re.compile('<[^>]*>') #去除html标签的正则表达式


#封装的获取etree对象的函数

def get_url_text(url):

    response=requests.get(url)

    return etree.HTML(response.text)


#封装的获取json对象的函数

def get_url_json(url):

    response=requests.get(url)

    return response.json()


#封装的通过xpath解析的函数

def parse_html(html,xpathstr):

    result = html.xpath(xpathstr)

    return result


#采集url的线程,thnum线程id,startpage开始采集的页数,step单个线程采集页数间隔(与线程个数相同),maxpage采集结束的页数,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) #计算循环的范围

        for i in range(0,mm):

            startnum=self.startpage+self.thnum+i*self.step #开始页数

            tempurl=baseurl+self.url+"&pn="+str(startnum*50) #构造每一页的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') #通过xpath解析,获取当前页所有帖子的url后缀

                for tu in turls: #入队列

                    q.put(tu)

            except:

                print("%s get failed"%(tempurl))

                pass

            sleep(1)


#解析url的线程,thrnum线程id,barname贴吧名,用来构造文件保存路径

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: #队列为空且等待没有超过MAX_WAIT时,继续等待

                sleep(1)

                waittime=waittime+1

                print("Thr %s wait for %s secs"%(self.thrnum,waittime))

            elif waittime>=MAX_WAIT: #等待超过MAX_WAIT时,线程退出

                print("Thr %s quit"%(self.thrnum))

                break

            else: #队列不为空时,重置等待时间,从队列中取帖子url,进行解析

                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,后面构造楼中楼url以及保存文件时用到

        filename = "E:/tieba/"+barname+"/"+tid+".txt" #文件保存路径

        if os.path.exists(filename): #判断是否已经爬取过当前帖子

            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()') #获取当前帖子页数

            numpage=int(findreault[0])

            poststr=self.getpagestr(baseurl+item,tid,1) #获取第一页的内容

            if numpage>1:

                for i in range(2,numpage+1): 

                    tempurl=baseurl+item+"?pn="+str(i) #构造每一页的url,循环获取每一页

                    pagestr=self.getpagestr(tempurl,tid,i)

                    poststr=poststr+pagestr

            poststr= reg.sub('',poststr) #正则表达式去除html标签

            with open(filename,'w',encoding="utf-8") as f: #写文件

                f.write(poststr)

        except:

            print("Thr %s get %s failed"%(self.thrnum,baseurl+item))

            pass

    #抓取一页的内容,包括帖子及楼中楼,入参为页面url和帖子id,返回值为帖子的内容字符串

    def getpagestr(self,url,tid,pagenum):

        html=get_url_text(url)

        lzlurl=baseurl+"/p/totalComment?tid="+tid+"&pn="+str(pagenum)+"&see_lz=0" #构造楼中楼url

        jsonstr=get_url_json(lzlurl) #正常一页能看到的楼中楼的内容返回为json格式,如果有楼中楼层数大于10的,需要通过其他格式的url获取楼中楼10层以后的内容

        result1 = parse_html(html,'//*[@class="d_post_content j_d_post_content "]/text()') #xpath解析返回楼中楼内容

        pagestr=""

        for res in result1:

            pagestr=pagestr+res+" "  #先整合帖子内容

        if jsonstr['data']['comment_list']!=[]: #如果某页没有楼中楼,返回是空的list,不加判断的话会报错

            for key,val in jsonstr['data']['comment_list'].items(): #循环获取每层楼中楼的内容,key是楼中楼id,val为包含楼中楼层数、内容等信息的字典

                lzlid=key

                lzlnum=int(val['comment_num'])

                tpage=math.ceil(lzlnum/10) #计算楼中楼的页数

                for cominfo in val['comment_info']:

                    pagestr=pagestr+cominfo['content']+" "

                if tpage>1: #楼中楼超过1页时,需要构造第二页及以后的楼中楼url

                    for i in range(1,tpage+1):

                        replyurl="https://tieba.baidu.com/p/comment?tid="+tid+"&pid="+lzlid+"&pn="+str(i) #构造楼中楼url

                        htmlreply=get_url_text(replyurl)

                        replyresult=parse_html(htmlreply, '/html/body/li/div/span/text()') #获取楼中楼的评论内容

                        for reply in replyresult:

                            pagestr=pagestr+reply+" "

        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="英雄无敌3"

    html=get_url_text(baseurl+testurl)

    findreault = parse_html(html, '//*[@class="th_footer_l"]/span[1]/text()') #获取当前帖子页数

    numpost=int(findreault[0])

    numpage=math.ceil(int(numpost)/50) #计算页数

    path = "E:/tieba/"+barname

    folder=os.path.exists(path)

    if not folder:

        os.makedirs(path)

    for i in range(3): #创建获取帖子链接的线程

        t=getlinkthread(i,10,3,30,testurl)

        t.start()

    for j in range(3): #创建解析帖子链接的线程

        t1=parselinkthread(j,barname)

        t1.start()

    t1.join()

    endtime = datetime.datetime.now()

    print(endtime-starttime)


执行的结果:

方法1:urllib+正则执行时间:0:32:22.223089,爬下来984个帖子,失败9个帖子

方法2:requests+xpath执行时间:0:21:42.239483,爬下来993个帖子,失败0个帖子

结果与经验不同!后来想了一下,可能是因为对楼中楼的爬取方式不同,方法1中对每一个楼中楼每一页都要请求一次url,因为当时不会用浏览器F12工具,楼中楼的url格式是百度查到的。。。在写方法2时用F12工具抓到了第一页楼中楼的url,就是返回json的那个,这样如果楼中楼层数不超过10的话,每一页帖子的楼中楼只需要请求一次,只有超过10层的楼中楼才需要用方法1中的url进行爬取,这样效率就高了许多。这样看来,这个测试不是很合理。

分享一点经验:

1、就个人感觉来说,正则比xpath好用,只要找到html中的特定格式就行了,不过似乎容错差一点,方法1失败的9个帖子可能就是因为个别帖子html格式与其他不同导致正则匹配不到;

2、requests比urllib好用,尤其对于返回json格式的url,字典操作感觉比返回字符串做正则匹配要方便;

3、pip装lxml的时候报错,提示Cannot open include file: 'libxml/xpath.h': No such file or directory,以及没有安装libxml2,后来百度到https://www.cnblogs.com/caochuangui/p/5980469.html这个文章的方法,安装成功

如何入门爬虫(基础篇)

...hon爬虫实战一之爬取糗事百科段子Python爬虫实战二之爬取百度贴吧帖子Python 查看详情

爬虫实战:百度失信人名单(代码片段)

文章目录失信人信息爬虫项目1.需求2.开发环境3抓取百度失信人名单3.1.实现步骤:3.2创建爬虫项目3.3.根据需求,定义数据模型3.4实现百度失信名单爬虫3.5.保存失信名单信息3.6.实现随机User-Agent和代理IP下载器中间件,解决IP反爬.4.实... 查看详情

百度图片小爬虫

     刚学习爬虫,写了一个百度图片爬虫当作练习。    环境:python3.6(请下好第三方库requests)     实现的功能:输入关键字,下载240张关键字有关的百度图片到本地的d:百度图片... 查看详情

百度贴吧爬虫程序

#coding:utf-8importrequestsimportrandomclassTiebaSpider:  def__init__(self,tieba_name):    self.headers={‘User-Agent‘:‘Mozilla/5.0(WindowsNT10.0;WOW64)AppleWebKit/537.36( 查看详情

百度贴吧的网络爬虫(v0.4)源码及解析

更新:感谢评论中朋友的提醒,百度贴吧现在已经改成utf-8编码了吧,需要把代码中的decode(‘gbk‘)改成decode(‘utf-8‘)。 百度贴吧的爬虫制作和糗百的爬虫制作原理基本相同,都是通过查看源码扣出关键数据,然后将其存储... 查看详情

实用的开源百度云分享爬虫项目yunshare-安装篇

今天开源了一个百度云网盘爬虫项目,地址是https://github.com/callmelanmao/yunshare。百度云分享爬虫项目github上有好几个这样的开源项目,但是都只提供了爬虫部分,这个项目在爬虫的基础上还增加了保存数据,建立elasticsearch索引的... 查看详情

(scrapy框架)爬虫获取百度新冠疫情数据|爬虫案例(代码片段)

目录前言环境部署插件推荐爬虫目标项目创建webdriver部署项目代码Item定义中间件定义定义爬虫pipeline输出结果文本配置文件改动验证结果总结前言闲来无聊,写了一个爬虫程序获取百度疫情数据。申明一下,研究而已。... 查看详情

速学爬虫

爬虫前奏 爬虫实例:  1.搜索引擎(百度、谷歌、360搜索等)。  2.伯乐在线。  3.惠惠购物助手。  4.数据分析与研究(数据冰山知乎专栏)。  5.抢票软件等。 什么是网络爬虫:  1.通俗理解:爬虫是一个模拟... 查看详情

python爬虫-爬取百度html代码前200行

Python爬虫-爬取百度html代码前200行-改进版,  增加了对字符串的.strip()处理 查看详情

爬虫下载百度贴吧图片

在爬取之前需要在浏览器先登录百度贴吧的帐号,各位也可以在代码中使用post提交或者加入cookie 爬行地址:http://tieba.baidu.com/f?kw=%E7%BE%8E%E5%A5%B3&ie=utf-8&pn=0#-*-coding:utf-8-*-importurllib2importreimportrequestsfromlxmlimportetre 查看详情

百度图片爬虫

爬虫工具:webmagic爬取百度图片,不能通过获取html然后通过匹配标签的形式,而是要找到对应的提供json数据的请求,这个坑我踩了两三个小时,最初自信满满的按官方文档注解形式写了model,pipeline,然后就运行时就发现问题很... 查看详情

如何入门爬虫(基础篇)

...hon爬虫实战一之爬取糗事百科段子Python爬虫实战二之爬取百度贴吧帖子Python爬虫实战三之实现山东大学无线网络掉线自动重连Python爬虫实战四之抓取淘宝MM照片Python爬虫实战五之模拟登录淘宝并获取所有订单Python爬虫实战六之抓... 查看详情

百度爬虫用啥语言

参考技术A问题一:Google和百度的爬虫是用什么语言写的?15分每个网站都有一个“爬虫协议”,至少大型网站都会有。Robots协议(也称为爬虫协议、机器人协议等)的全称是“网络爬虫排除标准”(RobotsExclusionProtocol),网站通... 查看详情

ruby用百度搜索爬虫

Ruby用百度搜索爬虫博主ruby学得断断续续,打算写一个有点用的小程序娱乐一下,打算用ruby通过百度通道爬取网络信息。第三方库准备mechanize:比较方便地处理网络请求,类似于Python中的requestsnokogiri:解析HTML文本,采用的是jque... 查看详情

python爬虫1.网络爬虫是什么?(代码片段)

网络爬虫又称网络蜘蛛、网络机器人,它是一种按照一定的规则自动浏览、检索网页信息的程序或者脚本。网络爬虫能够自动请求网页,并将所需要的数据抓取下来。通过对抓取的数据进行处理,从而提取出有价值的... 查看详情

python爬虫1.网络爬虫是什么?(代码片段)

网络爬虫又称网络蜘蛛、网络机器人,它是一种按照一定的规则自动浏览、检索网页信息的程序或者脚本。网络爬虫能够自动请求网页,并将所需要的数据抓取下来。通过对抓取的数据进行处理,从而提取出有价值的... 查看详情

通用爬虫和聚焦爬虫

爬虫分为两个领域:聚焦爬虫和通用爬虫。通用爬虫:  搜索引擎用的爬虫系统。目标:  搜索互联网所有的信息下载下来,放到本地服务器,再对这些网页进行相关处理,提取关键字什么的,最终给用户提供一个检索的接... 查看详情

python百度空间博文爬虫(代码片段)

查看详情