引子

我一直是一个懒惰的人

每次水博客时发文章都需要花费很多的精力

不管是寻找图片素材,还是调整比例,都不是一件容易的事情

后来看了群里耳东橙的blog令我大受启发,他的封面都是一个模板制作的,而且非常好看。

接着我参考了一下github上的一个项目,简单有效。就是不知道为什么保存图片的时候老是出错…

https://github.com/kuaidy/BackImg

于是橙子哥推荐我自己造一个轮子,并且推荐了pywebIO给我。

我花了点时间尝试了一下,写了一个功能非常有限的,生成封面图的项目~

封面图生成

实现的思路主要是先用python写出功能,接着通过pywebIO编程一个web服务。

python

之前简单了解过python的pillow库

发现用来批量生成图片非常的方便

于是写了一小段代码来生成一张简单的封面图

代码的逻辑非常的简单

  • 生成一张符合比例的纯色底板
  • 导入字体文件,然后在底板上加字

唯一比较麻烦的地方在于,第一次生成的时候需要协调背景的分辨率、字体大小等方面,需要多尝试几次。

pywebIO

pywebIO是橙子哥推荐给我的。这是它的中文文档,写得通俗易懂,非常容易上手。

这个项目可以构建简单的Web应用或基于浏览器的GUI应用 ,而且在这个过程当中不需要额外的学习前端的语法,学了python就可以简单使用。

部署

对于本小白来说,部署可以说是头等大事。

因为缺乏基础知识,我总担心自己研究了半天,最后没办法运行使用。

幸好pywebIO的部署方式比较好懂。

作者在使用指南中提到了一个demo,我们可以贴出来先观察一下。

from pywebio import start_server
from pywebio.input import *
from pywebio.output import *
from pywebio.session import info as session_info

def t(eng, chinese):
    """return English or Chinese text according to the user's browser language"""
    return chinese if 'zh' in session_info.user_language else eng

def main():
    """BMI Calculation
    Simple application for calculating Body Mass Index.
    计算BMI指数的简单应用
    """

    put_markdown(t("""# Body Mass Index
    
    [Body mass index](https://en.wikipedia.org/wiki/Body_mass_index) (BMI) is a measure of body fat based on height and weight that applies to adult men and women. 
    
    BMI Categories:
    
    | Category             | BMI           |
    | -------------------- | ------------- |
    | Severely underweight | BMI<14.9      |
    | Underweight          | 14.9≤BMI<18.4 |
    | Normal               | 18.4≤BMI<22.9 |
    | Overweight           | 22.9≤BMI<27.5 |
    | Moderately obese     | 27.5≤BMI<40   |
    | Severely obese       | BMI≥40        |
    
    ## BMI calculation
    The source code of this application is [here](https://github.com/wang0618/PyWebIO/blob/dev/demos/bmi.py)
    """, """# BMI指数
    [`BMI指数`](https://baike.baidu.com/item/%E4%BD%93%E8%B4%A8%E6%8C%87%E6%95%B0/1455733)(Body Mass Index,BMI),是用体重千克数除以身高米数的平方得出的数字,是国际上常用的衡量人体胖瘦程度以及是否健康的一个标准。
    
    成年人的BMI值处于以下阶段
    
    | 体形分类 | BMI值范围 |
    | ------ | -------- |
    | 极瘦    | BMI<14.9    |
    | 偏瘦    | 14.9≤BMI<18.4     |
    | 正常    | 18.4≤BMI<22.9     |
    | 过重    |  22.9≤BMI<27.5  |
    | 肥胖    |  27.5≤BMI<40  |
    | 非常肥胖 |     BMI≥40      |
    
    ## BMI指数计算器
    本程序的源代码[链接](https://github.com/wang0618/PyWebIO/blob/dev/demos/bmi.py)
    
    """))

    info = input_group(t('BMI calculation', '计算BMI:'), [
        input(t("Your Height(cm)", "请输入你的身高(cm)"), name="height", type=FLOAT),
        input(t("Your Weight(kg)", "请输入你的体重(kg)"), name="weight", type=FLOAT),
    ])

    BMI = info['weight'] / (info['height'] / 100) ** 2

    top_status = [(14.9, t('Severely underweight', '极瘦')), (18.4, t('Underweight', '偏瘦')),
                  (22.9, t('Normal', '正常')), (27.5, t('Overweight', '过重')),
                  (40.0, t('Moderately obese', '肥胖')), (float('inf'), t('Severely obese', '非常肥胖'))]

    for top, status in top_status:
        if BMI <= top:
            put_markdown(t('Your BMI: `%.1f`, Category: `%s`', '你的 BMI 值: `%.1f`,身体状态: `%s`') % (BMI, status))
            break

