Lab4是不是又出新擴展實驗了?

讀了這麼多年書,頭一次看到大家搶着做作業,做遲了就白做了。真心佩服fm的機制設計。有人說,刷fm的網站,要像刷微博一樣勤快,否則遲了就沒有機會了。對於這種機械性操作,作爲CS的人,自然想讓它自動化。於是就有了下面的設計。

總體設計:

整體結構:

核心流程圖:

總體設計考慮:

  • 運行平臺,希望它哪個平臺都能跑,最重要的還是能在樹莓派這個linux的系統上跑。因爲,我不想7*24開着筆記本,但是,樹莓派嘛,隨便它開着吧。
  • 計算機語言的選擇:C,C++是我常用的,但是,我得找好多庫,否則這事沒法幹。C#,java,C#沒用過,而且又不能在linux上跑。java用過一些,但是知道不多。因爲,要在樹莓派上跑,java太重量級了。這件事情可能還是腳本做比較好。python,pearl,ruby,之前只用過一段時間的python,還是相當不錯的。而且各種庫,幹什麼事都省力了。
  • 之前想過,如果出現了新題目,用其他方式來提醒。比如:在PC端寫一個軟件,讓樹莓派來觸發這個軟件,然後,在桌面上,給我提示。又或者是通過發送飛信的方式來提醒。但是,他們都不如發郵件來得省力。
  • 除了能夠收到郵件提醒之外,我還希望能夠將提交數目可視化出來,從而得到一些有趣的結論。也就是這裏的Construct web&graphs.

模塊設計:

Web Login&Downloader 模塊:

實現方法:

因爲,此處需要用到http協議,所以,尋找相應的庫來完成是最方便的。我使用的是httplib2,一個號稱要取代python的標準庫httplib。不管是POST還是GET,亦或是加上cookie機制,都是簡單好用的,下面的代碼就是Login的代碼。

    def Login(self):
        #Login.
        url = self.baseUrl+'login.php'
        body = {'passwd':'***********','user':'***********'}#****處需要填入相應的用戶名和密碼。
        headers = {'Content-type':'application/x-www-form-urlencoded'}

        response,content=self.http.request(url,'POST',headers=headers,body=urllib.urlencode(body))
        self.cookie=response['set-cookie']
這樣就得到了login之後的cookie了,以後只要將這個cookie傳給web server,它就知道你是登錄了的那個了。然後,就可以去下載網頁了。
    def _GetOnePage(self,url):
        headers = {'Content-type':'application/x-www-form-urlencoded'}
        headers['Cookie'] = self.cookie
        response, content = self.http.request(url, 'GET', headers=headers)

Html Parser 模塊:

設計考慮:

  • 不得不說,這是有重新造輪子的嫌疑。在我寫這個模塊之前,就認爲一定會有這樣一個庫非常完美的完成了這一切。後來,我發現python標準庫裏面就有。不過,我這個爲的不是實用,更多的是訓練。從實現效率考量,我不應該用python去實現,應該用C或者C++去實現,更加不應該實用費時的正則表達式去識別標籤以及其他東西。但是,從訓練考量,因爲我一直沒用過正則表達式,這次是一次嘗試。所以也就有了以下代碼。

實現方法:

實現思路也很簡單,先用正則表達式去識別出start tag 和 end tag,然後將它們看成左右括號,用堆棧的方法進行括號匹配。這裏有點小麻煩是,有些tag它只有開始沒有結束比如:<br>。一種方案是說,那就將這些特殊的記住,然後特殊處理。另一種方案是說,如果,我在匹配end tag時出現錯誤(也就是當前棧頂的那個tag,不是end tag對應的start tag),那就認爲當前棧頂的tag是一個特殊tag只有開始沒有結束。如果,網頁是完全按照規範來做的話,這樣就夠了。但是,現實世界比較複雜。就我parse的那個網頁,它就不符合規範。平白無故多出了幾個end tag。然後,我再進行了一些錯誤處理。總共代碼也就150+行,代碼寫得也就一般般,沒做太多測試,也沒做錯誤處理。

import re
class HtmlParser:
	def __init__(self):
		self.rootDocObject=None
		self.html=None
	def Parse(self,html):
		self.html=html
		self.rootDocObject=ParseHtml(self.html);
		return self.rootDocObject
	def GetRootDocObject(self):
		return self.rootDocObject
	def ShowHtml(self):
		return self._ShowHtml(self.rootDocObject,0)
	def _ShowHtml(self,docObject,level):
		parentTag = None
		if(docObject.GetParent() is not None):
			parentTag = docObject.GetParent().GetTag()
		else:
			parentTag = "I'm the root!"
		print(level*"\t"+"TagName:"+docObject.GetTag()+";Attr:"+str(docObject.GetAttr()) \
			+";ParentName:"+parentTag)
		for i in docObject.GetBody():
			if(isinstance(i,basestring)):
				print((level+1)*"\t"+i)
			else:
				self._ShowHtml(i,level+1)
		return None
	
