关键词:
自学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.网络爬虫是什么?(代码片段)
网络爬虫又称网络蜘蛛、网络机器人,它是一种按照一定的规则自动浏览、检索网页信息的程序或者脚本。网络爬虫能够自动请求网页,并将所需要的数据抓取下来。通过对抓取的数据进行处理,从而提取出有价值的... 查看详情
通用爬虫和聚焦爬虫
爬虫分为两个领域:聚焦爬虫和通用爬虫。通用爬虫: 搜索引擎用的爬虫系统。目标: 搜索互联网所有的信息下载下来,放到本地服务器,再对这些网页进行相关处理,提取关键字什么的,最终给用户提供一个检索的接... 查看详情