用SGMLParser爬取天涯的帖子

                之前在天涯论坛看到一高三老师的一篇帖子,是高三一年的记录。当时就想扣下来,虽然只分九页,但每页有百来屏,采取纯手工的方法不可取。做个工具以后还可以用!但一直没动手。

               这两天突然看到《任务列表.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 类数据。

  • 开始标记 (Start tag) 
  • 结束标记 (End tag) 
  • 字符引用 (Character reference) 
  • 字符引用 (Character reference) 
  • 注释 (Comment) 
  • 处理指令 (Processing instruction) 
  • 声明 (Declaration) 
  • 文本数据 (Text data) 
后来发现,这个类 将HTML整个字符串分成一段一段 的。碰到哪一类就调用相应的函数(跟API一样,函数名和参数形式要一致)。如果你想对div进行处理:
def start_div(self,attrs):
#写自己的处理代码
pass
对文本块的处理
def handle_data(self,text):
#写自己的处理代码
pass    

#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壁纸站。扣小说、游戏、图片。你懂的

我只是默默学习编程。



你可能感兴趣的:(爬虫,SGMLParser)