分类目录归档:计算机与编程

开始写作《Flask Web 开发实战》第二版

Flask Web 开发实战》第二版已经开始写作,计划在今年完成这本书。那本《Python Web API 设计与开发》一直拖着没写完,让我感觉很焦虑。因为拖得太久,HelloFlask 群聊里已经开始谣传我转行送快递了 :D。

所以我想先把《Flask Web 开发实战》新版完成,这样这一年我至少完成了三个年度目标中的一个。「优化」是我擅长和喜欢做的事情,除了要用 Word 写之外,我很乐意给这本书写新版。

内容变动

在各处收集了很多读者反馈和建议,综合我自己的规划,第二版主要有这些大的变动:

  • 不再兼容 Python 2.7,基于 Python 3.x、Flask 2.x。
  • 把大部分知识点集中到第一部分,让第二部分的示例程序内容更精简。
  • 第 1 章使用 pip+venv(+pip-tools) 或 PDM 替代 Pipenv,增加更多对 PyCharm 的介绍。
  • 第 6 章使用 Flask-Mailman 替代 Flask-Mail(同时引入介绍一些新的扩展,比如 Flask-Admin)。
  • 删除第 9、10、11 章(删除后 3 个实战项目),仅保留 Sayhello 和 Bluelog,删减项目的部分功能会合并到这两个项目。
  • 第 10 章删除实战项目,只保留 Web API 开发部分,会新引入 marshmallow 和 APIFlask。
  • 第 12 章添加异步任务相关内容。
  • 第 14 章添加 Docker 相关内容。
  • 删除第 15 章(Flask 扩展开发)。
  • 删掉一些在国内访问有问题的服务介绍,比如 SendGrid(第 6 章)、Heroku(第 14 章)、Twitter 第三方登录(第 11 章)。

大部分内容变动是在做删减,第一版 704 页,第二版内容会删减到 450 页左右。

代码片段

第一部分每一章都有一个示例程序,读者需要切换到每一个子文件夹运行,如果同时自己编写练习代码的话,很容易产生混乱。所以新版的考虑是,第一部分的示例程序不再给出可运行的示例文件(至少不在书里介绍),而是把所有的代码片段放到 HelloFlask 文档,以代码片段的形式组织。读者需要的话可以自己复制代码到本地运行。作为示例,第一章的代码片段可以在这里看到:https://docs.helloflask.com/book/4/snippets/c1/。因为前六章的代码片段变化不大,所以第一版的读者也可以使用这些文档。

