Python3写爬虫(六)爬取百度贴吧帖子并分层显示

      今天用Python3改写一片名为《Python爬虫实战二之爬取百度贴吧帖子》的博客,文章很好,基本就按照这个文章的结构将整个程序重写的一边,收获很大,感谢名为崔庆才的网友(附上他的博客地址:http://cuiqingcai.com/)。并且改用Pycharm编程,很痛快,调试也很方便。

1.URL格式的确定

首先,我们先观察一下百度贴吧的任意一个帖子。

比如:http://tieba.baidu.com/p/3138733512?see_lz=1&pn=1,这是一个关于NBA50大的盘点,分析一下这个地址。

1
2
3
4
http : //  代表资源传输使用http协议
tieba . baidu . com 是百度的 二级域名,指向百度贴吧的服务器。
/ p / 3138733512 服务器某个资源,即这个帖子的地址定位符
see _lz和 pn是该 URL的两个参数,分别代表了只看楼主和帖子页码,等于 1表示该条件为真

所以我们可以把URL分为两部分,一部分为基础部分,一部分为参数部分。

例如,上面的URL我们划分基础部分是 http://tieba.baidu.com/p/3138733512,参数部分是 ?see_lz=1&pn=1

2.页面的抓取

熟悉了URL的格式,那就让我们用urllib2库来试着抓取页面内容吧。上一篇糗事百科我们最后改成了面向对象的编码方式,这次我们直接尝试一下,定义一个类名叫BDTB(百度贴吧),一个初始化方法,一个获取页面的方法。

其中,有些帖子我们想指定给程序是否要只看楼主,所以我们把只看楼主的参数初始化放在类的初始化上,即init方法。另外,获取页面的方法我们需要知道一个参数就是帖子页码,所以这个参数的指定我们放在该方法中。

综上,我们初步构建出基础代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
__author__ = 'BALUW'
import urllib
import urllib.request
import re
 
#百度贴吧爬虫类
class BDTB :
 
     #初始化,传入基地址,是否只看楼主的参数
     def __init__ ( self , baseUrl , seeLZ ) :
         self . baseURL = baseUrl
         self . seeLZ = '?see_lz=' + str ( seeLZ )
 
     #传入页码,获取该页帖子的代码
     def getPage ( self , pageNum ) :
         try :
             url = self . baseURL + self . seeLZ + '&pn=' + str ( pageNum )
             request = urllib.request . Request ( url )
             response = urllib.request . urlopen ( request )
             print response . read ( )
             return response
         except urllib.error . URLError  as  e :
             if hasattr ( e , "reason" ) :
                 print( "连接百度贴吧失败,错误原因:" , e . reason)
                 return None
 
baseURL = 'http://tieba.baidu.com/p/3138733512'
bdtb = BDTB ( baseURL , 1 )
bdtb . getPage ( 1 )

运行代码,我们可以看到屏幕上打印出了这个帖子第一页楼主发言的所有内容,形式为HTML代码。

Python3写爬虫(六)爬取百度贴吧帖子并分层显示_第1张图片

1)提取帖子标题

首先,让我们提取帖子的标题。

在浏览器中审查元素,或者按F12,查看页面源代码,我们找到标题所在的代码段,可以发现这个标题的HTML代码是

1
< h1 class = "core_title_txt  " title = "纯原创我心中的NBA2014-2015赛季现役50大" style = "width: 396px" >纯原创我心中的 NBA2014 - 2015赛季现役 50< / h1 >

所以我们想提取<h1>标签中的内容,同时还要指定这个class确定唯一,因为h1标签实在太多啦。

正则表达式如下

1
< h1 class =" core_title_txt . * ? > ( . * ? ) < / h1 >

所以,我们增加一个获取页面标题的方法

1
2
3
4
5
6
7
8
9
10
#获取帖子标题
def getTitle ( self ) :
     page = self . getPage ( 1 )
     pattern = re . compile ( '<h1 class="core_title_txt.*?>(.*?)</h1>' , re . S )
     result = re . search ( pattern , page )
     if result :
         #print result.group(1)  #测试输出
         return result . group ( 1 ) . strip ( )
     else :
         return None

2)提取帖子页数