if __name__ == '__main__':
    start_server(main, debug=True, port=8080)

BMI Calculation

以上是代码跟对应的网站效果。

代码中首先定义了一个main 函数,然后最后使用start_server() 进行调用。当程序运行时,就会出现一个网页。而网页的端口等其他的数据都可以参考文档进行修改。

除了start_server() 之外,还有一个path_deploy_http() , 它跟前者的区别在于,是从一个目录来部署PyWebIO应用,所以需要传入当前py文件的路径。当网页打开的时候,并不是直接显示网页本身,而是一个上层的目录。当然这个页面也可以修改成自己想要的样式。

所以我想到部署的方式很简单:

  1. 在服务器上安装python,并把需要运行的文件上传。
  2. 然后在命令行下输入python3 file.py
  3. 通过服务器的IP+刚才设定的端口即可访问网页,如49.132.12.123:8312

通过以上的方式,勉强算是可以访问与使用。但是通过IP访问毕竟还是麻烦的,于是就想着跟之前部署docker一样,反向代理+ssl一套搞上,最好再挂个CDN。


事与愿违,我在使用的start_server() 进行部署时,如果通过ip访问,那么一切正常。当时一旦用域名访问,那么只能显示一个白色的页面,其他的任何内容都没有。

截屏2023-01-05 19.15.55.png

以上是我在项目文档中找到的提示,我估计就是对应的这个原因。于是我按照上面的要求去配置反向代理。但是说实话,虽然我经常在折腾网站,但都是依赖的宝塔面板,Nginx根本就没有搞懂过,所以按照作者这个方法来配置我并没有成功….

天无绝人之路,虽然start_server() 的方式不行,但是path_deploy_http() 的方式进行部署的时候一点问题都没有,一切正常。


似乎是已经解决了问题,但是defer_call() 没办法进行使用。

所谓的defer_call() 就是在会话结束之后调用的函数。而我的项目正好就需要用到这个。

所以现在属于是矛盾了,要不就是选择使用defer_call() ,要不就是选择不能反向代理。

这个问题我还去github上提问,有人给了我一个方案,就是使用caddy 来反向代理。caddy真的是属于开箱即用,但是跟Nginx是冲突,我现在还没有做好用caddy来代替Nginx的准备,所以暂时把服务放在自己家里的服务器先挂着。如果以后有机会我倒是真的想全局使用caddy。

后来过了一段时间,又有开发者回复说没有问题,都是能够调用的,不也怕麻烦试了,就这样好了。

输入函数

如何借助pywebIO把自己的代码变成一个网页端的形式呢?

作者在文档里面很多次强调需要把整个过程想象成一个命令行式的逻辑。

首先推荐它自带的线上代码调试工具:

PyWebIO Playground: Edit, Run, Share PyWebIO Code Online

按照下图输入命令,就可以自动生成一个输入框的组件。

截屏2023-01-05 22.04.43.png

其他的输入函数都可以参照文档。

