作者StubbornLin (Victor)
看板Python
标题[教学] Twsited : 美味的串烧
时间Fri Jul 18 16:21:49 2008
接下来介绍如何用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
1F:推 rs6000:大大的blog好像很久没更新了^^" 不过还是很感谢大大的好文 07/19 00:27