您的位置:首页 > 编程语言 > Python开发

python爬虫实战二之爬取百度贴吧帖子

2017-03-31 14:01 507 查看
目标:

1.对百度贴吧的任意帖子进行抓取

2.指定是否只抓取楼主发帖内容

3.将抓取到的内容分析并保存到文件

1.URL格式的确定

首先,我们先观察一下百度贴吧的任意一个帖子。比如:http://tieba.baidu.com/p/3138733512?see_lz=1&pn=1,分析一下地址

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方法。另外,获取页面的方法我们需要知道一个参数就是帖子页码,所以这个参数的指定我们放在该方法中。

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

import urllib.request
from urllib.request import urlopen
import urllib.error
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().decode('utf-8'))
return response
except urllib.error.URLError as e:
if hasattr(e,'reason'):
print(u'连接百度贴吧失败,错误原因',e.reason)
return None
baseURL='http://tieba.baidu.com/p/3138733512'
bdtb=BDTB(baseURL,1)
bdtb.getPage(1)

部分运行结果:(可以看到屏幕上打印出了这个帖子第一页楼主发言的所有内容,形式为HTML代码)

  <a data-field='{"un":"\u660e\u4e4b\u7ffc"}'alog-group="p_author" class="p_author_name j_user_card" href="/home/main?un=%E6%98%8E%E4%B9%8B%E7%BF%BC&ie=utf-8&fr=pb" target="_blank">明之翼</a>

                    

                </li>

                

                <li class="l_badge" style="display:block;">

                    <div class="p_badge">

                        <a href="/f/like/level?kw=nba&ie=utf-8&lv_t=lv_nav_intro" target="_blank" class="user_badge d_badge_bright d_badge_icon3_1" title="本吧头衔12级,经验值6206,点击进入等级头衔说明页"><div class="d_badge_title ">MVP</div><div class="d_badge_lv">12</div></a>

                    </div>

3.提取相关信息

1)提取帖子标题

关于标题的源代码:

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

所以我们想提取<h3>标签中的内容,同时还要指定这个class唯一,因为h3标签实在太多,正则表达式如下

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

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

def getTitle(self):
page=self.getPage(1)
pattern=re.compile('<h3 class="core_title_txt .*?>(.*?)</h3>',re.S)
result=re.search(pattern,page)
if result:
#print result.group(1)#测试输出
return result.group(1).strip()
else:
return None

2)提取帖子页数

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

#获取帖子一共有多少页
def getPageNum(self,page):
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)提取正文内容

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)

运行部分结果:

很多媒体都在每赛季之前给球员排个名,我也有这个癖好…………,我会尽量理性的分析球队地位,个人能力等因素,评出我心目中的下赛季50大现役球员,这个50大是指预估他本赛季在篮球场上对球队的影响力……不是过去的荣誉什么的,所以难免有一定的主观性……如果把你喜欢的球星排低了,欢迎理性讨论!<img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=557ae4d4fadcd100cd9cf829428947be/a9d6277f9e2f0708468564d9eb24b899a801f263.jpg"
pic_ext="jpeg"  pic_type="0" width="339" height="510"><br><br><br><br>状元维金斯镇楼<br>P.S 1 我每天都至少更新一个,不TJ。<br>      2 今年的新秀我就不考虑了,没上赛季参照

            <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=cb6ab1f8708b4710ce2ffdc4f3ccc3b2/06381f30e924b899d8ca30e16c061d950b7bf671.jpg" pic_ext="jpeg"  pic_type="0" width="339" height="510"><br><br><br><br>50 惊喜新人王 <a href="http://jump2.bdimg.com/safecheck/index?url=x+Z5mMbGPAsY/M/Q/im9DR3tEqEFWbC4Yzg89xsWivS12AkS11WcjnMQsTddE2yXZInIi4k8KEu5449mWp1SxBADVCHPuUFSTGH+WZuV+ecUBG6CY6mAz/Zq1mzxbFxzAG+4Cm4FSU0=" 
class="ps_cb" target="_blank" onclick="$.stats.track(0, 'nlp_ps_word',{obj_name:'迈卡威'});$.stats.track('Pb_content_wordner','ps_callback_statics')">迈卡威</a><br>上赛季数据<br>篮板 6.2  助攻 6.3  抢断 1.9 盖帽  0.6 失误 3.5 犯规  3  得分 16.7<br><br><br>    

