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

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

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)+"\n"

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

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']+"\n"

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+"\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="英雄无敌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这个文章的方法,安装成功

原文地址:http://blog.51cto.com/13904513/2153384

时间: 2024-10-05 04:40:44

爬虫试手——百度贴吧爬虫的相关文章

爬虫入门 手写一个Java爬虫

本文内容 涞源于  罗刚 老师的 书籍 << 自己动手写网络爬虫一书 >> ; 本文将介绍 1: 网络爬虫的是做什么的?  2: 手动写一个简单的网络爬虫; 1: 网络爬虫是做什么的?  他的主要工作就是 跟据指定的url地址 去发送请求,获得响应, 然后解析响应 , 一方面从响应中查找出想要查找的数据,另一方面从响应中解析出新的URL路径, 然后继续访问,继续解析;继续查找需要的数据和继续解析出新的URL路径  . 这就是网络爬虫主要干的工作.  下面是流程图: 通过上面的流程图

Python爬虫爬取百度贴吧的帖子

同样是参考网上教程,编写爬取贴吧帖子的内容,同时把爬取的帖子保存到本地文档: #!/usr/bin/python#_*_coding:utf-8_*_import urllibimport urllib2import reimport sys reload(sys)sys.setdefaultencoding("utf-8")#处理页面标签,去除图片.超链接.换行符等class Tool: #去除img标签,7位长空格 removeImg = re.compile('<img.*

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

今天开源了一个百度云网盘爬虫项目,地址是https://github.com/callmelanmao/yunshare. 百度云分享爬虫项目 github上有好几个这样的开源项目,但是都只提供了爬虫部分,这个项目在爬虫的基础上还增加了保存数据,建立elasticsearch索引的模块,可以用在实际生产环境中,不过web模块还是需要自己开发 安装# 安装node.js和pm2,node用来运行爬虫程序和索引程序,pm2用来管理node任务 安装mysql和mongodb,mysql用来保存爬虫数

爬虫练手,爬取新浪双色彩,信息并进行分析

爬虫练手,爬取新浪双色彩,信息并进行分析 import requests from lxml.html import etree url = 'http://zst.aicai.com/ssq/betOrder/' response = requests.get(url) response_html = etree.HTML(response.text) text_path = '/html/body/div[7]/form/div[2]/table/tbody/tr/td/text()' da

开源百度云分享爬虫项目yunshare最新分享 - 安装篇

今天开源了一个百度云网盘爬虫项目,地址是https://github.com/callmelanmao/yunshare. 百度云分享爬虫项目 github上有好几个这样的开源项目,但是都只提供了爬虫部分,这个项目在爬虫的基础上还增加了保存数据,建立elasticsearch索引的模块,可以用在实际生产环境中,不过web模块还是需要自己开发 安装 安装node.js和pm2,node用来运行爬虫程序和索引程序,pm2用来管理node任务 安装mysql和mongodb,mysql用来保存爬虫数据

[转载]网络爬虫(12):爬虫框架Scrapy的第一个爬虫示例入门教程

我们使用dmoz.org这个网站来作为小抓抓一展身手的对象. 首先先要回答一个问题. 问:把网站装进爬虫里,总共分几步? 答案很简单,四步: 新建项目 (Project):新建一个新的爬虫项目 明确目标(Items):明确你想要抓取的目标 制作爬虫(Spider):制作爬虫开始爬取网页 存储内容(Pipeline):设计管道存储爬取内容 好的,基本流程既然确定了,那接下来就一步一步的完成就可以了. 1.新建项目(Project) 在空目录下按住Shift键右击,选择“在此处打开命令窗口”,输入一

零基础写python爬虫之使用Scrapy框架编写爬虫

网络爬虫,是在网上进行数据抓取的程序,使用它能够抓取特定网页的HTML数据.虽然我们利用一些库开发一个爬虫程序,但是使用框架可以大大提高效率,缩短开发时间.Scrapy是一个使用Python编写的,轻量级的,简单轻巧,并且使用起来非常的方便.使用Scrapy可以很方便的完成网上数据的采集工作,它为我们完成了大量的工作,而不需要自己费大力气去开发. 首先先要回答一个问题. 问:把网站装进爬虫里,总共分几步? 答案很简单,四步: 新建项目 (Project):新建一个新的爬虫项目 明确目标(Item

php爬虫抓取信息及反爬虫相关

58爬虫了百姓,赶集和58互爬,最后各种信息相同,都是爬虫后的数据库调用,潜规则啊,几家独大还暗中各种攻击,赶驴网的幽默事例我不想多评价.这个时代是砸.钱*养.钱的时代,各种姚晨杨幂葛优,各种地铁公车广告,各种卫视广告,铺天盖地~~~ 来谈php爬虫抓取信息~~ php爬虫首推Curl函数了,先来认识下它. 0x01.curl扩展的安装: 1.确保php子文件夹ext里面有php_curl.dll(一般都有的,一般配置时候会设置环境变量的) 2.将php.ini里面的;extension=php

03,Python网络爬虫第一弹《Python网络爬虫相关基础概念》

引入 为什么要学习爬虫,学习爬虫能够为我们以后的发展带来那些好处?其实学习爬虫的原因和为我们以后发展带来的好处都是显而易见的,无论是从实际的应用还是从就业上. 我们都知道,当前我们所处的时代是大数据的时代,在大数据时代,要进行数据分析,首先要有数据源,而学习爬虫,可以让我们获取更多的数据源,并且这些数据源可以按我们的目的进行采集. 优酷推出的火星情报局就是基于网络爬虫和数据分析制作完成的.其中每期的节目话题都是从相关热门的互动平台中进行相关数据的爬取,然后对爬取到的数据进行数据分析而得来的.另一