不过我想特别说一下[input_group](https://pywebio.readthedocs.io/zh_CN/latest/input.html#pywebio.input.input_group) 函数,这里是成组输入,返回的是一组字典。我之前学python的时候正好跳过了字典这个基础知识…结果通过这个项目才知道字典最基本的使用方法。

输出函数

输出函数就是负责展示各种信息,包括文字图片等,排版的时候甚至还支持markdown语法。

我在写自己这个生成封面图的项目时,需要用到pillow的库,生成一个Image 的实例,输出函数[put_image](https://pywebio.readthedocs.io/zh_CN/latest/output.html#pywebio.output.put_image) 则支持直接读取这种格式,非常的方便。

输出函数中比较重要的我觉得是按钮。[put_button](https://pywebio.readthedocs.io/zh_CN/latest/output.html#pywebio.output.put_button) 里有一个onclick参数,可以绑定函数,按下按钮就调用。我刚开始写的时候怎么写都不太对,后来参照文档然后写成下面的样子:

put_button('重新生成',onclick=lambda : re_create('color'))

我现在还是没有明白为什么要用lambda,为啥不能直接写成onclick=re_creat() 的形式~

页面布局

页面布局我觉得主要涉及到了两个方面,首先第一个就是输出函数里面的输出域[use_scope](https://pywebio.readthedocs.io/zh_CN/latest/output.html#pywebio.output.use_scope)

这个函数的用法其实很简单,页面上呈现的内容一定会属于一个,如果没有定义的话,那么就会处在一个默认的域。

举一个简单例子,比如我想在页面最上方拥有一个固定的地方来介绍我的项目,然后下面则是输入、输出的操作部分。

put_markdown("""# 项目介绍
- 输入参数
- 生成封面图
""")

input("输入函数的各种参数")

put_text("生成成功")

以上的示例运行之后,初始的页面便是:

  1. put_markdown 函数所呈现的标题及介绍
  2. 一个输入框

当输入参数之后,下一个页面是:

  1. put_text 所生成的文字。

显然的,最开始的标题与介绍已经不见了,因为就像在命令行输入命令一样,当你提交参数之后就会返回新的内容。当然这不是我们的本意,因为我们需要这个标题无论什么情况都需要保持在最上方,于是使用[use_scope](https://pywebio.readthedocs.io/zh_CN/latest/output.html#pywebio.output.use_scope) 对代码进行调整。

with use_scope('title'):
    put_markdown("""# 项目介绍
    - 输入参数
    - 生成封面图
    """)

input("输入函数的各种参数")
put_text("生成成功")

以上代码就把标题及介绍放到了一个叫做title 的域单独管理,而输入输出函数则不会对其造成影响,当然也可以把输入输出函数放到别的域中进行分割。总之,起到了一个互不干扰的作用。

[use_scope](https://pywebio.readthedocs.io/zh_CN/latest/output.html#pywebio.output.use_scope) 还有一个比较重要的参数,就是clear 这里需要填入一个布尔值来控制新的内容进入时旧的内容会不会清除。

with use_scope('title',clear=True):
    put_markdown("""# 项目介绍
    - 输入参数
    - 生成封面图
    """)

input("输入函数的各种参数")
put_text("生成成功")

with use_scope('title',clear=True):
    put_markdown("""我是新的提示
    """)

例如在上面的代码中,输出内容之后我需要新的提示信息,我再次把内容放到title 的域中。此时clear=True 所有最开始的标题与介绍都会被清除,反之,以前的信息就会被保留。


另一个跟页面布局有确切关系的就是[put_row](https://pywebio.readthedocs.io/zh_CN/latest/output.html#pywebio.output.put_row) 等函数,用法其实比较简单,就是把要展现的内容按照横排、竖排或者表格等方式进行排列。我比较在意的是里面各个元素的比例关系。

通常来说,我们的元素会进行平分,例如使用[put_row](https://pywebio.readthedocs.io/zh_CN/latest/output.html#pywebio.output.put_row) 函数,把两个按钮在横向位置进行排列,那么两个按钮会各占一半,如果是三个按钮就是各占1/3。我在实际应用的时候,需要让两张图片并排输出,此时有一定小问题,因为中间完全没有任何空隙。

文档中给了很多种解决方式,首先就是在size 函数中指定None ,这样会默认给它划分10像素的宽度。这个宽度值可以进行更改:

文档上的介绍

  • 像素值: 例如: 100px
  • 百分比: 表示占可用宽度的百分比. 例如: 33.33%
  • fr 关键字: 表示比例关系, 2fr 表示的宽度为 1fr 的两倍
  • auto 关键字: 表示由浏览器自己决定长度
  • minmax(min, max) : 产生一个长度范围,表示长度就在这个范围之中。它接受两个参数,分别为最小值和最大值。 例如: minmax(100px, 1fr) 表示长度不小于100px,不大于1fr

其中auto 并不奏效,并不能按我满意的方式进行排列,可能是我的用法有问题。百分比的方式是我最开始选用的方式,但是在手机上好像自适应并不是做得很好,于是我最后选择了fr 进行控制。

成品

项目已经开源了

好叭

虽然我想也没人会用

确实比较简陋

不过下面也贴了一个示范站点

https://github.com/xhhdd/simple_platform_cover

封面图生成

总结

以上描述了使用pywebIO作为网络框架创建封面图片工具的过程。它解释了如何使用Python的Pillow库生成一个简单的封面图片,以及如何使用pywebIO创建一个Web服务。它还概述了部署项目的过程。

最后修改:2023 年 01 月 15 日
如果觉得我的文章对你有用,请随意赞赏