之前在天涯论坛看到一高三老师的一篇帖子,是高三一年的记录。当时就想扣下来,虽然只分九页,但每页有百来屏,采取纯手工的方法不可取。做个工具以后还可以用!但一直没动手。
这两天突然看到《任务列表.txt》里有 这个任务记录,便开始复习python了。
高三老师日记 2014-2015 http://bbs.tianya.cn/post-no16-276224-1.shtml
整个工作是昨天下午开始的 也就是2015-08-15.因为要上班 下午5:32开始写,大概7点下班。
回来后从7:51开始到11:09结束,抓了374张图,文字1.22MB。写这个时当时一算,发现用了将近5小时,便思考值不值?
首先是对帖子每一楼的分析,发现每一楼都是在div里有id class js_username js_resttime等属性。帖子的主要内容是在div class="bbs-content"里。因为只关注楼主的帖子,通过div的js_username属性就可以把其他用户的帖子过滤掉了。关键是对这一楼是楼主的帖子还是楼主的回复的区分。楼主发帖基本是日常记录,基本没有什么闲聊。如果是回复,div.bbs-content里会有a标签而且是第一个。
帖子里可能有img标签,还有就是<br/>转'\n',不然文字没分段。resttime里有时间,因为是日记贴,也有必要抓下来。
下面就是爬虫部分了。实现上面的思路。
首先要对SGMLParseer这个模块有了解,继承这个类自己对标签进行处理。刚开始我还是对里面的函数有误解的。
这个模块用于解析HTML,SGMLParser 将 HTML 分析成 8 类数据。
#coding:utf-8 import re,cookielib,urllib2,socket,random from sgmllib import SGMLParser #模拟浏览器获取html class Browser(object): def __init__(self): #20 --> 4 节约了很多时间 爬一千个url由15.5s-->6.5s socket.setdefaulttimeout(4) def speak(self,content): print '[%s]' %(content) def openurl(self,url): cookie_support= urllib2.HTTPCookieProcessor(cookielib.CookieJar()) self.opener = urllib2.build_opener(cookie_support,urllib2.HTTPHandler) urllib2.install_opener(self.opener) user_agents = [ 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11', 'Opera/9.25 (Windows NT 5.1; U; en)', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12', 'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9', "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7", "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0 ", ] agent = random.choice(user_agents) self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://www.baidu.com')]; #self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://bbs.tianya.cn/post-no04-2185536-1.shtml')]; res=''; err=[]; try: res = self.opener.open(url) #print res.read() except Exception,e: #self.speak(str(e)+url) #对于因为网络不稳定带来的错误 应当 再获取两次 减少误差 print '获取网页失败,尝试重新获取'; if len(err)!=0: try: res = self.opener.open(url); print '获取成功!'; except Exception,e: print '尝试获取网页失败!'; #raise Exception return res; def getHTML(self,url): ress=''; restemp = self.openurl(url); try: if restemp!=None and restemp!='': ress = restemp.read(); else: ress = ''; except Exception,e: print 'read error!'; return ress; #用于获取图片地址 def getFileName(filestr): m1=re.compile(r'(?P<path>[0-9_A-Za-z-]+(.jpg|.png|.gif)$)') extName=m1.search(filestr) if extName!=None: return extName.group('path') else: return '';
#getURLs从SGMLParser继承 class getURLs(SGMLParser): def reset(self): self.bItem=False; self.bContent=False; self.id=0; self.time=''; self.content=''; SGMLParser.reset(self); def start_div(self,attrs): temptime=''; tempid=''; for k,v in attrs : if k=='js_username' and v=='%E7%A6%85%E9%A6%99%E9%9B%AA': self.bItem=True; if k=='js_restime': temptime=v; if k=='id': tempid=v; if k=='class' and v=='bbs-content': self.bContent=True; if self.bItem: self.time=temptime; self.id=tempid; self.bItem=False; def start_br(self,attrs): self.content+='\n\t'; def start_img(self,attrs): src=[v for k,v in attrs if k=='original']; if src: print src; filename=getFileName(src[0]); self.content+='\n\t'; self.content=self.content+'<img src="images/'+filename+'" style="width:80%;"/>'; self.content+='\n\t'; def end_div(self): if self.bContent: self.bContent=False; fh.write('{"id":"'+str(self.id)+'",'+'"time":"'+self.time+'",'+'"content":"'+self.content.replace('"',"'").strip()+'"},\n'); arrItem.append('0'); print str(self.id)+'\n'; self.id='id'; self.time='time'; self.content=''; def handle_data(self,text):#处理文本 if self.bContent==True: self.content+=text; if __name__ == '__main__': #===================全局变量============================== #入口地址 entrance='http://bbs.tianya.cn/post-no16-276224-1.shtml'; #分页页面地址规则 rule = 'http://bbs.tianya.cn/post-no16-276224-\d.shtml'; arrPage=[entrance]; arrItem=range(0); curr=1; id=0; time=''; #分页地址匹配 m = re.compile(r'(?P<path>'+rule+')'); fh = open("f:\\test\\recorder.txt",'a+'); #====================进入入口=========================== spider = Browser(); html = spider.getHTML(entrance); obj = getURLs(); obj.feed(html); obj.close(); #开始循环 for i in range(2,10): html = spider.getHTML('http://bbs.tianya.cn/post-no16-276224-'+str(i)+'.shtml'); obj = getURLs(); obj.feed(html); obj.close; print '第'+str(curr)+'页'; curr=curr+1; print '完毕!'; fh.close();
就是这段程序完成了抓取整个帖子和图片。但没有对发帖和回复的区分。输出的还是json格式。最后会多个逗号。
当时没考虑传参。移植性差。
class getURLs(SGMLParser)这句是说getURLs是继承于SGMLParser
今天打算写个模块,可以传参,发现__init__和reset两个函数始终有矛盾。后来将reset里的都放到__init__构造函数里了。
既然是模块,当然希望按引用传参。然而int和string类型只能按值传参。参数写成数组的形式就可以按引用传参了。
另外发现了个关于定义空数组的问题。arr=[];是可以,但这种方法arr.append(xxx);始终没效果。第二种方法是arr=range(0);
期间发现了两个有趣的东西。href=[v for k,v in attrs if k=='href'];和 value in Array==True判断数组是否有变量。然而第二个我没用,换成了自己的函数arrHas(a,arr)
还有问题,就是HTML里面div嵌套非常多,像<div>aaa<p>bbb</p>ccc</div>要获取里面的文本就麻烦一些。一般方法是在构造函数里加个变量self.isDiv=False;
在def start_div(self,attrs):self.isDiv=True;在def end_div(self): self.isdiv=False;
而帖子里每楼都有5、6div嵌套的。还要加个self.divDepth=-1。用于记录嵌套的深度。start_div就自增,end_div时就自减。
后来写了个获取网页链接的模块
#coding:utf-8 ''' auto:阮家友 time:2015-08-16 14:22 ''' import re,cookielib,urllib2,socket,random,urllib,urlparse from sgmllib import SGMLParser class Browser(object): def __init__(self): #20 --> 4 节约了很多时间 爬一千个url由15.5s-->6.5s socket.setdefaulttimeout(4) def speak(self,content): print '[%s]' %(content) def openurl(self,url): cookie_support= urllib2.HTTPCookieProcessor(cookielib.CookieJar()) self.opener = urllib2.build_opener(cookie_support,urllib2.HTTPHandler) urllib2.install_opener(self.opener) user_agents = [ 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11', 'Opera/9.25 (Windows NT 5.1; U; en)', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12', 'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9', "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7", "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0 ", ] agent = random.choice(user_agents) self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://www.baidu.com')]; #self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://bbs.tianya.cn/post-no04-2185536-1.shtml')]; res=''; err=[]; try: res = self.opener.open(url); except Exception,e: #self.speak(str(e)+url) #对于因为网络不稳定带来的错误 应当 再获取1次 减少误差 print '获取网页失败,尝试重新获取'; if len(err)!=0: try: res = self.opener.open(url); print '获取成功!'; except Exception,e: print '尝试获取网页失败!'; print url; #raise Exception return res; def getHTML(self,url): ress=''; restemp = self.openurl(url); try: if restemp!=None and restemp!='': ress = restemp.read(); else: ress = ''; except Exception,e: print 'read error!'; return ress; def arrHas(strArr,string): for i in range(0,len(strArr)): if(strArr[i]==string): return True; return False; class getURLs(SGMLParser): def __init__(self,args): self.len=0; self.url = args[0][0]; self.arrPage=args[0]; self.arrDest=args[1]; self.pageRule = args[2]; self.DestRule = args[3]; SGMLParser.reset(self); spider = Browser(); html=spider.getHTML(self.url); self.url=urlparse.urlparse(self.url); pathinfo = self.url.path.split('/'); if len(pathinfo)>0 and pathinfo[len(pathinfo)-1].find('.')!=-1: pathinfo.pop(); self.url.path = pathinfo.join('/'); self.feed(html); def start_a(self,attrs): #print attrs; href=[v for k,v in attrs if k=='href']; if href: tempurl = urlparse.urlparse(href[0]); if(tempurl.netloc==''): temppathinfo=tempurl.path.split('/'); pathstr = '/'.join(temppathinfo); pathstr = pathstr.lstrip('/'); href[0]='http://'+self.url.netloc+self.url.path+pathstr; #=========分页地址=================== r = self.pageRule.search(href[0]); if r!=None and arrHas(self.arrPage,href[0])==False: self.arrPage.append(href[0]); #=========终页地址=================== r = self.DestRule.search(href[0]); if r!=None and arrHas(self.arrPage,href[0])==False : self.arrDest.append(href[0]); print href[0]; return ; def __del__(self): print '析构函数将执行'; self.close(); if __name__ == '__main__': #进行参数构造 entrance = 'http://h.7k7k.com/pc/'; m1 = re.compile(r'(?P<path>http://h.7k7k.com/pc/\d+)'); m2 = re.compile(r'(?P<path>http://flash1.7k7k.com/h5/)'); arrPage=[entrance]; arrDest=range(0); arguments = [arrPage,arrDest,m1,m2]; print arguments; o1 = getURLs(arguments); del o1; print 'list page url:' for i in range(0,len(arrPage)): print arrPage[i]; print 'destiny page url:' for j in range(0,len(arrDest)): print arrDest[j]; #del o1; print 'url 全部获取!';
这个获取了所有的游戏分页地址和游戏页面地址。
还写了个模块,专门获取页面文章(或小说),跟上面差不多,测试了一下将笔趣阁的星战风暴1130章全都down了下来。
实习的另外两个同学想玩玩,做小说站、游戏站和APP壁纸站。扣小说、游戏、图片。你懂的
我只是默默学习编程。