可以看到,结果中还有一大片换行符和图片符,既然这样,我们就要对这些文本进行处理,把各种各样复杂标签给它剔除掉,还原精华内容,把文本处理写成一个方法也可以,不过为了实现更好的代码架构,我们可以考虑把标签等的处理写成一个类。

那就向他叫做tool(工具类),里面定义了一个方法,叫replace,是替换各种标签的,在类中定义了几个正则表达式,主要利用了re.sub方法对文本进行匹配后然后替换。具体的思路可以看注释。

import urllib.request
from urllib.request import urlopen
import urllib.error
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>
141ce
')
#将段落开头换为\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()
# 百度贴吧爬虫类
class BDTB:
#初始化,传入基地址,是否只看楼主的参数
def __init__(self,baseurl,seeLZ):
self.baseURL=baseurl
self.seeLZ='?see_lz='+str(seeLZ)
self.tool=Tool()
#传入页码,获取该页帖子的代码
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().decode('utf-8'))
return response.read().decode('utf-8')
except urllib.error.URLError as e:
if hasattr(e,'reason'):
print(u'连接百度贴吧失败,错误原因',e.reason)
return None
def getTitle(self):
page=self.getPage(1)
pattern=re.compile('<h3 class="core_title_txt .*?>(.*?)</h3>',re.S)
#re.S整体匹配
result=re.search(pattern,page)
if result:
#print result.group(1)#测试输出
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:
#print result.group(1)测试输出
return result.group(1).strip()
else:
return None
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)
print (self.tool.replace(items[1]))
baseURL='http://tieba.baidu.com/p/3138733512'
bdtb=BDTB(baseURL,1)
bdtb.getContent(bdtb.getPage(1))

Tool类看的不是特别懂,尤其是7位长空格是什么意思?做不到像原作者一样那么棒的匹配替换

部分运行结果如下:

50 惊喜新人王 迈卡威

上赛季数据

篮板 6.2  助攻 6.3  抢断 1.9 盖帽  0.6 失误 3.5 犯规  3  得分 16.7

新赛季第50位,我给上赛季的新人王迈卡威。 上赛季迈卡威在彻底重建的76人中迅速掌握了球队,一开始就三双搞定了热火赢得了万千眼球。后来也屡屡有经验的表现,新秀赛季就拿过三双的球员不多,迈卡威现在可以说在76人站稳了脚跟。

作为上赛季弱队的老大,迈卡威刷出了不错的数据,但我们静下心来看一看他,还是发现他有很多问题。

4)替换楼层

至于这个问题,我感觉直接提取楼层没什么必要呀,因为只看楼主的话,有些楼层的编号是间隔的,所以我们得到的楼层序号是不连续的,这样我们保存下来也没什么用。

所以可以尝试下面的方法:

(1)每打印输出一段楼层,写入一行横线来间隔,或者换行符也好。

(2)试着重新编写一个楼层,按照顺序,设置一个变量吗,每打印出一个变量结果加一,打印出这个变量当做楼层

修改getContent方法如下:

def getContent(self,page):
pattern=re.compile('<div id="post_content_.*?>(.*?)</div>',re.S)
items=re.findall(pattern,page)
floor=1
for item in items:
print(floor,u'楼..................................................\n')
print (self.tool.replace(item))
floor+=1
部分运行结果:
9 楼..................................................

开更今天的了!
10 楼..................................................

47 神塔的绝唱  保罗加索尔
上赛季数据
篮板9.7  助攻3.4  抢断0.5  盖帽1.5  失误2.4  犯规2.1  得分17.4
作为上赛季湖人唯一一个明星级别的球员(以效率20左右看,科比也没达到)加索尔真心是尽力了,但是将要年满35岁的他还有多少油呢?就不得而知了!
先来看看优点,作为NBA技术流内线的代表,加索尔策应,篮板,背筐,面框都是非常全能的表现,在进攻端,依然是内线做中轴的非常优秀的选择,防守端利用身高臂长,也能在禁区内有一定的威慑力。而且技术流的内线一般下滑比较慢,上赛季加索尔场均才13分本赛季恢复到了上上赛季的接近17+10的水平,不知道加索尔下一站还会不会是湖人,但是他依然可以用他自己的表现,帮助一支需要内线的球队发挥自身的能力。
然后来看看问题所在,加索尔的命中率只有很一般的48%了,哥们你是内线啊!加索尔三分能力本来就不佳但受困于身体对抗能力的下降,加索尔的进攻离篮下越来越远,现在居然连续几个赛季开始丢三分了,但是28%的命中率就凄惨了。只要还在看湖人比赛的人就知道,加索尔现在的问题是身体机能下降,身体很多时候无法支持技术动作的完成,篮下终结力已经从顶级内线变成了一般内线,中距离也是逐年下滑,只是和一般的四号位球员差不多!遇到身体强硬的内线防守人几乎没有出手几乎,而且横移速度也在下滑,加之35岁的年龄,以及球队地位的不稳固(估计在哪一队都很难是当家球星了)我只能把他排在这个位置,不知道明年是否还能在50大之内呢?
11 楼..................................................