class DocObject:
	def __init__(self,tag=None,attr=None,parent=None):
		self.tag=tag
		self.attr=attr #attr should be a dict.
		self.parent=parent #parent should be a DocObject.
		self.body=[] #should be a list.element type is either string or DocObject.

	def GetTag(self):
		return self.tag
	def GetAttr(self):
		return self.attr
	def GetParent(self):
		return self.parent
	def GetBody(self):
		return self.body;
	#def FindText(self,text,pos):
	#	for i in self.body[pos:]:
	#		if isinstance(i,basestring) && i==text:
	#			return i
	#	return None
	def FindTag(self,text,pos):
		index=pos
		for i in self.GetBody()[pos:]:
			if (not isinstance(i,basestring)) and i.GetTag()==text:
				print(text)
				return (i,index)
			index+=1
		return None
	
				

def ParseAttr(str):
	"""The str should be striped the tags"""

	reModule = re.compile(r"((?P<key1>\w+) *= *['\"](?P<value1>[-=&_%@!#,$+?:.; \w/]*)['\"])|((?P<key2>\w+) *= *(?P<value2>[-=&_%@!#,$+?:.;\w/]+))")
	iter=reModule.finditer(str)
	#print(str)
	attrDict = {}
	for i in iter:
		if i.group('key1') is not None:
			attrDict[i.group('key1')]=i.group('value1')
		else:
			attrDict[i.group('key2')]=i.group('value2')
	return attrDict

def ParseHtml(html):

	#stack
	stack=[]

	#The root DocObject
	rootDocObject = DocObject("root",None,None)
	stack.append(rootDocObject)

	#regular expression
	reTag = re.compile(r"(?P<all>(<(?P<startTagName>\w+)(?P<attr>.*?)>)|(</(?P<endTag>\w+)>)|(<!--(?P<comment>[\s\S]*?)-->))")
	
	normalTextStart = 0
	pos = 0

	reMatch = reTag.search(html,pos)
	while(reMatch is not None):
		#append the text.
		normalTextEnd = reMatch.start('all')
		text = html[normalTextStart:normalTextEnd]
		if(len(str.strip(text))!=0): #check whether text is consist of space.
			stack[len(stack)-1].GetBody().append(text);
		normalTextStart = reMatch.end('all')
		
		#deal with the tag.
		tagName = None
		attr = None
		if(reMatch.group('startTagName') is not None):
			#It is a start tag.
			tagName = reMatch.group('startTagName')
			attr = ParseAttr(reMatch.group('attr'))
			docObject = DocObject(tagName,attr,stack[len(stack)-1])
			stack[len(stack)-1].GetBody().append(docObject);
			stack.append(docObject)
		elif(reMatch.group('endTag') is not None):
			#It is a end tag.Go to the stack,and find the start tag.
			tagName = reMatch.group('endTag')
			findIt = False
			for i in stack:
				if(i.GetTag()==tagName):
					findIt=True
					break
			if findIt:
				if len(stack)==1:
					print("Error:Pos:"+str(pos)+";tagName:"+tagName)
					return None #Error here.
				docObject=stack.pop()
				while(docObject.GetTag()!=tagName):
					docObjectParent=stack[len(stack)-1]
					docObjectBodyLen = len(docObject.GetBody())
					docObjectParentLen = len(docObjectParent.GetBody())
					docObjectParent.GetBody()[docObjectParentLen:docObjectParentLen+docObjectBodyLen]=\
						docObject.GetBody()
					del docObject.GetBody()[0:docObjectBodyLen]

					if len(stack)==1:
						print("Error:Pos:"+str(pos)+";tagName:"+tagName)

						return None #Error here.
					docObject=stack.pop()				
			else:
				#find a little error.
				print("A little Error:Find the end tag but without start one." \
				+"Pos:"+str(pos)+";tagName:"+tagName)

		elif(reMatch.group('comment') is not None):
			#It is comment tag.
			tagName = "comment" #reMatch.group('comment')
			attr = None #ParseAttr(reMatch.group('attr'))
			docObject = DocObject(tagName,attr,stack[len(stack)-1])
			stack[len(stack)-1].GetBody().append(docObject)
			docObject.GetBody().append(reMatch.group('comment'))
		else:
			#it must be a bug.
			return None
		pos = reMatch.end('all')
		reMatch = reTag.search(html,pos)
	return rootDocObject

Send Email模塊:

實現方法:

這次是使用python裏的標準庫,也是十分簡單,就可以完成發郵件的功能。此處碰到了一個怪事,如果我不用ssl的方法發送郵件,速度會很慢10s左右,使用了ssl,就1s差不多了。

