[python]基于BeautifulSoup解析百度贴吧water_maker(谁最水)的脚本,练手打趣之作[更新0.3]

来源:互联网 发布:深入浅出软件开发 编辑:程序博客网 时间:2024/05/18 02:00

5.29

百度在第二页添加了一个标题,导致扫的时候又出错了,原来维护这样一个小东西也是不容易的啊

代码更新

5.22修复不可抗拒BUG

这是一个不知道怎么回事,发生在BeautifulSoup中调用的Python的html解析库中发生的问题。这个问题的发生具有不确定性,我重新在调试窗口解析出错的网页的时候就不会出问题。

由于这个问题并无大碍,于是我利用一次except对它进行了一次等待重试,如果还出现错误,再用一次except忽略报错

代码更新

        try:#这里有可能会出现不可抗的错误——5.22            soup=BeautifulSoup(res)        except:            try:                time.sleep(3)                soup=BeautifulSoup(res)            except:                return [[],'nopages']
这让我知道了,代码真正要投入使用,就尽量做到不出错,在有可能出错的地方使用try,并对可能产生的错误做出相应的处理。突然产生停止程序的错误会极大地降低用户体验

5.17修复BUG

1、利用‘下一页’对帖子内部进行遍历,修复了超过10页后的页数没法统计的BUG

2、tie=soup.findAll('tr',{'class':'thread_alt'}) 这句出现一个BUG只找到一半的帖子,实在是失误,感谢davidbauer1117 提出,现改为下
        tie=soup.findAll('td',{'class':'thread_title'})可以找会所有帖子

3、实现了对扫过的帖子做记录防止重复扫,并每扫三页返回第一页再扫一遍防止漏扫

代码改动较多,且发现百度贴吧的个人动态处并没有把用户所有的发言全显示,害我一直以为自己扫错。。但确实不容易证实自己的脚本的正确性,写程序最无奈之处便在此。

源码更新

5.16 感谢davidbauer1117发现的BUG

1、当帖子回复页数超过9页时脚本不再继续遍历

2、寻找帖子的链接时只找到了一半的帖子


今天课实在是满一天,晚上复习了一下将要考试的数字信号处理,没能实现昨天说的更新,对关注的朋友感到抱歉,明天一定会抽出时间来把BUG修好微笑

--------------------------------------------------

5.15 更新

改为统计从某一日期到现在的回复,而不仅仅按页数统计。

此次更新的收获:

涉及到datetime库,datetime库能方便地显示当前时间和计算时间加减


有些在chrome审查元素中出现的标签并不是真正的源码,最靠谱的方法还是直接看源码

接触到json库,直接用json库可以把字符串形式的数据和Python语句相互转换(dumps、loads)

类的静态方法。并不是直接把sel参数去掉就好(为什么不呢?),有几种方法,只记一种,在相应函数上方添加
@staticmethod

同时发现一个尚未解决的问题:由于帖子是不断被顶上去,跌下来的,所以当遍历到某一页的时候,往往会有些已经统计过的帖子跌到这个页面,从而造成重复统计;或者某些帖子升到以前统计过的页面,导致漏掉

目前想到的一个比较傻的办法,首先把所有扫过的帖子地址记录,以后每次扫帖子之前看看是否扫过。同时特别留意前面几页,多扫几遍,尽量把新往上跑的帖子都扫到。

夜深了明天还得上课,暂时没实现,明天有时间再做更新,下源码改为按日期统计


------------------------------------------------



呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


第一要发的文章就这样被网站这种恶心的格式和自动保存搞丢了,这么难编辑,又不支持恢复返回,你妹的CSDN!!!!!

这就是我第一篇在CSDN要发的文章!

第二次手打了一整面,又被吃了!!!滚!!!!直接放代码,你爱咋的咋的吧


Python作为一个强大的脚本,解析网页,爬东西是最常用的功能之一了。小可之前也曾做过一些简单的解析爬图片等,但真正接触网页解析还是在这次watermaker中。

这个脚本要实现的功能很简单,遍历百度贴吧指定页数内所有帖子,并做出基于回复量的ID的排行。(BYW:之所以选择这个题目练手,纯粹是因为最近开始水贴吧,觉得做这么个东西,很有意思。但其核心在于标签定位,定位到了之后至于抓什么东西,那只是应用问题。)

用到的HTML解析库是BeautifulSoup,一个很强大的库,利用标签特征迅速地定位到指定标签,找到所有匹配的标签。

以下eg均始于

res=urllib.urlopen('www.baidu.com').read()

p=bs4.BeautifulSoup(res)

BeautifulSoup返回的对象中,要么是标签对象,要么是标签对象的列表形式(egp.findAll('a')p.contents()p.tr()等的返回值)

BeautifulSoup常用方法,也是本次练手中用到的功能,相信已经能够满足以后的大多需要

1、bs查找指定标签主要使用的方法

eg:要找到符合<div class='small'>的所有标签

p.findAll('div',{'class':'small'})

反复利用标签特征可以找到最终需要的标签

2、直接加标签名可以找到所有子标签

