[教學] Twsited : 美味的串燒
接下來介紹如何用Deferred來達成一些美好的串燒
我是說,簡單、輕鬆,但又有彈性的程式
我目前在寫的是利用Twsited抓網頁的程式
一開始我自己弄了一些class,利用繼承等方式
來改變處理抓到的資料,後來我發現這實在很蠢
因為處理網頁之類的函數難以重覆使用
物件之間有一堆不必要的藕合
後來我發現,我寫的那些東西根本是多餘的
利用Twsited本身的Deferred機制
就可以讓我把事情做得很簡單又很美好
看看以下程式,是去抓台灣上市公司股票資料的程式
例用BeautifulSoup解析網頁
def parseHtml(html):
"""Function for parse html and get company list
@param content: html to pasrse
"""
from BeautifulSoup import BeautifulSoup
soup = BeautifulSoup(html.decode('cp950'))
# find <table width="100%" border="0" bordercolor="#CCCCCC"
cellpadding="1" cellspacing="1">
table = soup.find('table', dict(width="100%", border="0",
bordercolor="#CCCCCC", cellpadding="1", cellspacing="1"))
trList = table.findAll('tr', dict(align='right'))[2:]
companyList = []
for tr in trList:
tdList = tr.findAll('td')
id = int(tdList[0].contents[0])
name = unicode(tdList[1].contents[0]).replace(' ', '').strip()
close = float(tdList[4].contents[0])
open = float(tdList[5].contents[0])
high = float(tdList[6].contents[0])
low = float(tdList[7].contents[0])
volume = int(tdList[8].contents[0])
companyData = {'id': id,
'name': name,
'open': open,
'close': close,
'high': high,
'low': low,
'volume': volume}
companyList.append(companyData)
return companyList
def getCompanyList():
"""Get company list from web site
"""
from twisted.web.client import getPage
from HandleInThread import handleInThread
d = getPage('http://www.sinotrade.com.tw/today_stock_price.htm')
d.addCallback(handleInThread, parseHtml)
return d
我說過在這之前我用了一些不好的設計來寫
在這裡,你看到的是簡單、但是卻非常有效
低藕合、高重覆使用性的程式
parseHtml被獨立出來,與Twisted一點關系都沒有
它只是被當做"責任鍊"裡的一環,解析html後把資料丟給下一個環節
如果之中有什麼差錯,也是丟給errback的下一個環節去處理,與它一點關係都沒有
而getCompanyList只是利用了getPage這個Twsited提供的取得網頁的函數
然後在它的責任鍊裡加了一個parseHtml的一環,如此而已
使用者就可以輕鬆地呼叫
然後加入他們自己的callback去處理已經是公司資料的list
我知道你還看到了奇怪的東西,是的
def handleInThread(data, func, *args, **kwargs):
from twisted.internet.threads import deferToThread
return deferToThread(func, data, *args, **kwargs)
這是我自己寫的函數,它也是被當作責任鍊的一環
其目的是為了避免怕處理網頁花太多時間
卡到reactor的thread,所以丟給thread-pool去處理
deferToThread的功用,是在thread-pool裡呼叫function
然後function的回傳值丟給它所回傳的Deferred物件
Deferred物件神奇的地方在於,它可以很輕鬆地將一堆東西串起來
在這裡會回傳Deferred物件,而當Deferred物件發現
某個callback回傳的是Deferred物件時,它會自動幫你把它串起來
因此只要在callback裡加上這個handleInThread
就輕輕鬆鬆地把這些東西串起來了
parseHtml它在thread裡被執行,而不是main thread
你不必改任何已存在的程式碼,只是簡單地把它串起來 就是如此美好
我在這裡介紹另一個我寫的好用的callback
我們都知道網路難免會出錯,為了偶爾出錯
而讓程式難以處理,這一直以來都是很麻煩的事情
很直觀的方法之一,重試幾次如果全失敗才算失敗
如果用笨一點的寫法,你可能需要寫
retryGetCompanyList()
retryGetStockData()
....
一一幫你的各種非同步工作寫出可重試的版本?
我知到這是一個惡夢,我一開始差點作了這樣的惡夢
但是後來我發現Deferred的威力比我想像中還驚人
幾乎什麼東西都串得起來,於是我就把retry這樣的功能
用deferred串了起來,以下是retry的原始碼
from twisted.internet import defer, threads
def retry(times, func, *args, **kwargs):
errorList = []
deferred = defer.Deferred()
def run():
d = func(*args, **kwargs)
d.addCallbacks(deferred.callback, error)
def error(error):
errorList.append(error)
# Retry
if len(errorList) < times:
run()
# Fail
else:
deferred.errback(errorList)
run()
return deferred
def retryForCallback(data, times, func, *args, **kwargs):
return retry(times, func, data, *args, **kwargs)
if __name__ == '__main__':
from twisted.internet import reactor
from twisted.web.client import getPage
def output(data):
print 'output', data
def error(error, data):
print 'finall error', error.value
d = retry(2, getPage, 'http://www.google.com')
d.addCallbacks(output, error)
reactor.run()
從此以後,當我想讓某個非同步的工作重試5次才算錯誤
我只要像上面的測試例子
用retry去串,輕鬆地就把retry的功能串到任何非同步工作去
以上只是一些簡單的例子,我還寫了限制同時最多task數量的串燒
讓我抓網頁的工作可以很輕鬆的完成
如你所見,它們之間沒什麼關係,只要到處串來串去就搞定了
這就是它美味的地方 XD
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 118.170.5.243
推
07/19 00:27, , 1F
07/19 00:27, 1F
Python 近期熱門文章
PTT數位生活區 即時熱門文章