用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壁紙站。扣小說、遊戲、圖片。你懂的

我只是默默學習編程。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章