eg:找到所有<td>标签

p.td()

3、直接以字典形式,可以访问标签内对应属性的值

eg:要找到<a href='www.csdn.net'>href的值'www.csdn.net'

p.['href']

4、要获得标签的内容,可以直接加.string

Eg:要找到<p>1235</p>中的'1235'

p.string

---------------------------------

程序流程

Input urlpages

1、打开贴吧首页(url),找到所有给定页(pages)的链接

2、对于每一页,找到所有帖子的链接

3、对于每个帖子,找到所有页的链接(“下一页”和“尾页”的链接不用算在内,且只有一页的情况要另外处理)

4、收集帖子里每一页的ID回复(注意,并非每个回复都有ID

5、对于每个帖子,所有收集到的ID通过mangeId这个函数(manageId判断ID是否存在res,存在ID对应的值加1,不存在插入ID设初值为1)整理到主类的res属性(res为一个字典,keyID,值为回复数)中

6、最后,对主类中的res进行排序,输出结果(这时候我才发现我犯了一个很二的错,字典没有顺序,于是亡羊补牢地做了一个ID类,ID类很简单,有namereply两个属性,然后把res转换为一个ID类的列表,再进行排序)

小结:这个练手之作做得很顺利,一个早上就做得差不多了,关键还是比较简单,复习了一遍简单的排序算法,代码的结构层次也比较清晰易读微笑


#! /usr/bin/env python#coding=utf-8import urllibfrom bs4 import BeautifulSoupimport datetimeimport json,stringimport timeclass idk:    name='None'    reply=0    def __init__(self,name='',reply=0):        self.name=name        self.reply=reply        class TieBa:    def __init__(self,url='http://tieba.baidu.com/f?kw=%CE%E4%BA%BA%B4%F3%D1%A7',count_day=1): #输入贴吧首页地址,和开始计算的时间点距现在的天数        self.url=url        self.tb='http://tieba.baidu.com'        self.res={}        self.count_hours=count_day*24#从距现在多少小时开始起计算        self.already_read_tiezi=[]#记录已经扫过的帖子,防止帖子变动引起的重复统计    #以下为debug,查看某ID是否有重复计算       # self.count=0#debug       # self.record=[]#debug    def FindTieHref(self,url):        res=urllib.urlopen(url).read()        soup=BeautifulSoup(res)       # tie=soup.findAll('tr',{'class':'thread_alt'}) 这句出现一个BUG只找到一半的帖子,实在是失误,感谢davidbauer1117 提出,现改为下        tie=soup.findAll('td',{'class':'thread_title'})        href=[]        for i in tie:            a=i.find('a')            hreft=a['href']#在每一个标签中再细找          #  print a.string.encode('gbk')#debug-输出每个帖子的名字           # print hreft#debug            if len(hreft)>40:                continue            href.append(hreft)        return href    def count_idIntiezi_allpages(self,tiezi='http://'):#遍历指定帖子内所有的页                #改为寻找‘下一页’链接遍历        res_t=self.count_idIntiezi(tiezi=tiezi)        res=res_t[0]        while res_t[1]!='nopages':            res_t=self.count_idIntiezi(tiezi=self.tb+res_t[1])#返回该页的ID和下一页地址            res+=res_t[0]        return res        #以下为直接将帖子中给的链接记录并遍历。这样会使得无法遍历超过十页的帖子        '''        soup=BeautifulSoup(urllib.urlopen(tiezi).read())        pages=soup.find('li',{'class':'l_pager'})        if pages is None:#如果只有一页            return self.count_idIntiezi(tiezi=tiezi)        else:            page_list=pages.findAll('a')            res=self.count_idIntiezi(tiezi=tiezi)            res_t=[]            for i in page_list:                res_t.append(i)#所有页链接放入res_t容器中                        for i in res_t[:-2]:#count每页的id,重复也算在内。最后把列表都相加连在一起                print 'in the tiezi '+i.string+'th page.........is reading'                res+=self.count_idIntiezi(tiezi=self.tb+i['href'])                                    #之所以放入容器,还是为了计数方便,过掉后面两页,否则可以采用下面这种方法            for i in page_list:                print 'in the tiezi '+i.string+'th page.........is reading'                res+=self.count_idIntiezi(tiezi=self.tb+i['href'])                        return res        '''            def count_idIntiezi(self,tiezi='http://tieba.baidu.com/p/1583478764'):        res=urllib.urlopen(tiezi).read()        try:            soup=BeautifulSoup(res)        except:            try:                time.sleep(3)                soup=BeautifulSoup(res)            except:                return [[],'nopages']        #找出下一页的链接        #这里虽然可以使用递归,但还是避免了使用递归,改为在上层做循环        nextpage=''        pages=soup.find('li',{'class':'l_pager'})        if pages is None:#如果只有一页            nextpage='nopages'        else:            page_list=pages.findAll('a')            if page_list[-2].string.encode('utf8')!='下一页':#如果不存在‘下一页’这几个字,即终结                nextpage='nopages'            else:                nextpage=page_list[-2]['href']                print page_list[-2].string+nextpage[-2:]#debug         '''        if self.judge_time(0,soup):#判断是否为当天的帖子,不是则跳过            return []        '''        #还是判断是否为指定时间点之后的回复比较好        post=soup.findAll('div',{'class':'l_post'})        idk=[]        for i in post:            try:                date=json.loads(i.div['data-field'])['content']['date']                if self.jTime(date)==False:                    continue                name=i.find('a',{'class':'p_author_name'}).string                '''                #debug 查看某个ID的回复记录,看是否有重复计算                if name.encode('utf8')=='爱情小小猫':                    self.count+=1                    print name.encode('utf8')                    print date+'.............count='+str(self.count)                    if date in self.record:                        print 'here maybe an error'                        time.sleep(10)                    else:                        self.record.append(date)                '''            except AttributeError,NoneType:#如果找到的ID名字无法识别(可能为IP回复)                name='no name'            idk.append(name)        return [idk,nextpage]    def manage_id(self,idlist=[]):        for i in idlist:            if self.res.has_key(i): #如果存在id,则加1,否则创建id                self.res[i]=self.res[i]+1            else:                self.res[i]=1    def count_idInAll(self,pages=50,begin_time=''):        b=0        tag=0        while b<pages:#遍历贴吧每一页            #每扫三页再对第一页重复扫,以防帖子漏掉            if (b+1)%3!=0 or tag==1:                tag=0                url=self.url+'&pn='+str(b*50)                b+=1                print str(b)+'th page is reading-----------------------------'+url            else:                tag=1                url=self.url                print 'return to page 1 to check if there is new tiezi'            href=self.FindTieHref(url=url)            for tiezi in href:#遍历每一页中的每个帖子                if tiezi in self.already_read_tiezi:#判断帖子是否扫过                    print tiezi+'.............is repeated'                    continue                else:                    self.already_read_tiezi.append(tiezi)                                    print "tiezi.........url="+self.tb+tiezi                idlist_t=self.count_idIntiezi_allpages(tiezi=self.tb+tiezi)#遍历每个帖子中的每个回复                try:                    if len(idlist_t)==0:                        print 'this tiezi does not exist ID required'                        continue                    print 'in the '+str(b)+'th page '+idlist_t[0].encode('gbk')+"'s tiezi.........successed"                except:                    print 'here an error id************************'+str(len(idlist_t))                                self.manage_id(idlist=idlist_t)                '''                #debug---检测每个帖子后是否发生回复量异常                for i in self.res:                    if self.res[i]<10 or i.encode('utf8')!='爱情小小猫':                        continue                    print i.encode('gbk')+'.............'+str(self.res[i])                '''        return self.res    def rangeId(self,res=[]):        idlist_all=[]        for i in res:#把id全转换成ID类放进容器            idlist_all.append(idk(i,res[i]))        ranged_idlist=[]        print str(len(idlist_all))+'........idlist_all'        for i in idlist_all:#来吧……复习一个数据结构,插入排序            if len(ranged_idlist)==0:#                ranged_idlist.append(i)            elif len(ranged_idlist)==1:                print 'here'                if ranged_idlist[0].reply>=i.reply:                    ranged_idlist.append(i)                else:                    ranged_idlist.insert(0,i)            else:                k=0                for l in ranged_idlist:                    if l.reply>i.reply:                        k+=1                        if k==len(ranged_idlist)-1:                            ranged_idlist.append(i)                        continue                    ranged_idlist.insert(k,i)                    break        return ranged_idlist    '''    @staticmethod    def judge_time(hours=0,soup='bs'):#接收时间差,和一个已经做成soup的网页        res=datetime.datetime.now()+datetime.timedelta(hours=hours)        #从soup中找到时间        ppost=soup.find('div',{'class':'p_post'})        date=json.loads(ppost['data-field'])['content']['date']        day=string.atoi(date[8:10])        month=string.atoi(date[5:7])        year=string.atoi(date[:4])        if day!=res.day:            return False        elif month!=res.month:            return False        elif year!=res.year:            return False        else:            return True    '''    def jTime(self,date=''):#只负责判断给定结构的时间是否符合要求        res=datetime.datetime.now()-datetime.timedelta(hours=self.count_hours)#获取指定起始日期        day=string.atoi(date[8:10])        month=string.atoi(date[5:7])        year=string.atoi(date[:4])  #只实现判断到月        if day<res.day and month<=res.month:            return False        elif day>res.day and month<res.month:            return False        elif day==res.day and month<res.month:            return False        elif year!=res.year:            return False     #   elif year!=res.year:      #      return False        else:            return True    def countWaterMaker(self,pages=1):        idlist=self.count_idInAll(pages=pages)        return self.rangeId(idlist)if __name__=='__main__':    a=TieBa(count_day=2)    k=a.countWaterMaker(pages=10)    c=0    print 'water-maker top 30'    for i in k:        if c==30:            break        print str(c)+' th is '+i.name+'...........................'+str(i.reply)        c+=1    '''    k=a.count_idInAll(pages=1)    for i in k:        print i.encode('gbk')        print k[i]    '''