之前在天涯論壇看到一高三老師的一篇帖子,是高三一年的記錄。當時就想扣下來,雖然只分九頁,但每頁有百來屏,採取純手工的方法不可取。做個工具以後還可以用!但一直沒動手。
這兩天突然看到《任務列表.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)
#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壁紙站。扣小說、遊戲、圖片。你懂的
我只是默默學習編程。