同样地,帖子总页数我们也可以通过分析页面中的共?页来获取。所以我们的获取总页数的方法如下

1
2
3
4
5
6
7
8
9
10
#获取帖子一共有多少页
def getPageNum ( self ) :
     page = self . getPage ( 1 )
     pattern = re . compile ( '<li class="l_reply_num.*?</span>.*?<span.*?>(.*?)</span>' , re . S )
     result = re . search ( pattern , page )
     if result :
         #print result.group(1)  #测试输出
         return result . group ( 1 ) . strip ( )
     else :
         return None

3)提取正文内容

审查元素,我们可以看到百度贴吧每一层楼的主要内容都在<div id=”post_content_xxxx”></div>标签里面,所以我们可以写如下的正则表达式

1
< div id =" post_content_ . * ? > ( . * ? ) < / div >

相应地,获取页面所有楼层数据的方法可以写成如下方法

1
2
3
4
5
6
#获取每一层楼的内容,传入页面内容
def getContent ( self , page ) :
     pattern = re . compile ( '<div id="post_content_.*?>(.*?)</div>' , re . S )
     items = re . findall ( pattern , page )
     for item in items :
         print item

好,我们运行一下结果看一下

Python3写爬虫(六)爬取百度贴吧帖子并分层显示_第2张图片

我们就要对这些文本进行处理,把各种各样复杂的标签给它剔除掉,还原精华内容,把文本处理写成一个方法也可以,不过为了实现更好的代码架构和代码重用,我们可以考虑把标签等的处理写作一个类。

那我们就叫它Tool(工具类吧),里面定义了一个方法,叫replace,是替换各种标签的。在类中定义了几个正则表达式,主要利用了re.sub方法对文本进行匹配后然后替换。具体的思路已经写到注释中,大家可以看一下这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import re
 
#处理页面标签类
class Tool :
     #去除img标签,7位长空格
     removeImg = re . compile ( '<img.*?>| {7}|' )
     #删除超链接标签
     removeAddr = re . compile ( '<a.*?>|</a>' )
     #把换行的标签换为\n
     replaceLine = re . compile ( '<tr>|<div>|</div>|</p>' )
     #将表格制表<td>替换为\t
     replaceTD = re . compile ( '<td>' )
     #把段落开头换为\n加空两格
     replacePara = re . compile ( '<p.*?>' )
     #将换行符或双换行符替换为\n
     replaceBR = re . compile ( '<br><br>|<br>' )
     #将其余标签剔除
     removeExtraTag = re . compile ( '<.*?>' )
     def replace ( self , x ) :
         x = re . sub ( self . removeImg , "" , x )
         x = re . sub ( self . removeAddr , "" , x )
         x = re . sub ( self . replaceLine , "\n" , x )
         x = re . sub ( self . replaceTD , "\t" , x )
         x = re . sub ( self . replacePara , "\n    " , x )
         x = re . sub ( self . replaceBR , "\n" , x )
         x = re . sub ( self . removeExtraTag , "" , x )
         #strip()将前后多余内容删除
         return x . strip ( )

在使用时,我们只需要初始化一下这个类,然后调用replace方法即可。

3.程序实现

现在将整体的代码写到下面:

#!C:\Python34\python.exe
__author__ = 'baluw'
import urllib
import urllib.request
import re


class Tool:
    # 去除img标签,7位长空格
    removeImg = re.compile('<img.*?>| {7}|')
    # 删除超链接标签
    removeAddr=re.compile('<a.*?>|</a>')
    # 把换行的标签换为\n
    replaceLine=re.compile('<tr>|<div>|</div>|</p>')
    # 将制表符<td>换成\n
    replaceTD=re.compile('<td>')
    # 将段落开头换为\n加空两格
    replacePara=re.compile('<p.*?>')
    # 将换行符或双换行符替换为\n
    replaceBR=re.compile('<br><br>|<br>')
    # 将其余标签剔除
    rmExtraTag=re.compile('<.*?>')

    def replace(self,x):
        x=re.sub(self.removeImg,"",x)
        x=re.sub(self.removeAddr,"",x)
        x=re.sub(self.replaceLine,"\n",x)
        x=re.sub(self.replaceTD,"\t",x)
        x=re.sub(self.replacePara,"\n",x)
        x=re.sub(self.replaceBR,"\n",x)
        x=re.sub(self.rmExtraTag,"",x)
        # strip()将前后内容删除
        return x.strip()

