资讯专栏INFORMATION COLUMN

Python3结合Sciter编写桌面程序第三节

fobnn / 2600人阅读

摘要:第三节协程继续基础框架搭好了,下面来正式的来一个项目吧全球设计师的作品展示平台就从这拉几张图吧,具体的网页解析方式网上有很多,在此略过,我已经取出了一些图片地址,保存在了里,这次就用这些吧。

第三节 协程!?

继续...基础框架搭好了,下面来正式的来一个项目吧

behance

全球设计师的作品展示平台

就从这拉几张图吧,具体的网页解析方式网上有很多,在此略过,我已经取出了一些图片地址,保存在了list.txt里,这次就用这些吧。

综合多种因素,最后选用了协程来下载图片

asyncio

框架则用了aiohttp

实现思路:

目的

将网络上的图片(最好是缩略图)先下载到本地,记录图片信息,比如ID以便获得更高质量的图片,将图片显示到界面

问题

为了更快速的展示页面,我需要同时下载一定数量的图片...

我需要动态的将下载任务发送给后台服务...

这里可以在程序启动的时候设置一个配置列表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("
")
        # 设置图片的保存位置
        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的内容,添加一个自定义控件,用来存放图片:


    
    

在服务进程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操作

相对一个单纯的爬虫脚本来说,还是有点复杂的,尤其是交互,难怪这么多人不愿意写界面...

虽然还有不足,但本次项目的内容就到这了。

谢谢。

GPU云服务器 云服务器 第三节 webrtc结合electron桌面分享 Sciter 安卓程序编写

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/40761.html

相关文章

发表评论

0条评论

fobnn

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<