繼續...基礎框架搭好了,下面來正式的來一個項目吧html
behancegit
全球設計師的做品展現平臺github
就從這拉幾張圖吧,具體的網頁解析方式網上有不少,在此略過,我已經取出了一些圖片地址,保存在了list.txt裏,此次就用這些吧。網絡
綜合多種因素,最後選用了協程來下載圖片session
即asyncio
多線程
框架則用了aiohttp
app
目的框架
將網絡上的圖片(最好是縮略圖)先下載到本地,記錄圖片信息,好比ID以便得到更高質量的圖片,將圖片顯示到界面async
問題oop
爲了更快速的展現頁面,我須要同時下載必定數量的圖片...
我須要動態的將下載任務發送給後臺服務...
這裏能夠在程序啓動的時候設置一個配置列表cfg
from os import path as osPath, getcwd, mkdir ... def __init__(self): ... self.cfg = self.initCfg() def initCfg(self): cfg = {} # 代理,沒有可不用設置 # cfg['proxies'] = '127.0.0.1:61274' # 加載圖片列表 filename = "list.txt" if osPath.exists(filename): with open(filename, "r") as f: cfg['picList'] = f.read().strip().split("\n") # 設置圖片的保存位置 current_folder = getcwd() cfg['pic_temp'] = osPath.join( current_folder, 'pic_temp') if not osPath.isdir( cfg['pic_temp'] ): mkdir(cfg['pic_temp']) return cfg
而後傳遞給服務進程就能夠了
p = Process(target = startServiceP, args = ( self.GuiQueue, self.ServiceQueue, self.cfg ))
先來修改一下html的內容,添加一個自定義控件,用來存放圖片:
<section#body> <button class="click-me">點我下載圖片</button> <widget id="pic-view"></widget> </section>
在服務進程ServiceEvent
裏添加一個方法getPicByList()
def getPicByList(self, msg): # 爲圖片建立佔位圖 imgidList = self.__creatPlaceholderImg() for imgid in imgidList: picHttp = self.cfg['picList'].pop(0) file_name = picHttp.split("/")[-1] file_path = osPath.join( self.cfg['pic_temp'], file_name ) # 圖片下載完成後須要執行的任務 _GuiRecvMsgDict = { 'fun' : 'setImgBg', 'msg' : {'id':imgid,'fpath':file_path} } if not osPath.isfile(file_path): # 將下載任務動態添加到協程循環中 self.__run_coroutine_threadsafe( {'id': imgid,'http': picHttp,'fpath': file_path}, _GuiRecvMsgDict ) else: self.__putGui( 'setImgBg', {'id':imgid,'fpath':file_path} )
當用戶點擊下載圖片的按鈕後會執行到這個方法,爲了更好的體驗,在圖片下載以前先爲其佔據了空間,能夠在其上顯示loading動畫,更重要的一點是,經過它能夠控制圖片的顯示順序,由於用協程下載圖片,你沒法預知完成的順序...
def __creatPlaceholderImg(self): # 先建立5個佔位圖 html = '' imgidList = [] time_now = '' for i in range(0, 5): time_now = '-'.join( ( str(i), str(time()) ) ) # 儲存圖片的id imgidList.append( time_now ) html += self.html % ( time_now ) self.__putGui('creatPlaceholderImg', html) return imgidList
以後就到了動態建立協程的部分了
def __run_coroutine_threadsafe(self, data, _GuiRecvMsgDict): asyncio.run_coroutine_threadsafe(self.dld.stream_download( data, _GuiRecvMsgDict ), self.new_loop)
但在正式介紹run_coroutine_threadsafe()
以前,咱們須要先開啓一個協程循環
但咱們已經開啓了一個用於處理隊列的循環了,沒辦法再開一個(也不排除是咱太菜),因而另開了一個線程專來處理協程
class ServiceEvent(object): '''服務進程''' def __init__(self, _GuiQueue, cfg): ... # 主線程中建立一個事件循環 self.new_loop = asyncio.new_event_loop() self.dld = AsyncioDownload( self.new_loop, self.GuiQueue, self.proxies )
class AsyncioDownload(object): '''使用協程下載圖片''' def __init__(self, loop, _GuiRecvMsg, proxies=None ): self.GuiRecvMsg = _GuiRecvMsg self._session = None self.loop = loop self.prox = ''.join(('http://', proxies)) if proxies else proxies self.timeout = 10 # 啓動一個線程,傳遞主線程中建立的事件循環 t = Thread(target=self.start_loop, args=(self.loop,)) t.setDaemon(True) # 設置子線程爲守護線程 t.start() def start_loop(self, loop): # 啓動事件循環 asyncio.set_event_loop(loop) loop.run_forever() def __session(self): if self._session is None: self._session = aiohttp.ClientSession(loop=self.loop) return self._session async def stream_download(self, d, _GuiRecvMsgDict): try: client = self.__session() async with client.get( d['http'], proxy=self.prox, timeout=self.timeout) as response: if response.status != 200: print('error') return # 保存圖片到本地 if not osPath.isfile(d['fpath']): with open(d['fpath'], 'ab') as file: while True: chunk = await response.content.read(1024) if not chunk: break file.write(chunk) # 圖片下載完成後告知主線程 self.GuiRecvMsg.put(_GuiRecvMsgDict) except asyncio.TimeoutError: pass
最後,主進程得到圖片的id及路徑,顯示到窗口中
function setImgBg( d ){ var div = $(div[imgid="{d.id}"]); if(div){ div.post( ::this.style#background-image = "url(" + d.fpath + ")" ); } }
總結:
完成這個項目使用了
多進程 ---- 後臺服務
多線程 ---- 事件循環
協程 ---- IO操做
相對一個單純的爬蟲腳原本說,仍是有點複雜的,尤爲是交互,難怪這麼多人不肯意寫界面...
雖然還有不足,但本次項目的內容就到這了。
謝謝。