def mail(content):
    import smtplib
    from email.mime.text import MIMEText

    fromAddr = "[email protected]"
    toAddr = "[email protected]"
    msg = MIMEText(content,'html')
    msg['Subject']="ES Assignments Report"
    msg['From']=fromAddr
    msg['To']=toAddr

    smtp = smtplib.SMTP_SSL("smtp.126.com")
    smtp.set_debuglevel(4)
    smtp.login('zju3100102665','*********')#***爲密碼
    smtp.sendmail(fromAddr,toAddr,msg.as_string())
    smtp.quit()

Store Difference 模塊:

設計考慮:

  • 在存儲從網站上抓取下來的數據時,我並沒有將所有數據都存儲下來,那樣子太大了,而且大部分都是無用數據。我只在網站上數據更新後,在本地保存一份。

實現方法:

基本就是文件和字符串處理的事情。文件的結構如下圖所見。


Construct Web&Graphs 模塊:

設計考慮:

  • 對於構建web 網頁,那只有一種辦法也是最簡單的就是直接打開一個文件然後寫進去就可以了。但是,如何創建一個graph呢?這個至少有兩種方案。第一種是找一個圖形庫,然後直接用textout,drawline之類的方法畫出來。還有一種方案是生成一個腳本,然後讓這個腳本去畫出圖形來。從效率上來說,一般是第一個高,同樣這也是最常見的方法。但是,第二個的好處是靈活。能夠完成第二種功能的,有gnuplot,maple,matlab等,如果,是最終要在網頁上顯示的話,javascript也算一個。它們一般需要數據文件,也需要腳本文件。類似的還有graphviz,只是它只要數據,不需要腳本,但是隻能畫圖論中的圖。此處,我選擇的是gnuplot。顯然,matlab和map等數學軟件都太重量級了。javascript也是一種比較好的選擇,使用d3庫的話,圖形結果應該是比較漂亮而且自由度更高。但是,程序複雜度提高了好些,爲了不這麼麻煩。最終選擇了gnuplot。gnuplot這個東西是在1986年出現的,非常老的一個工具,主要流行在科學工作者之間。他可以將結果輸出到屏幕,png,svg,convas等上面,所以靈活度大大增加。此處,我是將結果輸出到了svg文件中,方便在瀏覽器中顯示。

實現方法:

實現上,我需要輸出html文檔,將待會生成的圖片嵌入到裏面去。還有就是生成gnuplot腳本以及相應的數據。最後執行腳本。下面給一個數據和腳本的樣例吧。

118.dat

2013-03-15#14:24 10 0
2013-03-15#15:31 10 0
2013-03-15#16:01 10 1
2013-03-15#16:21 10 3
2013-03-15#16:42 10 5
2013-03-15#16:52 10 6
2013-03-15#17:52 10 8
2013-03-15#18:02 10 9
2013-03-15#18:42 10 10
118.gp

set terminal svg
set output "118.svg"
set title "Code:118"
set timefmt "%Y-%m-%d#%H:%M"
set format x "%m-%d\n%H:%M"
set xdata time
set xlabel "Date"
set ylabel "Times"
plot "118.dat" using 1:2 with linespoints title "submits","118.dat" using 1:3 with linespoints title "submited" lt 4
結果:

系統結果:

總體效果:

效果十分不錯,這個系統是在2周前開始運行的,期間還是幫助我搶到了3個左右的實驗。同時也蒐集到了一些有趣的數據,接下來我們會看到。

郵箱部分效果:

網頁部分效果:

數據分析:

  • 介紹一下這個圖怎麼看。首先,X軸是時間,橫軸上的標籤比如03-15\n15:30指的是3月15日,15點30分。縱軸提交的此處。紅色的折線表示總的提交數的變化。天藍色的折線表示現在提交數目的變化。圖中只是畫出了,從題目出現到它提交滿並且不再變化的時候。
  • 完成速度最快的一次記錄:1:40分鐘完成。
  • 完成的最曲折的一次:fm or ta 很認真啊,半夜在看報告,更有同學半夜在改報告。
  • 之後若是有好玩的圖,則持續更新。如果,你和我在同一層樓的話,你應該可以打開這個http://222.205.46.240/es/來看看其他圖。

後記:

前後斷斷續續寫了好幾天,直到近日纔將其寫完。總共代碼只有700行,但是寫了總共5天差不多。也就是差不多在50小時左右。程序說難也不難,主要是對python以及它的庫,並不熟悉,經歷了比較長的學習時間,也是出了很多難纏的bug。尤其是在對象引用這塊。

通過這樣一次訓練,還是學習到了很多東西。比如:python,正則表達式,網絡編程,略略的使用了一下gnuplot。

代碼是寫的比較糟糕的,但是如果你希望得到源代碼參考一下的話,那麼可以給我發郵件,郵箱應該在上面的代碼中有。

備註:

此爲浙江大學計算機學院嵌入式課程實驗報告。

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