python实现爬虫统计学校BBS男女比例之多线程爬虫(二)
接着第一篇继续学习。
一、数据分类
正确数据:id、性别、活动时间三者都有
放在这个文件里file1='ruisi\\correct%s-%s.txt'%(startNum,endNum)
数据格式为293001男2015-5-119:17
- 没有时间:有id、有性别,无活动时间
放这个文件里file2='ruisi\\errTime%s-%s.txt'%(startNum,endNum)
数据格式为2566女notime
- 用户不存在:该id没有对应的用户
放这个文件里file3='ruisi\\notexist%s-%s.txt'%(startNum,endNum)
数据格式为29005notexist
- 未知性别:有id,但是性别从网页上无法得知(经检查,这种情况也没有活动时间)
放这个文件里file4='ruisi\\unkownsex%s-%s.txt'%(startNum,endNum)
数据格式221794unkownsex
- 网络错误:网断了,或者服务器故障,需要对这些id重新检查
放这个文件里file5='ruisi\\httperror%s-%s.txt'%(startNum,endNum)
数据格式271004httperror
如何不间断得爬虫信息
- 本项目有一个考虑:是不间断爬取信息,如果因为断网、BBS服务器故障啥的,我的爬虫程序就退出的话。那我们还得从间断的地方继续爬,或者更麻烦的是从头开始爬。
- 所以,我采取的方法是,如果遇到故障,就把这些异常的id记录下来。等一次遍历之后,才对这些异常的id进行重新爬取性别。
- 本文系列(一)给出了一个getInfo(myurl,seWord),通过给定链接和给定正则表达式爬取信息。
- 这个函数可以用来查看性别的最后活动时间信息。
- 我们再定义一个安全的爬取函数,不会间断程序运行的,这儿用到tryexcept异常处理。
这儿代码试了两次getInfo(myurl,seWord),如果第2次还是抛出异常了,就把这个id保存在file5里面
如果能获取到信息,就返回信息
file5='ruisi\\httperror%s-%s.txt'%(startNum,endNum) defsafeGet(myid,myurl,seWord): try: returngetInfo(myurl,seWord) except: try: returngetInfo(myurl,seWord) except: httperrorfile=open(file5,'a') info='%d%s\n'%(myid,'httperror') httperrorfile.write(info) httperrorfile.close() return'httperror'
依次遍历,获取id从[1,300,000]的用户信息
我们定义一个函数,这儿的思路是获取sex和time,如果有sex,进而继续判断是否有time;如果没sex,判断是否这个用户不存在还是性别无法爬取。
其中要考虑到断网或者BBS服务器故障的情况。
url1='http://rs.xidian.edu.cn/home.php?mod=space&uid=%s' url2='http://rs.xidian.edu.cn/home.php?mod=space&uid=%s&do=profile' defsearchWeb(idArr): foridinidArr: sexUrl=url1%(id)#将%s替换为id timeUrl=url2%(id) sex=safeGet(id,sexUrl,sexRe) ifnotsex:#如果sexUrl里面找不到性别,在timeUrl再尝试找一下 sex=safeGet(id,timeUrl,sexRe) time=safeGet(id,timeUrl,timeRe) #如果出现了httperror,需要重新爬取 if(sexis'httperror')or(timeis'httperror'): pass else: ifsex: info='%d%s'%(id,sex) iftime: info='%s%s\n'%(info,time) wfile=open(file1,'a') wfile.write(info) wfile.close() else: info='%s%s\n'%(info,'notime') errtimefile=open(file2,'a') errtimefile.write(info) errtimefile.close() else: #这儿是性别是None,然后确定一下是不是用户不存在 #断网的时候加上这个,会导致4个重复httperror #可能用户的性别我们无法知道,他没有填写 notexist=safeGet(id,sexUrl,notexistRe) ifnotexistis'httperror': pass else: ifnotexist: notexistfile=open(file3,'a') info='%d%s\n'%(id,'notexist') notexistfile.write(info) notexistfile.close() else: unkownsexfile=open(file4,'a') info='%d%s\n'%(id,'unkownsex') unkownsexfile.write(info) unkownsexfile.close()
这儿后期检查发现了一个问题
sex=safeGet(id,sexUrl,sexRe) ifnotsex: sex=safeGet(id,timeUrl,sexRe) time=safeGet(id,timeUrl,timeRe)
这个代码如果断网的时候,调用了3次safeGet,每次调用都会往文本里面同一个id写多次httperror
251538httperror 251538httperror 251538httperror 251538httperror
多线程爬取信息?
数据统计可以用多线程,因为是独立的多个文本
1、Popen介绍
使用Popen可以自定义标准输入、标准输出和标准错误输出。我在SAP实习的时候,项目组在linux平台下经常使用Popen,可能是因为可以方便重定向输出。
下面这段代码借鉴了以前项目组的实现方法,Popen可以调用系统cmd命令。下面3个communicate()连在一起表示要等这3个线程都结束。
疑惑?
试验了一下,必须3个communicate()紧挨着才能保证3个线程同时开启,最后等待3个线程都结束。
p1=Popen(['python','ruisi.py',str(s0),str(s1)],bufsize=10000,stdout=subprocess.PIPE) p2=Popen(['python','ruisi.py',str(s1),str(s2)],bufsize=10000,stdout=subprocess.PIPE) p3=Popen(['python','ruisi.py',str(s2),str(s3)],bufsize=10000,stdout=subprocess.PIPE) p1.communicate() p2.communicate() p3.communicate()
2、定义一个单线程的爬虫
用法:pythonruisi.py<startNum><endNum>
这段代码就是爬取[startNum,endNum)信息,输出到相应的文本里。它是一个单线程的程序,若要实现多线程的话,在外部调用它的地方实现多线程。
#ruisi.py #coding=utf-8 importurllib2,re,sys,threading,time,thread #myurlas指定链接 #seWordas正则表达式,用unicode表示 #返回根据正则表达式匹配的信息或者None defgetInfo(myurl,seWord): headers={ 'User-Agent':'Mozilla/5.0(Windows;U;WindowsNT6.1;en-US;rv:1.9.1.6)Gecko/20091201Firefox/3.5.6' } req=urllib2.Request( url=myurl, headers=headers ) time.sleep(0.3) response=urllib2.urlopen(req) html=response.read() html=unicode(html,'utf-8') timeMatch=seWord.search(html) iftimeMatch: s=timeMatch.groups() returns[0] else: returnNone #尝试两次getInfo() #第2次失败后,就把这个id标记为httperror defsafeGet(myid,myurl,seWord): try: returngetInfo(myurl,seWord) except: try: returngetInfo(myurl,seWord) except: httperrorfile=open(file5,'a') info='%d%s\n'%(myid,'httperror') httperrorfile.write(info) httperrorfile.close() return'httperror' #输出一个idArr范围,比如[1,1001) defsearchWeb(idArr): foridinidArr: sexUrl=url1%(id) timeUrl=url2%(id) sex=safeGet(id,sexUrl,sexRe) ifnotsex: sex=safeGet(id,timeUrl,sexRe) time=safeGet(id,timeUrl,timeRe) if(sexis'httperror')or(timeis'httperror'): pass else: ifsex: info='%d%s'%(id,sex) iftime: info='%s%s\n'%(info,time) wfile=open(file1,'a') wfile.write(info) wfile.close() else: info='%s%s\n'%(info,'notime') errtimefile=open(file2,'a') errtimefile.write(info) errtimefile.close() else: notexist=safeGet(id,sexUrl,notexistRe) ifnotexistis'httperror': pass else: ifnotexist: notexistfile=open(file3,'a') info='%d%s\n'%(id,'notexist') notexistfile.write(info) notexistfile.close() else: unkownsexfile=open(file4,'a') info='%d%s\n'%(id,'unkownsex') unkownsexfile.write(info) unkownsexfile.close() defmain(): reload(sys) sys.setdefaultencoding('utf-8') iflen(sys.argv)!=3: print'usage:pythonruisi.py<startNum><endNum>' sys.exit(-1) globalsexRe,timeRe,notexistRe,url1,url2,file1,file2,file3,file4,startNum,endNum,file5 startNum=int(sys.argv[1]) endNum=int(sys.argv[2]) sexRe=re.compile(u'em>\u6027\u522b</em>(.*?)</li') timeRe=re.compile(u'em>\u4e0a\u6b21\u6d3b\u52a8\u65f6\u95f4</em>(.*?)</li') notexistRe=re.compile(u'(p>)\u62b1\u6b49\uff0c\u60a8\u6307\u5b9a\u7684\u7528\u6237\u7a7a\u95f4\u4e0d\u5b58\u5728<') url1='http://rs.xidian.edu.cn/home.php?mod=space&uid=%s' url2='http://rs.xidian.edu.cn/home.php?mod=space&uid=%s&do=profile' file1='..\\newRuisi\\correct%s-%s.txt'%(startNum,endNum) file2='..\\newRuisi\\errTime%s-%s.txt'%(startNum,endNum) file3='..\\newRuisi\\notexist%s-%s.txt'%(startNum,endNum) file4='..\\newRuisi\\unkownsex%s-%s.txt'%(startNum,endNum) file5='..\\newRuisi\\httperror%s-%s.txt'%(startNum,endNum) searchWeb(xrange(startNum,endNum)) #numThread=10 #searchWeb(xrange(endNum)) #total=0 #foriinxrange(numThread): #data=xrange(1+i,endNum,numThread) #total=+len(data) #t=threading.Thread(target=searchWeb,args=(data,)) #t.start() #printtotal main()
多线程爬虫
代码
#coding=utf-8 fromsubprocessimportPopen importsubprocess importthreading,time startn=1 endn=300001 step=1000 total=(endn-startn+1)/step ISOTIMEFORMAT='%Y-%m-%d%X' #hardcode3threads #沒有深究3个线程好还是4或者更多个线程好 #输出格式化的年月日时分秒 #输出程序的耗时(以秒为单位) foriinxrange(0,total,3): startNumber=startn+step*i startTime=time.clock() s0=startNumber s1=startNumber+step s2=startNumber+step*2 s3=startNumber+step*3 p1=Popen(['python','ruisi.py',str(s0),str(s1)],bufsize=10000,stdout=subprocess.PIPE) p2=Popen(['python','ruisi.py',str(s1),str(s2)],bufsize=10000,stdout=subprocess.PIPE) p3=Popen(['python','ruisi.py',str(s2),str(s3)],bufsize=10000,stdout=subprocess.PIPE) startftime='['+time.strftime(ISOTIMEFORMAT,time.localtime())+']' printstartftime+'%s-%sdownloadstart...'%(s0,s1) printstartftime+'%s-%sdownloadstart...'%(s1,s2) printstartftime+'%s-%sdownloadstart...'%(s2,s3) p1.communicate() p2.communicate() p3.communicate() endftime='['+time.strftime(ISOTIMEFORMAT,time.localtime())+']' printendftime+'%s-%sdownloadend!!!'%(s0,s1) printendftime+'%s-%sdownloadend!!!'%(s1,s2) printendftime+'%s-%sdownloadend!!!'%(s2,s3) endTime=time.clock() print"costtime"+str(endTime-startTime)+"s" time.sleep(5)
这儿是记录时间戳的日志:
"D:\ProgramFiles\Python27\python.exe"E:/pythonProject/webCrawler/sum.py [2015-11-2311:31:15]1-1001downloadstart... [2015-11-2311:31:15]1001-2001downloadstart... [2015-11-2311:31:15]2001-3001downloadstart... [2015-11-2311:53:44]1-1001downloadend!!! [2015-11-2311:53:44]1001-2001downloadend!!! [2015-11-2311:53:44]2001-3001downloadend!!! costtime1348.99480677s [2015-11-2311:53:50]3001-4001downloadstart... [2015-11-2311:53:50]4001-5001downloadstart... [2015-11-2311:53:50]5001-6001downloadstart... [2015-11-2312:16:56]3001-4001downloadend!!! [2015-11-2312:16:56]4001-5001downloadend!!! [2015-11-2312:16:56]5001-6001downloadend!!! costtime1386.06407734s [2015-11-2312:17:01]6001-7001downloadstart... [2015-11-2312:17:01]7001-8001downloadstart... [2015-11-2312:17:01]8001-9001downloadstart...
上面是多线程的Log记录,从下面可以看出,1000个用户平均需要500s,一个id需要0.5s。500*300/3600=41.666666666667小时,大概需要两天的时间。
我们再试验一次单线程爬虫的耗时,记录如下:
"D:\ProgramFiles\Python27\python.exe"E:/pythonProject/webCrawler/sum.py 1-1001downloadstart... 1-1001downloadend!!! costtime1583.65911889s 1001-2001downloadstart... 1001-2001downloadend!!! costtime1342.46874278s 2001-3001downloadstart... 2001-3001downloadend!!! costtime1327.10885725s 3001-4001downloadstart...
我们发现一次线程爬取1000个用户耗时的时间也需要1500s,而多线程程序是3*1000个用户耗时1500s。
故多线程确实能比单线程省很多时间。
Note:
在getInfo(myurl,seWord)里有time.sleep(0.3)这样一段代码,是为了防止批判访问BBS,而被BBS拒绝访问。这个0.3s对于上文多线程和单线程的统计时间有影响。
最后附上原始的,没有带时间戳的记录。(加上时间戳,可以知道程序什么时候开始爬虫的,以应对线程卡死情况。)
"D:\ProgramFiles\Python27\python.exe"E:/pythonProject/webCrawler/sum.py 1-1001downloadstart... 1001-2001downloadstart... 2001-3001downloadstart... 1-1001downloadend!!! 1001-2001downloadend!!! 2001-3001downloadend!!! costtime1532.74102812s 3001-4001downloadstart... 4001-5001downloadstart... 5001-6001downloadstart... 3001-4001downloadend!!! 4001-5001downloadend!!! 5001-6001downloadend!!! costtime2652.01624951s 6001-7001downloadstart... 7001-8001downloadstart... 8001-9001downloadstart... 6001-7001downloadend!!! 7001-8001downloadend!!! 8001-9001downloadend!!! costtime1880.61513696s 9001-10001downloadstart... 10001-11001downloadstart... 11001-12001downloadstart... 9001-10001downloadend!!! 10001-11001downloadend!!! 11001-12001downloadend!!! costtime1634.40575553s 12001-13001downloadstart... 13001-14001downloadstart... 14001-15001downloadstart... 12001-13001downloadend!!! 13001-14001downloadend!!! 14001-15001downloadend!!! costtime1403.62795496s 15001-16001downloadstart... 16001-17001downloadstart... 17001-18001downloadstart... 15001-16001downloadend!!! 16001-17001downloadend!!! 17001-18001downloadend!!! costtime1271.42177906s 18001-19001downloadstart... 19001-20001downloadstart... 20001-21001downloadstart... 18001-19001downloadend!!! 19001-20001downloadend!!! 20001-21001downloadend!!! costtime1476.04122024s 21001-22001downloadstart... 22001-23001downloadstart... 23001-24001downloadstart... 21001-22001downloadend!!! 22001-23001downloadend!!! 23001-24001downloadend!!! costtime1431.37074164s 24001-25001downloadstart... 25001-26001downloadstart... 26001-27001downloadstart... 24001-25001downloadend!!! 25001-26001downloadend!!! 26001-27001downloadend!!! costtime1411.45186874s 27001-28001downloadstart... 28001-29001downloadstart... 29001-30001downloadstart... 27001-28001downloadend!!! 28001-29001downloadend!!! 29001-30001downloadend!!! costtime1396.88837788s 30001-31001downloadstart... 31001-32001downloadstart... 32001-33001downloadstart... 30001-31001downloadend!!! 31001-32001downloadend!!! 32001-33001downloadend!!! costtime1389.01316718s 33001-34001downloadstart... 34001-35001downloadstart... 35001-36001downloadstart... 33001-34001downloadend!!! 34001-35001downloadend!!! 35001-36001downloadend!!! costtime1318.16040825s 36001-37001downloadstart... 37001-38001downloadstart... 38001-39001downloadstart... 36001-37001downloadend!!! 37001-38001downloadend!!! 38001-39001downloadend!!! costtime1362.59222822s 39001-40001downloadstart... 40001-41001downloadstart... 41001-42001downloadstart... 39001-40001downloadend!!! 40001-41001downloadend!!! 41001-42001downloadend!!! costtime1253.62498539s 42001-43001downloadstart... 43001-44001downloadstart... 44001-45001downloadstart... 42001-43001downloadend!!! 43001-44001downloadend!!! 44001-45001downloadend!!! costtime1313.50461988s 45001-46001downloadstart... 46001-47001downloadstart... 47001-48001downloadstart... 45001-46001downloadend!!! 46001-47001downloadend!!! 47001-48001downloadend!!! costtime1322.32317331s 48001-49001downloadstart... 49001-50001downloadstart... 50001-51001downloadstart... 48001-49001downloadend!!! 49001-50001downloadend!!! 50001-51001downloadend!!! costtime1381.58027296s 51001-52001downloadstart... 52001-53001downloadstart... 53001-54001downloadstart... 51001-52001downloadend!!! 52001-53001downloadend!!! 53001-54001downloadend!!! costtime1357.78699459s 54001-55001downloadstart... 55001-56001downloadstart... 56001-57001downloadstart... 54001-55001downloadend!!! 55001-56001downloadend!!! 56001-57001downloadend!!! costtime1359.76377246s 57001-58001downloadstart... 58001-59001downloadstart... 59001-60001downloadstart... 57001-58001downloadend!!! 58001-59001downloadend!!! 59001-60001downloadend!!! costtime1335.47829775s 60001-61001downloadstart... 61001-62001downloadstart... 62001-63001downloadstart... 60001-61001downloadend!!! 61001-62001downloadend!!! 62001-63001downloadend!!! costtime1354.82727645s 63001-64001downloadstart... 64001-65001downloadstart... 65001-66001downloadstart... 63001-64001downloadend!!! 64001-65001downloadend!!! 65001-66001downloadend!!! costtime1260.54731607s 66001-67001downloadstart... 67001-68001downloadstart... 68001-69001downloadstart... 66001-67001downloadend!!! 67001-68001downloadend!!! 68001-69001downloadend!!! costtime1363.58255686s 69001-70001downloadstart... 70001-71001downloadstart... 71001-72001downloadstart... 69001-70001downloadend!!! 70001-71001downloadend!!! 71001-72001downloadend!!! costtime1354.17163074s 72001-73001downloadstart... 73001-74001downloadstart... 74001-75001downloadstart... 72001-73001downloadend!!! 73001-74001downloadend!!! 74001-75001downloadend!!! costtime1335.00425259s 75001-76001downloadstart... 76001-77001downloadstart... 77001-78001downloadstart... 75001-76001downloadend!!! 76001-77001downloadend!!! 77001-78001downloadend!!! costtime1360.44054978s 78001-79001downloadstart... 79001-80001downloadstart... 80001-81001downloadstart... 78001-79001downloadend!!! 79001-80001downloadend!!! 80001-81001downloadend!!! costtime1369.72662457s 81001-82001downloadstart... 82001-83001downloadstart... 83001-84001downloadstart... 81001-82001downloadend!!! 82001-83001downloadend!!! 83001-84001downloadend!!! costtime1369.95550676s 84001-85001downloadstart... 85001-86001downloadstart... 86001-87001downloadstart... 84001-85001downloadend!!! 85001-86001downloadend!!! 86001-87001downloadend!!! costtime1482.53886433s 87001-88001downloadstart... 88001-89001downloadstart... 89001-90001downloadstart...
以上就是关于python实现爬虫统计学校BBS男女比例的第二篇,重点介绍了多线程爬虫,希望对大家的学习有所帮助。