P.S. HelloFlask 文档(https://docs.helloflask.com)是我最近刚创建的一个文档集合。所有和书相关的内容都会放到这里,比如勘误、代码片段、衍生文章等。后续也会在这里写一些扩展快速入门介绍、Flask 基础知识等 Flask 相关的东西。

书名

第二版因为有独立的书号,所以可以改书名。责编老师认为原书名就很好,换书名会浪费掉第一本书建立的品牌。我也基本同意这个观点。目前的考虑是主书名不变,出于营销的考虑,副书名或许可以加一个「Python」关键字,比如改成「Python Web 开发入门、进阶与原理解析」。

封面

目前这本书主页上的封面不是正式封面(我用画图软件在初版封面上随手涂了两笔 >_<)。目前的想法是把封面标题颜色换成深红色,然后换一个封面图案,不过暂时还没想好换成什么。

措辞

第一版是用 Word 写的,排版很痛苦,毕竟要在 Word 里面排版代码。第二版也要继续用 Word 写,而且更加痛苦,因为第二版的书稿是编辑老师用工具从 PDF 文件转制成 Word 的,大部分格式都变得很混乱。希望这是我最后一次用 Word 来写技术书。

对于第一版,我交的终稿和最终成书有一些内容的变化,编辑老师会修正错别字和一些病句。但是后面也发现有一些句子修改得并不合理,导致句子原意产生了变化。这次我会在出版前过一遍校正后的书稿,确保不会再出现这样的情况。

另外,编辑老师还会替换一些词汇用法,比如把「我」替换为「笔者」,这个笔者勉强能接受,但是笔者最不能接受的就是要把所有的「点击」改成「单击」。单击一个链接?这太奇怪了。今年和编辑老师再次沟通了这件事,事情终于有了变化,书中的「点击」在这一版不用再死板的按照出版社的规定修改为「单击」。新的读者不会被指导在网页上单击一个链接了。

周边开源项目

我的很多开源项目的开发都是写作驱动的,上一本书驱动我开发了那几个 Flask 示例程序和扩展,这一次同样会有一些开发计划:

  • APIFlask:补齐所有文档,发布 1.0 版本。
  • Bootstrap-Flask:添加 Bootstrap 5 支持。
  • Flask-CKEditor:添加 CKEditor 5 支持,同时集成新版本内置的 Markdown 支持。
  • Flask-WTF:添加多文件上传验证支持。
  • Sayhello:添加分页、多语言、Markdown 支持等功能(总之就是尽可能把删减的示例程序里比较有意思的功能合并到这里)。
  • Bluelog:把 Albumy 的除了图片相关的功能合并过来,会换一个名字(暂定 WeBlog),作为一个社交博客。

如果你对这本书的新版有其他建议和想法,欢迎提出。这两本新书相关的动态会发到 Twitter @helloflask 和我的公众号,欢迎关注。

和中学生们聊聊学习和使用 Python 的趣事

  • 录像:bilibiliYouTube
  • 嘉宾:王俊杰 / 胡博文 / Andy Zhou / Rice Zong
  • 主持人:李辉
  • 详情页:https://cn.pycon.org/2021/talks/young-pythonistas
  • 介绍:高中生和初中生们是怎么学习 Python 的?他们都开发了什么项目?他们未来会计划做程序员吗? 在这场圆桌里,一个高中生和三个初中生会一起聊一聊他们学习和使用 Python 的经历,以及他们的未来畅想。

这是 PyCon China 2021 第一天的一场圆桌讨论(PyHouse)。辛庆老师跟我说的时候,本来没打算参加,后来想到可以邀请我的两个小读者来聊聊,所以在前一天决定来策划和主持这场圆桌。

邀请的两个人一个是 Andy Zhou,他在六年级就开始学习 Python 了,初中时通过读我的书学习了 Flask,然后做了一个班级网站(博客程序 Flog),让同学们可以注册在上面写东西。另一个是陈绍坤(现在才想起来他似乎还在上六年级),知道他是因为他在一个知乎回答里介绍了自己学习编程的经历,而且提到有读我的书(文章?)。不过因为我在大会前一天才发出邮件,陈绍坤没来得及看到,看到邮件时圆桌讨论已经接近尾声……

第二天上午,Andy 的同学 Rice Zong 突然发邮件给我,做了一番相当正式的自我介绍,然后说想和我做朋友,原话是「我正式向你发出交朋友的邀请。希望你能接受这个朋友。」。哈哈,感觉好可爱(希望这样说他不会觉得冒犯)。他告诉我 Andy 跟他嘚瑟我发出去的圆桌邀请邮件,所以问我「作为初中生,如何才能参加 PyCon China 2022?就像 Andy 一样。」。看到最后这个问题,还没看清他说的是 2022,我就赶紧补了一封邀请邮件给他,这样我们就有了一位新嘉宾。再加上辛老师邀请的两位在上海现场的王俊杰和胡博文同学,四人成行。

我一开始准备了很多问题,但是几个人三下五除二就回答完了,而这时圆桌还剩一个小时。后面我已经不知道要说什么了,好在 Andy 和 Rice 实在是能聊,他们完全撑起了整场圆桌。再后来他们久仰大名的 Frost Ming 神秘现身;shell 叔给我们科普了很多法律相关的事情;现场的其他老师也加入进来聊起了网络安全……最后我实在坐不住了,只好把已经跑到月球的话题拉回来,给这场圆桌收了尾。这些跑题内容在最终视频里都剪掉了,要不然视频足足有两小时那么长。

也许是我笑点太低,不过和他们聊天真的非常开心,你很难不被他们的单纯和热情感染。希望明年可以看到他们几个来 PyCon China 演讲,期待明年的 PyCon China。

P.S. 这个视频是基于直播录屏的私人剪辑版本。视频中使用了冒险岛(MapleStory)的背景音乐 101 Building,我知道这样使用属于侵权,但我还是用了……版权属于 Nexon 公司。希望这是我最后一次未经授权使用版权音乐。

2021 年 Flask 用户调查报告

七月份创建了一个 Flask 用户调查问卷,到目前为止一共收集到 104 份提交,感谢各位的参与!这篇文章会对问卷收集结果做一个总结,作为一份(非官方的)2021 年(104 位中国)Flask 用户调查报告。

如果你想自己看原始数据,下面是剔除了隐私信息的问卷提交数据文件(CSV 格式):

https://helloflask.com/downloads/flask_report_2021.csv

如果你现在还想参与的话,完全来得及,下面是问卷的地址:

https://jinshuju.net/f/UY6Rd8

你在工作中使用 Flask 吗?

在提交的 104 份问卷里,接近一半的人在工作中使用 Flask,详情如下:

你在工作中使用 Flask 吗?

算上我,还没找到工作的人竟然只有 5 个(《震惊!某份民间调查显示 Flask 用户无业率仅有 4.8 %!》:P)。

Flask 中文文档标语翻译

前段时间发起了 Flask 中文文档翻译,目前正在龟速进行中,欢迎英语比较好的同学参与(翻译或审核已经提交的 PR)。文档首页的 logo 图片里有一句标语——「web development, one drop at a time」,我临时翻译为「Web 开发,一次一滴」。在 GitHub 仓库的讨论里,大家给出了更多更好的备选项。根据这次问卷投票的结果,「Web 开发,起于点滴」排在第一位(由 HelloFlask QQ 群里一位翻译专业的同学翻译),详情如下:

Flask 标语翻译

更保险的选择是「不作翻译」,在投票中排在第二位,最终我会考虑从这两者之间选择。另外,如果翻译的话,如果翻译的话,「Web」会考虑按照英文近年的惯用趋势而使用全小写(你觉得呢?​)。

P.S. 在 GitHub 上的投票结果里,@abersheeran 翻译的「Web 开发,起自微毫」和「任取随用的 Web 框架」票数最高

对 Flask 的新功能请求 & 改进建议

剩下几个问题不方便详细统计和展示,我粗略看了一遍,整理出了关注度最高的几件事。我一一在下面评注了我的想法以及相应的动态。

添加更多 Web API 开发支持

开发接口时自动生成swagger

对 Web API 很友好的支持

加入pydantic用于类型检查

Flask 作为一个通用型框架,不会添加太多关于 Web API 这种细分方向的支持。除了手动集成这些功能/工具,你可以考虑选择基于 Flask 而且集成了这些功能的二次框架,比如 APIFlaskflask-smorestSpectree 等。

添加基于 ASGI 的异步实现

加强对异步的支持

异步支持的不是很成熟

可能是速度吧,据说现在很多新框架都比Flask快,要淘汰Flask云云

Flask 目前的异步支持已经能够满足大部分使用场景。据我所知,基于 ASGI 的异步支持短期内不会实现。

而对于鼓吹性能的文章可以不用太在意:异步和高性能不直接划等号。技术选型不会只考量性能。benchmark 未必客观和能够反映真实情况。如果对异步有强需求,可以考虑用和 Flask 相同 API 的 Quart(一个 Flask 的 ASGI 异步实现),或是换用其他异步框架。

官方维护的扩展列表

一个针对新手的 flask extension 的指南

由Pallets Projects开发团队的成员或者flask官方统一所有第三方插件的开发与发布,目前太乱了,有些都快停更十年了,还占用名字。

各种插件扩展选择太多,但是又质量层次不齐,此外很多个人维护的扩展可能由于开发者精力有限在后期会出现项目生命周期的问题,如:flask_restful,需要官方对常用扩展维护一套比较简洁的扩展。当然这样的话可能又走上了Django的老路,可能作者正在计划的“Flask扩展收容计划”是一个不错的选择,起码可以管理起来。但是脱离原作者维护的项目后期发展如何又成为另一个话题。比如需要对插件进行遴选,另外需要对该计划由官方进行宣传等

这件事我之前有计划在做,前段时间注册了 flaskextensions.org,想用来做一个这样的扩展推荐列表。在 Pallets 会议上沟通之后,决定交给 Pallets 官方来做,所以把域名转给了 Pallets 团队,后续会做一个这样的列表出来:列出来推荐的 Flask 扩展,并给出失去维护扩展的替代品。

另外 Pallets 在 GitHub 创建了一个 pallets-eco 组织,用来收纳失去维护(维护者不想再继续维护)的流行 Flask 扩展,让它们保持健康的维护状态(由社区维护)。目前有两个扩展进入这个组织,分别是 Flask-CachingFlask-OpenID

脚手架工具和大型项目组织

统一并明确开发的工程化流程,包括项目应该新建那些文件夹等。

缺少小白懒人快速一键生成集成框架(webadmin, mysql)

引入蓝图想搞玩大型项目,可大型项目又需要自己组织好项目结构,需要一个对flask很熟悉的人做这事 还不如用django。官方推一个脚手架生成项目模板吧像vue-cli一样 包括是否使用orm等插件,不然还不如django搭建起来快。。

作为一个微框架,Flask 的初衷是不限定项目文件组织形式,所以官方不太可能会出一个脚手架工具或是项目组织指南。在工厂函数和蓝本这两个概念的基础上,你可以自由组织你的项目。另一方面,市面上已经有很多项目模板或是脚手架工具(比如 cookiecutter-flask),也有很多介绍 Flask 项目组织的文章,你可以根据这些工具和文章制定自己的规则,然后为你的组织或团队确定一个项目组织规范或是写一个脚手架工具,或许这才是 Flask 的「正确食用方式」。如果你更需要一个固定的项目组织模式,那么 Django 或许是更好的选择。

我也有计划写一个更简单的脚手架工具(对我来说市面上那些都太复杂,引入了太多扩展和工具),同时给出一份 Flask 项目组织建议,这件事也许会在写《Flask Web 开发实战》第二版的过程里完成。

Bootstrap-Flask 的 Bootstrap 5 支持

Bootstrap-Flask 希望可以早点同步 Bootstrap5 的相关内容。因为Bootswatch的最新版本有我想要使用的组件,但是Bootstrap-Flask只支持到Bootstrap4,所以有些宏无法正常显示,比如render_form等。还有希望Bootstrap-Flask的wiki能够更加的完善和细节,我会持续关注,大佬辛苦

提交的问卷里有一条说到这件事,此前在 TwitterGitHub 上也有人提到,所以也放到一起说一下。我已经在做这件事了,基本的 Bootstrap 5 支持已在 #161 完成,完整的实现会在下个月完成(详见 #162),届时会随 2.0 版本发布。

除了这些,提交的问卷里还有很多其他的建议和问题,我没法一一回应。有时间我会看看问题里提出的问题有哪些我可以试着解决和改善。同时也欢迎你一起尝试解决这些提出的问题,感兴趣的话,去翻翻上面给出的问卷数据吧。

总结

总体来看,Flask 社区在朝着好的方向发展,希望明年这里提到的大多数计划都会有着落。一起加油!

P.S. FlaskCon 2021 会在今年 12 月举办,如果你有 Flask 相关的内容想分享,欢迎报名(英文,预录制演讲)。


相关链接:

欢迎填写 Flask 用户调查问卷

以参加捕蛇者说播客录制 Ep 30 为契机,创建了一个 Flask 用户调查问卷,用来收集 Flask 用户的反馈和建议。尽管这个问卷是由我个人而不是 Flask 官方(Pallets)发起(不过 Flask 官方也在计划着做一个调查问卷),但是我会把收集到的信息反馈到 Flask 开发团队进行讨论。因此,你的建议的确有可能让 Flask 变得更好。

不管你对于 Flask 是有明确的变动建议,还是只是对某个功能不甚满意,都可以把你的想法写下来。谢谢!

问卷地址:https://jinshuju.net/f/UY6Rd8


同时借这个问卷送出四本《Flask Web 开发实战》,填写问卷即可参与。每份问卷会有一个提交序号,中奖序号为 27、36、41、58。预计抽奖会在 7 月 22 号完成,届时抽奖结果会更新到这篇文章。

7 月 21 日更新,抽奖结果已产生:

blank

和捕蛇者说聊聊 Flask 2.0

六月初,应 laike9m 邀请做客捕蛇者说录了一期播客。今天中午发布了上半部分:Ep 30. 和李辉聊聊 Flask 2.0,关于自由职业的下半部分正在剪辑中,预计八月初发布。

这是我第一次录播客。录制那一天,房间里还没有装空调,而开风扇会导致风声录进去,我只能用静静降温法;为了防止录进邻居打骂孩子的声音,我又把门窗闭紧,所以录到临近中午,感觉越来越热。一开始还有点紧张和兴奋,但是我从来没有和别人聊过两小时那么久,录到后面,我就像漏气的气球一样,慢慢瘪了下去。

七月初拿到剪好的第一期,觉得可以再优化一下,所以就自告奋勇再剪一遍。花了接近十个小时,剪掉了很多「呃」、「嗯」、「对」、「然后」、「所以」……在这个过程里也发现不少自己说话上的毛病。

这一期上半部分聊了我从学习 Flask 到成为维护者的经历,这些在 19 年的 COSCUP 演讲简单介绍过。下半部分关于自由职业本来有不少内容想聊,但是因为说太多话有点累,所以有一些漏掉的话题就没有再提出来。录完的这些天有时会突然想到一些很有趣但忘记说的事情,偶尔还会想要不要再约着录一个补丁,也计划着写文章作补充。另外这一期虽然聊到了 FastAPI,但我对它了解并不多,如果有机会的话,以后还想再录一期详细聊聊 FastAPI 和 APIFlask,或是再聊一聊技术写作、翻译和演讲。

最后,如果你用过 Flask,欢迎来填写这个 Flask 用户调查问卷。关于捕蛇者说播客的更多信息,可以访问它的主页Twitter 了解。

下期:和捕蛇者说聊聊自由职业

开始翻译 Flask 文档

我在上周开始策划 Flask 文档翻译的事,制定了大致的翻译流程,创建了翻译协调仓库翻译模板仓库。中文翻译作为这个流程的试水项目,在上周末正式(静悄悄)启动。

blank

启动中文翻译项目的第一步是把 logo 里的标语「web development, one drop at a time」翻译成中文,这让我想起来一个也许冷也许不冷的冷知识。很多人知道 Flask 的名字是对 Bottle 的双关语——另一个容器/另一个 Python Web 框架。那为什么 Python Web 框架爱用容器做名字?其实是因为这些 Python Web 框架都是基于 WSGI 规范实现的,而 WSGI 不知从哪天起流行被读作 Whiskey(威士忌),所以这两个 Python Web 框架才叫做 Bottle 和 Flask,所以才有「Web 开发,一次一滴」。

欢迎在这些 discussions 中分别投票选出最佳翻译词汇、中文 logo 字体和中文标语。

回到正题。如果你对 Flask 足够熟悉,并且擅长翻译,欢迎参与进来!翻译通过 GitHub 进行协作(greyli/flask-docs-zh)。除了翻译文档,我们也需要有丰富翻译经验的朋友来审阅提交的翻译,欢迎在 GitHub 上关注(点 Watch 按钮)这个项目以接收新增翻译提醒。

下面是参与翻译之前要了解的三件事:

  1. 请务必完整阅读 Contributing Guide 了解翻译流程,然后阅读这个 issue 了解翻译要求。
  2. 需要强调的是,完成一章的翻译要提交两个 PR:第一个 PR 在 README 中对应的章节条目后添加自己的用户名以认领章节;第二个 PR 翻译对应的 .po 文件并勾选完成的章节条目。先认领,再翻译,一次只认领一章。
  3. 不要改动任何 ReStructuredText 标记、变量/类/函数/方法名称、URL 等。

如果有其他问题和相关想法,请创建 discussion 发起讨论;如果对翻译流程和项目设置有改进建议,或是发现了翻译错误和笔误,请创建 issue 反馈。

这个项目在后期会转移到 FlaskCWG 组织,并在翻译完成后链接到 Flask 官方文档。

Flask 2.0 版本发布

Flask 以及 Flask 依赖的 5 个 Pallets 项目都在今天发布了新的主版本(下面的链接指向各个项目的主版本变动日志):

​你可以使用下面的命令更新 Flask:

 pip install -U flask

如果你使用的国内 PyPI 镜像还没有同步最新版本,可以通过下面的命令临时切换到官方 PyPI 源:

 pip install -U flask -i https://pypi.org/simple/

这篇文章会介绍一些 Flask 新增的特性,完整的变动可以参考上面各个项目的变动日志。

三个核心特性

嵌套蓝本(#3923

对于一个比较大的项目,一般会使用蓝本来组织不同的模块。而如果你的项目非常大,那么嵌套蓝本就可以派上用场了。借助嵌套蓝本支持,你可以在某个蓝本之上再创建多个子蓝本,对项目进行多层模块化组织(而且支持无限嵌套,你可以嵌套很多层):

 parent = Blueprint("parent", __name__)  # 创建父蓝本
 child = Blueprint("child", __name__)  # 创建子蓝本
 parent.register_blueprint(child, url_prefix="/child")  # 把子蓝本注册到父蓝本上
 app.register_blueprint(parent, url_prefix="/parent")  # 把父蓝本注册到程序实例上

这样在生成子蓝本的 URL 时需要传入完整的端点链:

 url_for('parent.child.create')
 /parent/child/create

这个特性来源于一个 2012 年创建的 feature request issue

基本的 async/await 支持(#3412

Flask 2.0 带来了基本的异步支持,现在你可以定义异步视图(以及异步错误处理函数、异步请求钩子函数):

 import asyncio
 from flask import Flask
 ​
 app = Flask(__name__)
 ​
 @app.route('/')
 async def say_hello():
     await asyncio.sleep(1)
     return {'message': 'Hello!'}

注意要先安装额外依赖:

 pip install -U flask[async]

顺便说一句,如果你在 Windows 上使用 Python 3.8,那么会有一个来自 Python 或 asgiref 的 bug 导致出错:ValueError: set_wakeup_fd only works in main thread。可以通过下面两种方式(任选一种)处理(具体参考这个 SO 回答):

  • 升级到 Python 3.9
  • 在你的入口脚本顶部添加临时修复代码:
 # top of the file
 import sys, asyncio
 ​
 if sys.platform == "win32" and (3, 8, 0) <= sys.version_info < (3, 9, 0)::
     asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

不过目前只是一个基于 asgiref 的异步实现,作为异步支持的第一步,后续还会进行更多的优化和改进,更多相关信息可以参考文档

快捷路由装饰器(#3907

新增了下面的快捷路由装饰器:

  • app.get()
  • app.post()
  • app.delete()
  • app.put()
  • app.patch()

举例来说,使用 app.post() 等同于 app.route(methods=['POST'])

 from flask import Flask
 ​
 app = Flask(__name__)
 ​
 @app.post('/')
 def index():
     return {'message': 'Hello!'}

注意不要在这些快捷装饰器里传入 methods 参数。如果需要在单个视图处理多个方法的请求,使用 app.route()

我在某次 pallets 会议上提议添加这些装饰器时一开始是被拒绝的,后来 Phil Jones 创建了 #3907 经过二次讨论后才最终合并(被拒绝后我就把当时正在开发的 APIFlask 从扩展改成了继承 Flask 基类的框架,然后加了这些装饰器)。

我添加的三个特性

修复执行 flask run 找不到程序的奇怪设定(#3560

这个像 bug 又像是 feature 的设定我在《一个困扰我两年的 Flask「Bug」》里详细说过,最终终于在两年后修复了。

支持在 .flaskenv 和 .env 文件里写中文(#3932

我在《Flask Web 开发实战》第一章介绍 .flaskenv 文件时给了一个示例,演示如何添加注释:

SOME_VAR=1
# 这是注释

但这个示例没有实际测试……加了中文其实会报错,后来有两次收到读者反馈,最终终于在三年后修复了。

为文档添加命令切换面板(#3714

这个或许算不上特性,不过在我看来是对用户非常友好的变动。除了个别不需要区分操作系统和命令行程序的命令外,我给文档里所有的命令添加了支持切换 Bash/CMD/Powershell 以及 macOS/Linux/Windows 的切换面板(面板的样式后续会有一些优化):

blank

其他的有用特性

  • 优化了浏览器缓存控制,对 CSS、图片等静态文件做出的变动会在程序重载后立刻更新,不再需要手动清除页面缓存。
  • Werkzeug 的 multipart 解析(尤其是大文件上传处理)性能提高了 15 倍。
  • 配置对象增加 Config.from_file() 方法支持从任意文件加载器导入配置(比如 toml.loadjson.load),未来会取代 Config.from_json() 方法。
  • 在使用环境变量 FLASK_APP 指定工厂函数时支持传入关键字参数。
  • flask shell 支持 tab 和历史补全(需要安装 readline)。
  • CLI 系统优化了找不到程序时的错误处理和错误输出显示,同时修正了 Windows 上的命令行颜色输出。

破坏性变动

主要的破坏性变动(breaking change)是意外的把 send_from_directory() 函数的第二个参数名称直接由 filename 重命名为 path,将会在 2.0.1 加回来(#4019)。

另外 send_file() 函数的三个参数也进行了重命名(旧名称将在 2.1.0 移除):

  • attachment_filename -> download_name
  • cache_timeout -> max_age
  • add_etags -> etag

其他重要变化

  • 不再支持 Python 2 和 Python 3.5。
  • 所有 Pallets 项目都添加了 type hinting,这意味着更好的 IDE 自动补全体验。
  • 所有仓库的主分支由 master 改为 main。如果你在本地克隆了 Flask 等仓库,可以使用下面的命令来更新:
 git branch -m master main
 git fetch origin
 git branch -u origin/main main
 git remote set-head origin -a

感谢支持

这次更新对于整个 Pallets 项目来说是一个新的里程碑。接下来还有许多事情要做:FlaskCon 2021 正在准备中,新建立的 Flask 社区工作小组(Flask Community Work Group)正在进行 Flask 文档翻译(如果你对中文翻译感兴趣,可以订阅这个讨论)、被遗弃扩展收容计划(这是我一直想做的事情)等等。感谢支持,敬请期待!

欢迎通过下列途径关注 Pallets 项目:

相关文章:

请不要把 Flask 和 FastAPI 放到一起比较

The war has begun. Let’s end the war between Flask and FastAPI.

去年在知乎上看到一篇文章,后来想找却找不到了,最后通过关键词「5 分钟」和「四年」在掘金上找到了原文——《用它5分钟以后,我放弃用了四年的 Flask》。作者先是给出了一个手动用 if 验证请求数据的 Flask 视图函数:

@app.route('/insert', methods=['POST'])
def insert():
    info = request.json
    name = info.get('name', '')
    if not name:
        return {'success': False, 'msg': 'name 参数不可省略,不可为空!'}
    age = info.get('age', 0)
    if not isinstance(age, int):
        return {'success': False, 'msg': 'age参数不是数字!'}
    age_after_10_years = age + 10
    msg = f'此人名叫:{name},10年后,此人年龄:{age_after_10_years}'
    return {'success': True, 'msg': msg}

然后给出了一个使用 Pydantic 编写数据模型类 + FastAPI 路径操作函数的代码:

class People(BaseModel):
    name: str
    age: int
    address: str
    salary: float
    
@app.post('/insert')
def insert(people: People):
    age_after_10_years = people.age + 10
    msg = f'此人名字叫做:{people.name},十年后此人年龄:{age_after_10_years}'
    return {'success': True, 'msg': msg}

通过这两段代码进行对比,文章作者得出结论「我用了 Flask 四年,但在使用了5分钟 FastApi 以后,我决定以后不再使用 Flask 了」。你确定你用了四年的是 Flask 而不是 Flash?在此后的很长一段时间里,经常看到类似的对比和结论,所以我打算用这篇文章来澄清这个非常普遍的误解。如果看到有人把 FlaskFastAPI 放到一起比较,请把这篇文章的链接丢过去。

利益相关:笔者系 Flask 维护者和 APIFlask 作者。本文观点仅代表个人看法,与 Pallets 团队无关。

为什么 Flask 和 FastAPI 不能放到一起比较?

我在此前介绍 APIFlask 的文章里一开始就提到过这件事情:

经常看到有人把 FastAPI 和 Flask 放到一起比较,但是却没有意识到这完全是两种东西——前者是基于 Web 框架 Starlette 添加了 Web API 功能支持的(框架之上的)框架,而后者是和 Starlette 同类的通用 Web 框架。你怎么能让小明和骑电动车的小军赛跑然后还夸小军好快好强?

前两天看了 Reddit 上 Flask 2.0rc 帖子下的很多回复,预感到在 Flask 2.0 发布后在中文网络上也许会出现许多类似的问题和讨论,所以想着还是值得单独写一篇文章来谈这件事。

受那个 Reddit 主题里某个评论启发,想到一个更好的比喻: Flask 和 FastAPI 就像是苹果和橙汁。你不能把苹果和添加了更多蔗糖和添加剂的橙汁放在一起比较哪一个更甜。苹果(Flask)应该和橙子(Starlette)比较,橙汁(FastAPI)当然也应该和苹果汁(基于 Flask 的 Web API 框架)进行比较。也就是说,Flask 是一个通用型框架,和 FastAPI 依赖的 Starlette 一样,而 FastAPI 是添加 Web API 支持的二次框架。因此,Flask 应该和 FastAPI 所依赖的 Starlette 进行比较,而 FastAPI 应该和基于 Flask 的 Web API 框架进行比较。

同理,FastAPI 也不能和 Django、Tornado、web2py、Bottle、CherryPy、Pyramid、Sanic 等通用型 Web 框架比较。

更合理的比较对象是什么?

既然「FastAPI 应该和基于 Flask 的 Web API 框架比较」,那么合适的比较对象有哪些?Flask-RESTXFlask-Rebarflask-apispecflask-smorestFlask-RESTfulAPIFairy 这些虽然试图做成框架,但在具体实现上仍然是 Flask 扩展,所以真正公平合理的比较对象是:

这三个框架都是基于 Flask 实现的 Web API 框架。不过 Eve 没有内置 OpenAPI 支持,而 Connexion 是一个 Spec-First 框架,也就是 OpenAPI spec 优先(先编写 spec,然后生成项目基础代码进行开发)的框架。因此,尽管 APIFlask 只是一个刚发布没多久的实验项目,但它是最适合和 FastAPI 进行比较的项目。

FastAPI 和 APIFlask 有着同样的组成结构,都是作为一层胶水粘起来一个 Web 框架和一个数据序列化/验证库,然后加一点 Web API 功能支持:

在主要功能上也有很多交集:

  • 自动反序列化和验证请求数据,自动生成错误响应
  • 自动格式化和序列化响应数据
  • 自动生成 OpenAPI specification 文件
  • 自动生成交互式 API 文档

当然,相对于发布已经三年的 FastAPI,APIFlask 还只是一个三个月大的新项目,还有很多地方需要改进和完善。如果你对 APIFlask 感兴趣的话,可以在这篇文章了解详细介绍或是在它的 GitHub 仓库查看源码。

为什么会有这样的误解?

为什么会有那么多人把 FastAPI 和 Flask 放到一起比较?在我看来有三个原因:

首先是 FastAPI 采用了和 Flask 类似的装饰器路由,很容易让人联想到 Flask。

二是 FastAPI 没有给它的项目构成做足够的说明。如果 FastAPI 在其介绍的第一句就加上「based on Starlette and Pydantic」而不是放到 Requirements 部分才提及,这样也许会让 StarlettePydantic 获得更多应有的关注,也就不会有这么多人拿 Flask 和它比较。

再就是 FastAPI 的推介者对 FastAPI 了解的不够多。不清楚它的项目结构,自然就不了解它和其他 Web 框架的区别。而介绍时为了带来足够的吸引力,推介者常常选择拉上和用户量足够大的「竞品」进行对比这种方式,所以才会有大量类似本文开头提到的那种文章。这些文章甚至完全没有提及 Starlette 和 Pydantic,只是一味强调「这是一个超高性能的、碾压 Flask 的框架」。

FastAPI 不是银弹

FastAPI 只是说它能把「功能开发速度提升约 200% 至 300%」,距离十倍还差了一些,自然不能算是银弹。但是这两年看到了很多对 FastAPI 的盲目吹捧,仿佛 FastAPI 就是完美的解决方案。这也许都要归功于 FastAPI 在其 README 和文档里「大胆」的措辞和承诺以及不厌其烦的特性介绍。举例来说,如果它只是说「Very high performance, it is comparable to some frameworks in Go and NodeJS」,那么用户也许就会认为「和 Go/NodeJS 有一拼」,但是说成「Very high performance, on par with NodeJS and Go」,那么用户就会想「和 Go/NodeJS 不相上下」(以至于中文翻译是「可与 NodeJS 和 Go 比肩的极高性能」),这当然会带来一些争议。再比如,如果只是说「double or triple the development speed」,那么用户大概会想「效率还不错」,但是它用了「Increase the speed to develop features by about 200% to 300%」,数学不好的用户就会惊呼「哇,提高 30 倍开发效率」。同时这种营销带来的狂热用户,也很容易被煽动去给 Python Steering Council 施加压力——在 Python 3.10 快要发布的时候突然跳出来要求撤销一个 PEP。

这种营销至上的项目运营方式带来的直接后果是糟糕的维护状态。FastAPI 似乎在慢慢变成一个翻译项目,在代码里的文档字符串都还没写的情况下,其用户文档就已经开始扩展到十几种语言。而这十几种语言的翻译和项目源码都放在同一个仓库里,这导致所有开启的近 300 个 PR 里有过半是翻译。issue tracker 和 discussion 不做区分的使用,这导致所有开启的近 600 个 issue 里有九成是提问)。我在此前创建过一个 issue 来反馈这个问题,但并没有得到回应。另外凭个人喜好来说,在每个 commit 信息里都加上 emoji 并不可爱。而每一个 commit 都要触发 bot 更新 changelog,带来的是一份丑陋的 commit 历史(所有 commit 里有三分之一是在更新 changelog)。这些也许是发展社区或是让项目看起来很活跃的有效方法,但很显然这带来的是建立在混乱之上的虚假繁荣。

另外,许多推介文章都会在最后贴出来一张 benchmark 截图来证明 FastAPI 有多么 fast,但是完全不会提及的是:贴出来的 benchmark 对于开发生产应用来说有多大的意义?这里的 benchmark 背后有没有任何的 hacky?异步是否等同于高性能还是要看情况?框架本身的性能在一个请求处理流程中占多大的影响?asyncio 的生态怎么样?

从长远看这些大都是一些临时问题,而且 FastAPI 作者已经开始全职开发开源项目,这些问题在未来应该都会慢慢得到改善。指出这些是希望更多的人可以客观看待 FastAPI,吹捧并不能让一个东西变得更好,参与开发、介绍用法和回答社区提问是比盲目吹捧更有意义的事情。我当然期待 FastAPI 能够越来越好,也期待看到有更多优秀的 Python 框架出现,但我不喜欢过度炒作、盲目的吹捧和错误的对比。

P.S. Flask 2.0 版本即将发布

顺便说一句,Flask、Werkzeug、Jinja 等 Pallets 项目都将在 5 月 11 日(PyCon 开始)之前发布下一个主版本,主要包含下面这些新特性(完整变动见 changelog):

  • Flask、Werkzeug 和 Click 添加了 type hints
  • Flask 实现了有限的 async/await 支持
  • Flask 支持嵌套蓝本
  • Flask 添加了快捷版本的 route 装饰器(即 @app.get@app.post 等)
  • Flask 支持在视图类上使用 route 装饰器(我前两天刚提交了一个 PR 来实现这个功能,暂时不确定是否会被合并,在这先提前说一下 :p)
  • Werkzeug 的 multipart 解析性能大幅提升

你可以使用下面的命令安装 Flask 2.0.0rc 版本进行测试:

 $ pip install --pre flask

欢迎反馈 bug 和提改进建议。关于 2.0 版本的详细介绍将会在发布后另外撰文说明。

延伸阅读

APIFlask:一个基于 Flask 的 Web API 框架

经常看到有人把 FastAPI 和 Flask 放到一起比较,但是却没有意识到这完全是两种东西——前者是基于 Web 框架 Starlette 添加了 Web API 功能支持的(框架之上的)框架,而后者是和 Starlette 同类的通用 Web 框架。你怎么能让小明和骑电动车的小军赛跑然后还夸小军好快好强?为了让框架 PK 爱好者们有一个更公平的比较对象,从一份 APIFairy 0.6.2 版本的 fork 开始,我实现了一个基于 Flask 的 Web API 框架——APIFlask

blank

APIFlask 在 Flask 的基础上添加了更多 Web API 相关的功能支持,核心特性包括:

  • 更多方便的装饰器,比如 @app.input()@app.output()@app.get()@app.post() 等等
  • 自动反序列化和验证请求格式,当请求数据不符合模式类要求时,会自动生成包含错误详细信息的错误响应(基于 Webargs
  • 自动格式化和序列化响应数据,在定义好响应模式后,你可以直接在视图函数返回一个模型类对象,或是返回字典(基于 Marshmallow
  • 自动生成 OpenAPI Specification 文件,你可以把这个文件导入到 API 调试工具或是用来生成客户端代码(基于 APISpec
  • 自动生成交互式 API 文档,并自动为蓝本和视图设置对应的标签分类(基于 Swagger UI and Redoc
  • 自动为 HTTP 错误生成 JSON 格式的错误响应

下面是一个最基础的示例程序:

 from apiflask import APIFlask, Schema, abort
 from apiflask.fields import Integer, String
 from apiflask.validators import Length, OneOf
 ​
 app = APIFlask(__name__)  # 可以使用 title 和 version 参数来自定义 API 的名称和版本
 ​
 pets = [
     {
         'id': 0,
         'name': 'Kitty',
         'category': 'cat'
     },
     {
         'id': 1,
         'name': 'Coco',
         'category': 'dog'
     }
 ]
 ​
 # 定义一个请求数据模式类
 class PetInSchema(Schema):
     name = String(required=True, validate=Length(0, 10))  # 可以使用 description 参数添加字段描述
     category = String(required=True, validate=OneOf(['dog', 'cat']))
 ​
 # 定义一个响应数据模式类
 class PetOutSchema(Schema):
     id = Integer()
     name = String()
     category = String()
 ​
 ​
 @app.get('/pets/<int:pet_id>')
 @app.output(PetOutSchema)  # 使用 @output 装饰器标记响应数据模式
 def get_pet(pet_id):
     if pet_id > len(pets) - 1:
         abort(404)
     # 在真实程序里,你可以直接返回 ORM 模型类的实例,比如
     # return Pet.query.get(1)
     return pets[pet_id]
 ​
 ​
@app.patch('/pets/<int:pet_id>')
@app.input(PetInSchema(partial=True))  # 使用 @input 装饰器标记请求数据模式
@app.output(PetOutSchema)
def update_pet(pet_id, json_data):  # 通过验证后的请求数据字典会注入到视图函数,默认参数名为 json_data
    if pet_id > len(pets) - 1:
        abort(404)
    for attr, value in json_data.items():
        pets[pet_id][attr] = value
    return pets[pet_id]

P.S. 你也可以使用类视图(class-based views),具体示例见这里

如果你想在你的电脑上运行这个示例,可以先用下面的命令安装 APIFlask(需要 Python 3.7 及以上版本,Flask 1.1 及以上版本):

Linux 和 macOS:

 $ pip3 install apiflask

Windows:

 > pip install apiflask

安装完成后把上面的代码保存到文件 app.py,然后执行下面的命令运行程序:

 $ flask run

现在你可以在浏览器访问 http://localhost:5000/docs 查看基于 Swagger UI 自动生成的交互式 API 文档:

Swagger UI

或者访问 http://localhost:5000/redoc 查看基于 Redoc 生成的 API 文档:

Redoc

访问 http://localhost:5000/openapi.json 可以获取自动生成的 OpenAPI spec 文件。

这个示例程序的完整版本可以在 https://github.com/greyli/apiflask/tree/master/examples 看到。如果你想了解更多用法,可以阅读文档中的基本用法一章(目前只有英文)。

APIFlask 只在 Flask 之上做了轻量级包装,所以你实际上仍然是在写一个 Flask 程序,所有 Flask 的特性都完全兼容。你只需要记住下面两处不同点:

  • 创建程序实例的时候使用 APIFlask 类(from apiflask import APIFlask)。
  • 创建蓝本实例的时候使用 APIBlueprint 类(from apiflask import APIBlueprint)。
  • 使用 apiflask.abort() 函数返回 JSON 格式的错误响应。

以下面的 Flask 程序为例:

 from flask import Flask, request, escape
 ​
 app = Flask(__name__)
 ​
 @app.route('/')
 def hello():
     name = request.args.get('name', 'Human')
     return f'Hello, {escape(name)}'

迁移到 APIFlask 只需要改动两行代码:

 from apiflask import APIFlask  # 第一行
 from flask import request, escape
 ​
 app = APIFlask(__name__)  # 第二行
 ​
 @app.route('/')
 def hello():
     name = request.args.get('name', 'Human')
     return f'Hello, {escape(name)}'

欢迎提出改进建议,报告 bug 或是分享其他任何相关的想法。你也可以在 GitHub 上创建 Issue,或是提交 PR 来改进它。谢谢!

国内技术书盗版现状

我在 18 年出版了一本技术书,快两年过去了,和盗版打了很多交道。这篇文章从一个技术书作者的视角以 2018~2020 作为切片来记录一下国内技术书的盗版现状。

盗版的起源

我的书第一次上架到电子书平台是在 18 年 8 月 24 号,纸质书正式开始发售是在同年 9 月 13 号。仅仅不到一个月的时间,在 10 月初,淘宝上就开始出现了大量盗版影印书(大约有十几个,价格多在 ¥30 左右)。紧接着,在 11 月初,CSDN 上出现很多盗版电子书。同一时间,多个盗版电子书网站开始有了这本书,基本占领了百度和 Google 的前几页搜索结果。

SEO 赢家

从纸书发售的两个月后开始,一直到现在,不管在哪个搜索引擎上,盗版文件的链接都是 SEO 赢家。以 Google 为例,直接搜索书名「Flask Web开发实战:入门、进阶与原理解析」,前几页的盗版链接比例如下(四月末数据):

  • 第一页:4/10
  • 第二页:7/10
  • 第三页:5/10

单是直接搜索书名就已经得到这么高的盗版比例,更别说加上 PDF 之类的盗版关联词。

下面是在 Google 上的第一页搜索结果截图:

Google 搜索结果

书刚出版那段时间,盗版泛滥,但我能做的事情并不多。算是作为恶作剧,我在自己的博客上也放了一个盗版电子书下载页面(上面搜索结果里的第五位),复制了某个盗版电子书网站的标题和内容,特意用了中文 URL 和盗版关键词。本来是打算在页面结尾放下载链接的地方讽刺几句,嘲弄一下点进来想下载盗版电子书的笨蛋。但是又担心激怒了某个家伙,然后被到处写差评。而且这样也太没有气度,所以只是把下载链接指向了这本书的主页。

我的博客上的文章点击基本都是来自搜索引擎的自然流量,18 年 12 月 19 日发布的一篇无聊的技术文章到现在有 262 个点击,而第二天发布的这个盗版电子书下载文章有 2673 个点击:

盗版文章点击量

 

淘宝和闲鱼

淘宝是最早出现盗版影印书的地方,闲鱼其次。18 年集中处理了一批淘宝影印书,中间又零散处理一些,最近试着再次用关键词搜索,发现盗版影印书又开始卷土重来了。这是在淘宝使用关键词「Flask Web开发实战:入门、进阶与原理解析」得到的第一页结果(2020/5/23 数据),48 个商品里有 16 个是盗版影印书,刚好占据三分之一:

淘宝搜索结果大多盗版影印书都会打着二手的旗号卖盗版。以第一页里看起来最像是真二手的这家店为例(它甚至在商品介绍图片里清晰的注明「拒绝盗版」、「8 新正版二手」):

淘宝影印书

当你进一步询问,几乎总会得到这样的回答:

盗版影印书聊天对话

淘宝网站和闲鱼上自带的举报功能是没用的,即使商家有极其明显的售假描述,比如「PDF」、「影印」:

盗版商品描述

但是举报之后得到的举报结果总是固定不变的「从信息层面暂未能核实到售假信息」:

淘宝举报处理结果

对于淘宝和闲鱼的盗版书,更有效的方式是在阿里巴巴集团知识版权保护平台(https://ipp.alibabagroup.com/)提交投诉,不过每一件商品的投诉都要经历与客服/商家对话确认、生成阿里旺旺举证号、填写投诉单这样一个漫长的过程。慢慢地,我开始直接在对方承认售假后要求对方下架。大多数商家都是愿意合作的(偶尔有些商家在下架一段时间后会偷偷再次上架,这时再花时间在淘宝 IPP 平台提交投诉),所以那段时间用这种方式处理了一大批盗版影印书:

盗版影印书合影

虽然淘宝上的盗版影印书现在又大批量复活了,但我已经没有新书发售时那样的动力和愤慨心情去处理它们。

百度网盘

大部分独立站点售卖的盗版电子书都是放到百度网盘上的,这些独立站点的站长有些联系不上,有些拒绝删除,所以更直接的处理方式是在百度网盘上举报。

和淘宝类似,无论你提交多少次,百度网盘文件页面的举报功能是没用的。你需要在百度版权中心(http://copyright.baidu.com/)进行投诉,前前后后一共处理了 19 个盗版文件:

百度网盘投诉记录

但是这样做只对那些比较懒的盗版商人有用,因为勤劳的盗版商人总是会不停地更换掉失效的网盘链接,甚至每次都换一个相关性弱一点的名字,试图绕过百度网盘盗版审核的判断标准。我曾经连续四次跟踪举报某篇文章里的盗版网盘链接,对方把网盘的文件名从「《Flask Web开发实战入门进阶与原理解析》PDF+配套源代码+资料+参考」一直缩减到「Python Web 开发」。

这些电子书文件有很多版本,有一些是资源售卖者制作的不完整文件,最后一页会放一个购买方式,比如微信公众号或 QQ 号,还有一些会在最后放一个资源售卖网站的链接,你需要付费获取用来提取文件的密钥。他们还会像模像样的放一个这样的「版权声明」:

盗版电子书的版权声明

还有一些就是热心读者或者说乐于分享的读者自发分享出来的完整电子书文件,其中最完整的是一份 312M 的压缩包文件,其中包含了:

  • 一份从亚马逊 Kindle 客户端破解的 PDF 文件
  • 一份纸质书扫描版的 PDF 文件
  • 从 GitHub 下载的前六章源码
  • 一份包含对方盗版电子书资源网站信息的 TXT 文件

阿里云对象存储服务器

影响最大,最难处理的是阿里云服务器上的一个盗版文件。有人在阿里云的对象存储服务器上放了一个完整的 PDF 文件,而且这个文件在 Google 的搜索结果中排名非常靠前,这也就意味着任何人只要用 Google 搜索关键词,都可以直接点击链接在浏览器打开这个文件并下载。

在我没处理掉这个文件之前,它长期占据搜索引擎结果第一名的位置(使用次要关键词也会排在第三名):

盗版 PDF 的 Google 搜索排名

阿里云对象存储(OSS)是阿里云提供的存储服务,这个 PDF 文件存储在深圳区的阿里云服务器上,域名也是阿里云的域名(oss-cn-shenzhen.aliyuncs.com)。2020 年 3 月 2 号我在阿里云举报中心提交了一个举报,流程很麻烦,除了要提供身份证扫描件和著作权证明外,还要填一个列举各类证据和信息的承诺书,但是处理结果却很简单:

阿里云举报结果

先让我提供版权证明和列举侵权证据,最后却告诉我「除非接到法院判决或行政裁决,阿里云无权对侵权文件进行处理」。更好笑的是,建议我「直接通知该网站经营者或相关域名持有人并与之协商」,而这个盗版文件的「网站经营者和相关域名持有人」正是阿里云本身。

虽然声称「我方无权作出任何处置动作」,但是其 OSS 的服务条款里却清楚的注明了「阿里云有权采取相应的处理措施」:

「3.1.3.5.不利用阿里云提供的资源和服务上传(Upload)、下载(download)、储存、发布如下信息或者内容,不为他人发布该等信息提供任何便利(包括但不限于设置URL、BANNER链接等):

……

3.1.3.5.7.侵害他人合法权益的信息和/或其他有损于社会秩序、社会治安、公共道德的信息或内容;

……

3.1.3.11.如阿里云发现您违反上述条款的约定,有权根据情况采取相应的处理措施,包括但不限于立即终止服务、中止服务或删除相应信息等。如果第三方机构或个人对您提出质疑或投诉,阿里云将通知您,您有责任在规定时间内进行说明并出具证明材料,如您未能提供相反证据或您逾期未能反馈的,阿里云将采取包括但不限于立即终止服务、中止服务或删除相应信息等处理措施。因您未及时更新联系方式或联系方式不正确而致使未能联系到您的,亦视为您逾期未能反馈。」

我没法接受让一个盗版 PDF 文件直接出现在书名搜索结果的第一名。4 月 24 号,我在新闻出版广电总局的盗版举报平台(http://www.sapprft.gov.cn/sapprft/channels/6979.shtml)提交了对阿里云的举报。我想要是这也没用,那就只能起诉了。

结果某天正在写这篇文章的时候(这篇文章一直断断续续写了很久),多番尝试之后,搜索到了这个 OSS 仓库(Bucket)的拥有者放到 GitHub 上的相关代码,翻了下 commit 历史,竟然发现了被意外提交到 Git 仓库的 Access key ID 和密钥(用来登录阿里云 OSS 的认证信息):

GitHub 代码历史

所以,我就自己用这些认证信息登录阿里云 OSS 删掉了对方存储的盗版文件:

删除阿里云 OSS 文件

加上一个网络教程的 MP3 和 HTML 文件,这位同学一共存储了 400 多个盗版文件。我不是狂热的正版卫士,所以没有做出更大的破坏,只是删除了我的书。

也许更合理的方式是发电子邮件(他的 GitHub 资料上放了 Email 地址)请求他删除,但是既然他收到阿里云(如果阿里云的确通知了)的删除请求,但依然无所作为,而且这个文件困扰了我很长时间,自己删掉会让我觉得有报复的平衡……事后我发了邮件跟他说了这件事,结果对方很诚恳的道歉,倒让我因为「自己动手」有点不好意思。

技术博客和电子书网站

CSDN 的下载板块是盗版电子书的聚集地,还好我的书刚开始在 CSDN 传播的时候,出版社和 CSDN 协商删除了大部分文件,所以我不用跟 CSDN 有太多纠缠。

CSDN 下载页面

售卖电子书文件的人大都会在CSDN、博客园和 51CTO 上面发文章分享网盘链接,因为这三个站点的链接在搜索引擎上的排名都很靠前。51 CTO 和博客园上的文章都没有举报按钮,网站底部也没有投诉通道,所以你需要发邮件给网站方请求删除相关文章。

而对于各类专业的电子书资源网站,如果网站上有联系方式的话,那你可以直接联系站长,要求他们删除。如果站长什么也没留,那就没办法了。

有一个盗版网站的站长,我假装买电子书加上他的 QQ 后,他发给我一个包含大量技术书列表的 Excel 文件,意思是任我挑选。我问他你这样随便卖盗版电子书,假如大多数人都来买盗版,那作者拿不到钱不就没有人愿意写书了?

他告诉我说,他其实是从亚马逊买了正版电子书,破解以后便宜卖给别人,其实就相当于大家一起凑钱买了书(众筹?),这样大家都可以看到书。那一刻,我感觉对方心里有火也有光,仿佛面前是一个向无数编程学习者打开技术世界大门的武林前辈,一个带领大家走向共产新世界的先进同志。

多搜索几页,你会发现,这样热心伟大、一心只为人类进步,一本书只卖一块五的人,有很多:

盗版电子书网站

IM 群组和微信公众号

IM 群组尤其是 QQ 群也是盗版电子书的重灾区,技术相关的 QQ 群非常多,几乎每一个群里都有大量盗版电子书文件,有些还按照语言和框架整理的非常整齐:

QQ 群的盗版文件

这里的处理难题是,你没有时间和精力去申请加入每一个群,然后定期挨个检查有没有你的盗版书被上传。

去年年初,我也创建了一个 Flask 技术讨论 QQ 群,有趣的是,竟然会有人在群里分享我的书的电子书文件:

HelloFlask 群聊盗版电子书

微信群因为没有群文件功能,分享出来的盗版书影响相对要小一点。但还有很多技术相关的微信公众号靠分享盗版电子书来引流,每一个这类公众号都有不少关注者。

这种现状还会持续多久?

体面的生活里不应该存在盗版,何况是技术书这种存在大量正版购买渠道、基本没有封禁和删减的东西。但你很难让大多数人在匿名的网络空间里也保持现实中的道德感,而且还有很多人并不认为盗版有什么不对。

盗版读者越多,网络上的盗版资源商、淘宝影印书就会越来越多。搜索盗版关键词的人越多,盗版网站就会越来越多,排名也就越来越靠前。你关注分享盗版电子书的微信公众号,那靠分享盗版电子书吸引关注者的微信公众号就会越来越多。

与此同时,出版社和作者的收入就会越来越少,用心写书和出书的作者和出版社也会越来越少,有能力写出好书的人也就会更加犹豫要不要写书。无论盗版用户怎么辩驳, 盗版都是一件损害创作者和出版方利益的事情,最终也会影响整个技术写作环境和每一个人的阅读体验和选择。

百度知道盗版分享

看着这些礼貌的、充满对知识真诚的渴望和期盼的留言,我在想这种盗版现状还会持续多久?

我想不出答案。

P.S. 没有动物在本文写作过程中受到伤害,但是有 3 本淘宝影印盗版书、5 个百度网盘盗版电子书文件、一份阿里云 OSS 深圳服务器里的盗版文件,一篇博客园分享盗版的文章永远的离开了这个世界。

P.P.S 如果你没法访问 Google,请检查网线有没有正确连接,或打电话询问运营商,确认接入的是互联网而不是光明网。

相关链接:


Update 2020/7/30

QQ 上有一个群成员跟我私聊,说他在网上找到我的 Flask 教程,但是不知道解压密码,想问我密码是多少。我很疑惑,不知道他到底在说什么,所以让他发链接给我看看,结果他发过来一个盗版资源售卖网站的链接,链接解析信息写着:「本资料为最新整理的《Flask Web 开发实战》PDF+源代码,用于学习,内容丰富!」……

Update 2020/8/17

有读者在知乎上发现了别人新分享的盗版 PDF 分享文章,在 QQ 群里提醒我。我还没开始处理,已经有几个群友在文章下发评论要求删除,第二天作者自己删除了文章。

Update 2020/11/5

又在淘宝上发现大概二十多本影印书,价格大都在 25 左右。好奇下载了拼多多,搜索结果里 90% 都是盗版影印书。