class TBSpider:
    # 初始化,传入基地地址,是否只看楼主得参数
    def __init__(self, baseUrl, seeLZ,floorTag):
        self.user_agent = 'Mozilla/4.0(compatible:MSIE 5.5;Windows NT)'
        #初始化headers
        self.headers = {'User-Agent': self.user_agent}
        self.baseURL = baseUrl
        self.seeLZ = "?see_lz=" + str(seeLZ)
        # 初始化Tool工具
        self.tool=Tool()
        self.file=None
        self.floor=1
        self.defaultTitle="百度贴吧"
        self.floorTag=floorTag

    #传入页码,获取该页面帖子的代码
    def getPage(self, pageNum) -> object:
        try:
            url = self.baseURL + self.seeLZ + "&pn=" + str(pageNum)
            request = urllib.request.Request(url, headers=self.headers)
            reqonse = urllib.request.urlopen(request)
            content = reqonse.read().decode("utf-8")
            return content
        except urllib.error.URLError as e:
            if hasattr(e, "reason"):
                print("连接百度贴吧失败,错误原因:", e.reason)
                return None

    #获取帖子标题
    def getTitle(self,page):
        pattern = re.compile('<h1 class="core_title_txt.*?>(.*?)</h1>', re.S)
        result = re.search(pattern, page)
        if result:
            return result.group(1).strip()
        else:
            return None

    #提取帖子页数
    def getPageNum(self,page):
        pattern = re.compile('<li class="l_reply_num.*?</span>.*?<span.*?>(.*?)</span>', re.S)
        result = re.search(pattern, page)
        if result:
            return result.group(1).strip()
        else:
            return None

    def getConetent(self, page):
        pattern = re.compile('<div id="post_content_.*?>(.*?)</div>', re.S)
        items = re.findall(pattern, page)
        # 楼层
        contents=[]
        self.floor=1
        for item in items:
            content="\n"+self.tool.replace(item)+"\n"
            #contents.append(content.encode('utf-8'))就是这句出问题了,整了一个小时,终于排查出错误了
            contents.append(content)
        return contents


    def setFileTitle(self,title):
        if title is not None:
            self.file=open(title+".txt","w+")
        else:
            self.file=open(self.defaultTitle+".txt","w+")

    def writeData(self,contents):
        for item in contents:
            if self.floorTag=='1':
                floorLine="\n-------------"+str(self.floor)+"楼--------------\n"
                self.file.write(floorLine)
            self.file.write(item)
            self.floor+=1

    def start(self):
        indexPage=self.getPage(1)
        pageNum=self.getPageNum(indexPage)

        title=self.getTitle(indexPage)
        self.setFileTitle(title)

        if pageNum==None:
            print("URL链接已经失效,请重试")
            return
        try:
            print("该帖子共有{0}页".format(str(pageNum)))
            for i in range(1,int(pageNum)):
                print("正在写入第{0}页数据".format(str(i)))
                page=self.getPage(1)
                contents=self.getConetent(page)
                self.writeData(contents)
        except IOError as e:
            print("写入异常原因:",e.reason)
        finally:
            print("写入任务完成!")

#--------程序入口---------
print("""
------------------------------------
   程序:百度贴吧爬虫
   版本:1.0
   作者:{0}
   日期:2015-04-19
   语言:Python 3.4
   功能:百度贴吧里的内容,分段现实
------------------------------------
""".format(__author__))
print("请输入帖子代号")
baseURL = 'http://tieba.baidu.com/p/' + str(input('http://tieba.baidu.com/p/'))
seeLZ = input("是否只获取楼主发言,是输入1,否输入0\n")
floorTag = input("是否写入楼层信息,是输入1,否输入0\n")
bdtb = TBSpider(baseURL,seeLZ,floorTag)
bdtb.start()
#--------程序入口---------



运行效果如下:

Python3写爬虫(六)爬取百度贴吧帖子并分层显示_第3张图片

你可能感兴趣的:(Python3写爬虫(六)爬取百度贴吧帖子并分层显示)