4.写入文件

最后便是写入文件的过程,过程很简单,就几句话的代码,主要是利用了以下两句:
file = open(“tb.txt”,”w”)
file.writelines(obj)

5.完善代码

现在对代码进行优化重构,在一些地方添加必要的打印信息,整理如下:
import urllib.request
from urllib.request import urlopen
import urllib.error
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()
# 百度贴吧爬虫类
class BDTB:
#初始化,传入基地址,是否只看楼主的参数
def __init__(self,baseurl,seeLZ,floorTag):
#base链接地址
self.baseURL=baseurl
#是否只看楼主
self.seeLZ='?see_lz='+str(seeLZ)
#HTML标签剔除工具类对象
self.tool=Tool()
#全局file变量,文件写入操作对象
self.file=None
#楼层标号,初始为1
self.floor=1
#默认标题,如果没有成功获取到标题的话则会用这个标题
self.defaultTitle=u'百度贴吧'
#是否写入楼分隔符的标记
self.floorTag=floorTag

#传入页码,获取该页帖子的代码
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().decode('utf-8'))
return response.read().decode('utf-8')
except urllib.error.URLError as e:
if hasattr(e,'reason'):
print(u'连接百度贴吧失败,错误原因',e.reason)
return None
def getTitle(self,page):
pattern=re.compile('<h3 class="core_title_txt .*?>(.*?)</h3>',re.S)
#re.S整体匹配
result=re.search(pattern,page)
if result:
#print result.group(1)#测试输出
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:
#print result.group(1)测试输出
return result.group(1).strip()
else:
return None
#获取每一楼层的内容
def getContent(self,page):
pattern=re.compile('<div id="post_content_.*?>(.*?)</div>',re.S)
items=re.findall(pattern,page)
contents=[]
for item in items:
#将文本进行去除标签处理,同时在前后加入换行符
content='\n'+self.tool.replace(item)+'\n'
contents.append(content.encode('utf-8'))
return contents
def setFileTitle(self,title):
#如果标题不是为None,即成功获取到标题
if title is not None:
self.file=open(title+'.txt','wb')
else:
self.file=open(self.defaultTitle+'.txt','wb')
def writeData(self,contents):
#向文件写入每一楼的信息
for item in contents:
if self.floorTag=='1':
#楼之间的分隔符
floorLine='\n'+str(self.floor)+u'.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................\n'
self.file.write(floorLine.encode())
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('该帖子共有'+str(pageNum)+'页')
for i in range(1,int(pageNum)+1):
print('正在写入第'+str(i)+'页数据')
page=self.getPage(i)
contents=self.getContent(page)
self.writeData(contents)
#出现写入异常
except IOError as e:
print('写入异常,原因'+e.message)
finally:
print('写入任务完成')
print(u'请输入帖子代号')
baseURL='http://tieba.baidu.com/p/'+str(input(u'http://tieba.baidu.com/p/'))
seeLZ=input('是否只获取楼主发言,是输入1,否输入0\n')
floorTag=input('是否写入楼层信息,是输入1,否输入0\n')
bdtb=BDTB(baseURL,seeLZ,floorTag)
bdtb.start()
部分运行结果显示:
 
1.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

AJ群星镇楼。

2.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

几下拉杆来着?我数不清楚。

3.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

酋长?假动作不错,不过我在天上等着你。

4.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

其实90年代无防守,别喷我,我先承认这是我PS的好吗?

5.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

同样的进攻路线,你真不知道他到底会做什么。
部分参考:
静觅 » Python爬虫实战四之抓取淘宝MM照片 


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 网络爬虫