作者归档:李辉

知乎专栏降级

2016 年 11 月创建了一个知乎专栏来写 Flask 相关的内容,到现在关注人数慢慢涨到了 10000。

前段时间偶然看了下专栏,发现整个页面有很大的变化,原有的这些东西不见了:

    • 专栏头像
    • 专栏短介绍(被替换为关于页面内的长介绍)
    • 关于页面
    • 置顶文章
    • 关注人数
    • 专栏设置页面
    • 文章条目的作者和发布时间

页面上部去掉了很多信息,同时空间被压缩的很小,导致整体样式变得很难看。专栏介绍自动替换为长介绍加剧了这种情况,尝试修改介绍,发现字数限制和之前相比变得更严格了——只允许 25 个字符,这样原有的短介绍也没法使用,只好留空。

同时还发生了另外一个奇怪的变化:知乎账号的关注人数突然多出来 8000 左右。

查了一下,才发现原来这是知乎的一次专栏升级,而上面的这些变化属于这一条升级举措:

专栏升级后,过去喜欢和认可专栏的知友,将直接关注创作者本人帐号。对于升级完成后的专栏,「关注专栏」这一功能将会下线,专栏会完全属于创作者个人。

这也就意味着:

  • 不考虑专栏关注者意愿,强制专栏关注者关注作者。
  • 不考虑专栏作者意愿,直接去除关注等功能。
  • 要关注一个专栏就要关注这个作者,接收所有和专栏不相关的动态。

在我看来,这是知乎专栏的一次降级。

这让在互联网上想只接收自己关注的资讯变得更难了一点。

国内技术书盗版现状

我在 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% 都是盗版影印书。

欢迎参加 FlaskCon 2020!

FlaskCon 是一个社区举办的 Flask 大会,将会在 7 月 4 号举行(为期两天)。按照官方的介绍,它:

  • 100% Remote
  • 100% Free
  • 100% Community-driven

和 DjangoCon 类似,FlaskCon 会专注于 Flask 相关话题,包括 Flask 扩展介绍、开发经验、最佳实践、类似框架的对比等等。

如果你想分享 Flask 开发相关的经验,介绍你对 Flask 的有趣应用,或者是有任何和 Flask 相关而且你很想拿出来聊一聊的话题,欢迎报名演讲。演讲有 20 分钟和 40 分钟两种类型,需要使用英语。

你可能会觉得大会的 Logo 不是很好看……不要担心,这些设计还在慢慢改进中,如果你有任何和大会网站、议程设置、设计相关的建议,可以发邮件到 flaskcon@gmail.com,或是在 Discord 频道和 Reddit 主题帖里反馈。

P.S. 如果你想帮忙改进大会网站,最直接的方式是自己动手修改源码

相关链接:

一个困扰我两年的 Flask「Bug」

TL;DR 版本:

如果你的程序存储在单脚本里,比如 app.py,那么 .flaskenv 和 .env 应该放在程序脚本的同级目录:

myproject/
    - app.py
    - templates
    - static
    - .flaskenv
    - .env

如果你的程序存储在程序包里,那么 .flaskenv 和 .env 应该放在程序包的同级目录:

myproject/
    - app/
        - templates
        - static
        - __init__.py
        - views.py
    - .flaskenv
    - .env

如果你把 .flaskenv 和 .env 放到了再往上一层(myproject 再往上),或是把你的程序包或程序脚本放到了一个子目录(比如 /myproject/myapp),那么执行 flask run 就会出错。


严格来说算不上 bug,而是一个很容易导致出错的设计。具体行为是,如果你安装了 python-dotenv,同时在 Flask 程序的上层目录创建了 .env 或 .flaskenv 文件,那么你将没法成功执行 flask run 等命令,因为这会导致 Flask 没法正确找到对应的 Flask 程序实例。

这个问题从 Flask 开始引入 CLI 机制开始就存在了,困扰了我两年。18 年偶然在用户根目录创建了一个 .env 文件,发现 Flask 程序没法运行了,当时遇到的各种 bug 太多,没仔细考虑这两者之间的关联。后来经过几次测试,才确定下来是上层目录的 .env 和 .flaskenv 文件导致,但是一时找不到原因,就暂时放下了。直到 19 年 11 月,花了几个小时排查,还是没找到原因。

中间花了很长时间来追踪 Windows 特定的 Flask 程序无法启动的 bug(TypeError: environment can only contain strings),实在是怕了。因为 Flask 的 CLI 涉及太多东西,有时你要钻进 python-dotenv(#101) 和 Werkzeug(#1320) 才能找到问题的原因。

但是问题不解决的话,你永远睡不好觉。《Flask Web 开发实战》第一部分的示例程序都放在了一个程序仓库,而且都放在了子目录,这意味着如果读者错误的在仓库根目录创建 .env 和 .flaskenv 文件的话,就会导致子目录下的六个示例程序没法运行。前后大概收到 6 个相关的读者反馈,虽然后续在网站上添加了提醒,在重印的书里介绍创建 .env/.flaskenv 文件的地方追加提醒,但这终究没有真正解决问题,而且总会有人可以完美的错过所有提示。

如果每个人都可以在书上标记出错位置并共享,那么这一页应该会有很多红色小叉号(参考《超级马里奥制造》,也许未来某个电子书平台会做出来这个功能 :P)。

前几天在这个 Issue 的提醒下,又花了两个小时排查,这次终于找到原因。加上写 PR(#3560)和 Issue(#3561),前后两年一共花了 8 个小时,这个问题终于有了着落。没意外的话,预计会在下一个版本的 Flask 中更新。下面是具体原因。

本来以为这个问题和 python-dotenv 或 Werkzeug 相关,没想到只是 Flask 本身代码的问题,我太笨了,这个问题本可以早一点解决。

按照预定的行为,当安装了 python-dotenv,Flask 会自动加载 .env 和 .flaskenv 里的环境变量。python-dotenv 在搜索存储环境变量的文件时,会从当前目录开始向上搜索,如果找到就返回对应的文件路径。但是这时 Flask 如果发现 .env 或 .flaskenv 的所在目录不是当前目录,就会把当前工作目录切换到 .env 和 .flaskenv 所在的目录(相关源码)。而如果你的程序模块或程序包不是和 .env/.flaskenv 同级目录的话,就会导致找不到程序实例。

使用下面的步骤可以重现:

$ git clone https://github.com/greyli/flask-env-test
$ cd flask-env-test
$ pip install -r requirements.txt  # or just pip install flask[dotenv]
$ cd hello
$ flask run

示例项目的文件结构如下:

- flask-env-test
    - .env
    - hello
        - app.py

像示例程序这样把程序存储在 app.py 文件中时,运行 flask run 你会看到下面的报错:

$ flask run
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
Usage: flask run [OPTIONS]

Error: Could not locate a Flask application. You did not provide the "FLASK_APP" environment variable, and a "wsgi.py" or "app.py" module was not found in the current directory.

如果你使用 FLASK_APP 指定了程序的导入路径,那么错误大概会是这样:

$ flask run
 * Serving Flask app "myapp"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
Usage: flask run [OPTIONS]

Error: Could not import "myapp".

相关链接

安装 Python 依赖出现 MarkupSafe ImportError … Feature 报错的解决方法

这个报错在 3 月 8 号 setuptools 发布新版本之后出现,通常会在安装 Python 依赖时触发。

报错信息

使用 pip 安装依赖时的报错如下:

Collecting markupsafe==1.0
  Downloading https://.../MarkupSafe-1.0.tar.gz (14 kB)
    ERROR: Command errored out with exit status 1:
     command: '...\python.exe' -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'...\\pip-install-bsormril\\markupsafe\\setup.py'"'"'; __file__='"'"'...\\pip-install-bsormril\\markupsafe\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base '...\pip-install-bsormril\markupsafe\pip-egg-info'
         cwd: ...\pip-install-bsormril\markupsafe\
    Complete output (5 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "...\pip-install-bsormril\markupsafe\setup.py", line 6, in <module>
        from setuptools import setup, Extension, Feature
    ImportError: cannot import name 'Feature'
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

使用 Pipenv 安装依赖时的报错如下:

An error occurred while installing markupsafe==1.0 --hash=sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665! Will try again.
Installing initially failed dependencies…
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 1874, in do_install
[pipenv.exceptions.InstallError]:       keep_outdated=keep_outdated
[pipenv.exceptions.InstallError]:   File "...Python\Python36\site-packages\pipenv\core.py", line 1253, in do_init
[pipenv.exceptions.InstallError]:       pypi_mirror=pypi_mirror,
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 859, in do_install_dependencies
[pipenv.exceptions.InstallError]:       retry_list, procs, failed_deps_queue, requirements_dir, **install_kwargs
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 763, in batch_install
[pipenv.exceptions.InstallError]:       _cleanup_procs(procs, not blocking, failed_deps_queue, retry=retry)
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 681, in _cleanup_procs
[pipenv.exceptions.InstallError]:       raise exceptions.InstallError(c.dep.name, extra=err_lines)
[pipenv.exceptions.InstallError]: ['Looking in indexes: https://.../pypi/simple', 'Collecting markupsafe==1.0', '  Using cached https://.../MarkupSafe-1.0.tar.gz (14 kB)']
[pipenv.exceptions.InstallError]: ['ERROR: Command errored out with exit status 1:', '
command: \'...\\.virtualenvs\\helloflask-evdb6idn\\scripts\\python.exe\' -c \'import sys, setuptools, tokenize; sys.argv[0] = \'"\'"\'...\pip-install-pkgojp4t\\\\markupsafe\\\\setup.py\'"\'"\'; 
__file__=\'"\'"\'...\\\\pip-install-pkgojp4t\\\\markupsafe\\\\setup.py\'"\'"\';f=getattr(tokenize, \'"\'"\'open\'"\'"\', open)(__file__);code=f.read().replace(\'"\'"\'\\r\\n\'"\'"\', \'"\'"\'\\n\'"\'"\');f.close();exec(compile(code, __file__, \'"\'"\'exec\'"\'"\'))\' egg_info --egg-base \'...\\pip-install-pkgojp4t\\markupsafe\\pip-egg-info\'', '
cwd: ...\\pip-install-pkgojp4t\\markupsafe\\', '    Complete output (5 lines):', '    
Traceback (most recent call last):', '      
File "<string>", line 1, in <module>', ' 
File "...\\pip-install-pkgojp4t\\markupsafe\\setup.py", line 6, in <module>', '
from setuptools import setup, Extension, Feature', "
ImportError: cannot import name 'Feature'", '----------------------------------------',
'ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.']
ERROR: ERROR: Package installation failed...

其他工具输出类似,主要异常信息是 MarkupSafe setup.py: ImportError: cannot import name Feature。通常会在安装 Flask 项目的依赖时发生,因为 MarkupSafe 是 Flask 的依赖之一。

原因和解决方法

出现这个报错的原因是因为 Python 打包工具 setuptools 在 46.0.0 版本删掉了弃用的 Feature,而 MarkupSafe 刚好在 setup.py 文件里导入了这个类,所以会出现报错。

解决方法很多,最直接的是更新 MarkupSafe 到最新版本(1.1.1),新版本去掉了对 Feature 类的导入。如果使用 requirements.txt 存储依赖列表,那就把 MarkupSafe 的版本号改成 1.1.1(找到 MarkupSafe 开头那一行,替换版本号):

MarkupSafe==1.1.1

然后重新执行:

$ pip install -r requirements.txt

对于 Pipenv,可以直接执行:

$ pipenv install markupsafe==1.1.1

如果你是《Flask Web 开发实战》的读者,正在为第一部分的示例程序安装依赖,那你还需要执行下面的命令固定 sendgrid-python 的版本(它在新版本添加了不向后兼容的 API 变动):

$ pipenv install sendgrid==5.3.0

《Flask Web 开发实战》读者备注

如果你在 2020 年 3 月 8 号到 4 月 5 号之间买了《Flask Web 开发实战》,把示例程序克隆到了本地,然后尝试运行 pipenv install 或 pip install -r requirements.txt 命令来安装依赖,那很大概率你会遇到这个问题。

除了使用上面的方法之外,你还可以通过更新本地代码来解决。我最近给所有示例程序的依赖文件做了一次更新,除了书里涉及的 API 产生变动的依赖,其他依赖都已经更新到最新版本。

你可以使用下面的命令来更新你在本地的程序仓库(注意这会重置你对源码进行的修改):

$ git fetch --all
$ git fetch --tags
$ git reset --hard origin/master

然后重新执行一次 pipenv install 或 pip install -r requirements.txt 即可解决这个问题。如果遇到其他虚拟环境和依赖安装的问题可以参考这篇文章解决。

相关链接:

《Flask Web 开发实战》虚拟环境/依赖/Pipenv 等问题解决方法

注:这篇文章的主要受众是《Flask Web 开发实战》的读者。

注2:文中的 $ 符号标识一条命令行命令的开始,$ 前面是当前工作目录,# 号后面是注释。你实际只需要输入 $ 符号和 # 号之间的内容,不包括开头和结尾的空格。

在群聊和论坛里总是看到和虚拟环境和依赖安装相关的各类问题,这篇文章希望能够提供一个统一的解决方案。下次如果遇到有人问虚拟环境/Pipenv/依赖安装相关的问题,请把这篇文章的链接丢过去。

安装 Python 库非常慢?

在进入正题之前,你需要先解决基础设施问题。你在执行 pip install 命令或 pipenv install 等命令时会不会网速非常慢?20k/s 或者干脆看到 Time out,Connection reset 之类的报错,这种情况下,你需要设置 PyPI 镜像。具体操作可以在这篇《从国内的 PyPI 镜像(源)安装 Python 包》看到。

要不要继续使用 Pipenv?

因为书里面在一开始介绍了使用 Pipenv 管理依赖和虚拟环境,同时所有的安装第三方库的命令也都是使用 Pipenv,所以我们要解决的第一个问题是「要不要继续使用 Pipenv?」

我的建议是,如果你在使用的过程中没有遇到过任何报错,那么就继续使用它。直到你觉得它在某些地方不再让你满意。

但是如果你在使用的过程中遇到了问题(首先确保你使用的是最新版本的 Pipenv),比如:

  • 锁定依赖很慢,停留在「locking…」这样的提示不动
  • 执行正确的命令但是总是出现报错

附注 如果你安装依赖时的报错是「MarkupSafe setup.py: ImportError: cannot import name Feature」,请参考这篇文章解决。

那么就继续看下去。

如果不用 Pipenv,我该怎么办?

解决方法和替代工具非常多,这里给出两个。

方法一:不用虚拟环境

最简单的解决方法就是不用虚拟环境。如果你是一个初学者,那么不用虚拟环境完全没问题。现在你把所有的 Python 包全都安装在一个盒子里,你只需要会使用 pip 安装依赖,也就是使用下面的 pip install 命令:

$ pip install flask

附注 顺便说一句,不用虚拟环境时,如果你是使用 Linux 和 macOS 系统的 Python 3 用户,那么执行 Python 和 pip 相关命令的时候需要输入的是 python3 和 pip3,比如:pip3 install flask。后面不再提示。

这个命令会为你安装 Flask。每到需要安装一个包,你就执行这个命令,把上面的 flask 替换成你要安装的包名即可。类似的,书中所有 pipenv install xxx 形式的命令也都替换为 pip install xxx。

这时你可以跳过 Pipenv 和虚拟环境相关的内容。在第一章,当你把当前工作目录切换到 helloflask 文件夹内之后,整个 1.1 小节你只需要执行下面这行命令:

$ pip install -r requirements.txt

而第二部分每章开头的下面这两行命令:

$ pipenv install --dev
$ pipenv shell

都要替换为:

$ pip install -r requirements.txt

这个命令会安装对应项目的所有依赖,所以后续介绍各个 Python 库时的安装命令不需要再执行。

方法二:使用 virtualenv/venv 来管理虚拟环境,搭配 pip 来管理依赖

第二种方法是改用更基础的工具:你仍然使用 pip 安装依赖,同时搭配 virtualenv 或 venv 来管理虚拟环境。如果你选择这个方法,那就跳过书中对 Pipenv 的介绍,改为阅读这篇《要不我们还是用回 virtualenv/venv 和 pip 吧》。阅读完上述文章后,再继续阅读。

现在你可以跳过 Pipenv 相关的内容。第一章切换进 helloflask 目录后,整个 1.1 小节你只需要执行下面的命令:

$ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
$ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
$ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

附注 :上面命令里 # 号及之后的文字是注释,不需要输入。如果你使用 Python2,第一条命令需要改为 virtualenv env。这三行命令的作用依次为:创建虚拟环境、激活虚拟环境、从 requirements.txt 文件安装依赖列表。

类似的,第二部分每章开头的下面这两行命令:

$ pipenv install --dev
$ pipenv shell

都要替换为:

$ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
$ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
$ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

同时书中所有 pipenv install xxx 形式的命令都替换为 pip install xxx(不过你并不需要一个一个执行,因为每章开头执行 pip install -r requirements.txt 时会安装所有项目相关的依赖)。

对于 PyCharm 设置 Python 解释器部分的内容,大致可以沿用,只不过在图 1-4 的位置你需要从列表里选择当前目录 env 文件夹(虚拟环境文件夹)中的 Python 解释器,根据操作系统的不同,将会是 env/bin/python 或 env\Scripts\python.exe。

退出虚拟环境时,使用下面的命令:

$ deactivate

使用书中同样的代码,但是却出现报错?/ 我该怎么安装依赖?

有一些 Python 库在版本变化时会带来 API 的变化,而书中示例程序代码是基于每章开头注明的 Python 库来开发的,所以在更新依赖版本的时候可能会导致示例程序的代码出错。我建议你按照书中的命令来安装依赖,这会从项目依赖文件里安装固定版本的依赖列表,不要跳过第二部分每章开头的命令。

具体来说,如果使用 Pipenv,对应的安装依赖的命令是:

$ pipenv install --dev

如果使用 pip,对应的命令则是(如果创建了虚拟环境,需要先激活虚拟环境):

$ pip install -r requirements.txt

如果是第一章的示例程序,那么在 helloflask 目录下执行一次即可。如果是第二章的四个程序,那么在每一个程序的根目录执行一次即可。后续所有介绍新的 Python 包时给出的安装命令不用再执行。

关于第一部分示例程序的项目结构和启动问题

在第一部分的源码中,一共有 6 个 Flask 程序,分别保存在 helloflask/demos/ 目录下的六个子文件夹内。用来存储环境变量的 .env 和 .flaskenv 文件需要在这些子文件夹内创建,而不是放到顶层目录(helloflask/)。同时为了方便操作,这 6 个程序共用同一个虚拟环境,所以在 helloflask/ 目录下创建虚拟环境。

注意 不要在 helloflask/ 目录下创建 .env 和 .flaskenv 文件,这会导致子目录下的程序无法正确启动(issue #3561)。

如果你按照书里给出的提示执行命令,一般不会出现问题。但是为了防止各种意外情况,这里列一下本书第一部分的操作流程($ 符号前是当前工作目录)。

具体的操作顺序就是,克隆仓库以后,切换到 helloflask 目录:

$ cd helloflask

然后创建虚拟环境、激活虚拟环境、安装依赖:

/helloflask $ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
/helloflask $ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
/helloflask $ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

接着看到介绍 Flask 最小程序部分,启动第一部分的示例程序:

helloflask/ $ cd demos/hello
helloflask/demos/hello $ flask run

现在看到了第二章,启动第二部分的示例程序(先执行 cd .. 回到上层目录,即 demos/ 目录):

helloflask/demos/hello $ cd ..
helloflask/demos $ cd http
helloflask/demos/http $ flask run

看到第三章,启动第三部分的示例程序:

helloflask/demos/http $ cd ..
helloflask/demos $ cd template
helloflask/demos/template $ flask run

以此类推。

遇到了自己没法解决的问题怎么办?

如果遇到问题,你可以先尝试:

  • 看一下本书 GitHub 仓库里的 FAQ 页面 有没有你要找的答案
  • 在搜索引擎搜索你的错误信息关键字,尝试自己解决

自己无法解决的话,可以:

  • 发到 GitHub(issue)和论坛(用文字形式贴出完整的错误信息、相关代码和命令,尽可能的详细描述相关信息)。
  • 发到交流群(建议优先选择发到 GitHub 和论坛,我会定期回复,群聊沟通效率很低,更适合闲聊)

Flask 新书完成时间推迟

很抱歉没能按照预期时间完成 Flask 新书。本来这个消息要到四月一号(预估的完成时间)发布的,但是怕被当做愚人节玩笑,所以还是提前一点比较好。

去年大部分时间花在了组织活动和准备演讲上,而今年前几个月又大都用来玩游戏和做外包了,所以一直没有全身心投入到新书写作上,目前大约只完成了一半……

为新书写了一个简单的主页,地址在 http://helloflask.com/book/2。我在页面上放了一个写作进度条,方便感兴趣的人了解进度。同时也加了一个订阅功能,你可以用 Email 来订阅新书的发布消息。

感谢大家的关注和支持,祝大家一切顺利。

明天开始专心写作。

参加 PyCon China 2019 上海站

19 年的九月末,参加了在上海(主会场)举办的 PyCon China 2019(Python 中国开发者大会)。这是第二次参加 PyCon China。

台湾 COSCUP 回来休息了一阵,回过神来已经是九月初了。和去年不一样的是,今年同时参与了 PyCon China 的筹备工作。大脑短路,给自己安排了太多事情:除了大会网站更新、文档翻译、社交网站维护这些前期工作和讲师接待、闪电演讲组织和主持这些现场工作,还要准备自己的闪电演讲、主题演讲和一个三小时的教程(Tutorial),在忙到快要崩溃的时候,终于在开始前两天说服辛庆老师(组委会总负责人)取消了我的教程,要不然我可能会累死 :/

19 号下午出发,这是我第一次来上海,最初的几个记忆碎片是:爬满高架柱子的绿植,夜晚还清晰可见的云,在路边练习红歌的小学生……

blank

泡汤的 Flask 专场

今年本来是想把包括 Flask 作者在内的几个主要的 Flask 维护者都邀请来,这样我们就可以开一个非常货真价值的 Flask 专场,还可以凑成一次 Pallets 团队成员大聚会。所以我分别给 Armin RonacherDavid LordMiguel GrinbergHsiaoming Yang 发了邀请邮件。不过因为各种原因,最后只有 Armin Ronacher 和 Hsiaoming Yang 能来(后来意识到经费问题,幸好没有全都来)。

20 号早上去机场接 Armin。我的英语水平一般,而 Armin 的英语又有一点难听懂,所以沟通并不多。回酒店的路上,我们一起去吃了早饭。如果写作算是我的职业的话,那么我职业生涯的开端就是 Flask,请 Flask 作者吃顿饭也算是一次小小的感谢。当然,最后也送了一本我的书给他。

Hsiaoming 现在住在日本,做了很多有意思的开源项目,很羡慕他的生活状态。我一直不知道该怎么称呼他,这次终于知道了他的中文名。

大会第一天中午的时候,我们三个加上 Luciano Ramalho(《流畅的 Python》作者,这本书我还没读过……)一起吃了午饭,也算是勉强达成了 Pallets 成员聚会的计划。聊天的时候,发现作为英语不太好的东道主,能够拿出来聊一聊并且可以引起外国人共鸣的话题并不多,中国菜和伟大的城墙算两个。

因为 Armin 的演讲和 Flask 没关系,后来又有了两个 Django 的议题,所以 Flask 专场最后改成了 Web 专场。

闪电演讲的彩蛋

一开始只是提议在今年的大会上增加闪电演讲环节,让大会更加轻松有趣一点,最后却变成了负责组织和策划闪电演讲。第一次组织闪电演讲,第一次做主持人,第一次做闪电演讲,所以都做的不是非常好。

闪电演讲环节安排在 B 会场的最后一场。我在闪电演讲的开场画面里藏了一个彩蛋,但是还没来得及展示它。

blank

这个彩蛋是这样的:开场画面里的时间其实是实时变动的,比如上面写的是「5 点 50 分 准时开始」,如果时间过了 50 分,那么画面上的时间也会跟着变成「5 点 51 分 准时开始」……

本来想安排一个茶歇,让更多其他会场的人有时间过来听闪电演讲,顺便就会有人发现这个彩蛋。但是因为整体议程时间往后推迟太久,已经超出和酒店约定的结束时间,所以还没能等到超出 50 分就匆匆开始了闪电演讲。

虽然这个彩蛋可能会有点无聊,但我想还是要在这里写出来,给它一点存在感。

明年再见

今年的大会主会场组织出了很多问题。比如 B 会场各种设备不停出故障,简直车祸现场。同时因为摄像人员的工作失误导致上午主会场 laike9m 的演讲视频没有录完整,演讲最精彩的部分没有录进来,这大概是今年 PyCon China 最遗憾的事情。当然,这些失误大都是因为没有好的流程约定、工作监督和备用方案,经过会后的总结和反思,明年一定会做的更好。

因为酒店场地日程紧张,大会前一天(9/20)晚上才能开始布置会场,很多志愿者在这里通宵工作,感谢志愿者和工作人员们的辛苦付出!

这几天里发生了太多事情,见到了很多新朋友。一天过得很快,大会结束总让人感觉有些失落,大家从不同的方向汇集到这一点,一起在这里停留了一段时间,然后又各自向自己的方向出发。祝大家一切顺利,或许明年能再见。

相关资源

李辉

2020年1月17日

头像

去年新养了一个盆栽。因为不用浇水不用施肥,自带一个巨大的养料库,所以也可以叫做自带电池(batteries included*)的盆栽。

blank

获得步骤如下:

  • 买一个山芋(小的大概一块钱)
  • 不要吃,等它发芽
  • 切掉底部
  • Bingo!

P.S. 右边的龙猫是瑶瑶的作品。

P.P.S 第一次看到「batteries included」这个词是在 Django 的文档里,用来描述 Python 的特点,有时也会被翻译成「内置电池」。

2019 年总结

从 2019 年的日程本来看的话,越往后面字越潦草,涂鸦和乱画也越来越多。总的来说,2019 年偏离计划太多,虽然有一些计划外的收获,但年初定下的目标大都没有完成。从好坏两方面总结下:

Good

Bad

  • 玩游戏太多
  • 学东西太少
  • 看书太少
  • 产出太少
  • 收入太少

写作

一月份完成了电子书《Flask 入门教程》,虽然是免费电子书,但也在书里放了一个付款二维码(定价¥10),目前收入快接近 400,大概能证明自愿付费不太可行(也可能是因为我一开始错误的把付款二维码放在了后记里 )。2019 年快结束的时候,收到两笔记忆深刻的付款——一份是来自知友 CycleUser 的 66 元巨款,另一份是来自*昱的 2 块钱定金(现在还没收到剩下的 8 块……):

blank

上半年剩下的时间写了新书的第一部分,然后就开始投身到准备演讲和 PyCon China 里了。年底从疯狂的忙碌里安静下来,又回过头来更新了年初写的《Flask 入门教程》,然后花了两个月把上半年写的书稿重写了一遍。

游戏、书和电影

2019 年没怎么看书,Kindle 被闲置到电池故障。电影和电视剧看的也不多,两者分别最喜欢《守望者》和《废柴联盟》。游戏倒玩了不少,和瑶瑶一起通关了《超级马里奥 3D 世界》,《超级马里奥银河》进度玩到 30%,其他大部分时间都在玩《喷射战士》。

2018 年沉迷《皇室战争》,浪费了很多时间,所以在新年计划里加了一条「2019 年不玩皇室战争」。一年过去了,说到做到,但是又沉迷上了《喷射战士》……单场最高战绩是 16 杀,不过最喜欢 0 死 12 杀这一场:

blank

为了避免重蹈覆辙,元旦开始放任自己玩了两天游戏后,打算执行「2020 年不玩皇室战争和喷射战士」计划。但是想到那样生活会少了很多乐趣,所以现在又改成春节后再开始执行这个计划 :P

今年计划把书架上大部分的书都读完,通关手里的游戏,在这之前不买新的书和游戏。

演讲

2019 下半年大部分时间都花在了五个演讲上。输出太多输入太少,本该学的很多东西只好又推到新的一年了。因为经验太少,也没法像其他前辈那样可以在很多话题上游刃有余的自由发挥。收获是了解了怎么演讲,变得不那么容易紧张。

这几次演讲里,在台湾 COSCUP成都 PyCon China 的演讲反响最好,和别人交流的也最多,有很多人因为 COSCUP 那场演讲认识我。参加这几个技术大会也认识了很多的前辈和朋友。

除了准备演讲,19 年还完整参与了 PyCon China 2019 的筹备工作,有很多想法,有时间单独写篇文章。想起来这几次参会的总结文章还都在草稿状态,虽然已经是 2020 年了,我还是要写完它们的 :/

其他

五月份来了南京。这一年仍然没工作,靠《Flask Web 开发实战》的稿费和几个外包项目的收入维持生活开支。每次钱花得差不多,已经在翻招聘网站、了解面试技巧的时候,总会有一笔不多不少的稿费让我可以再拖一段时间。就这样一年又过来了。

很感谢女朋友没有反对我不工作,而且还乐观的和我过这种有点拮据的生活。19 年在自己的事情上投入太多的时间,对她的陪伴和关心不够多,今年要多和她出去玩。

2020 计划

今年的重点是这三件事:

大部分时间还是会用来写书。除了写书,还会尝试录一个视频教程。编程有太多的东西要学,只能先专注流行和基础的东西。保持时间投入,不强求成效。英语的话,虽然 19 年是真正开口说英语最多的一年,不过并没有什么实质性的提高,还需要大量输入和练习。

2018 年因为写书的生活非常单调,所以停掉了时间记录(要不然我就能清楚知道自己在游戏上浪费了多长时间了),今年重新开始记录每天的时间花销,年底来分享数据。


年总结还是要在当年完成,这样就不用纠结「今年」和「明年」两个词的用法。

参加微软 2019 华东高校黑客松(Hackathon)

上上上周,我从南京东边的郊区跑到南边的郊区参加在东南大学九龙湖校区举办的微软 2019 华东高校黑客松。这是我第一次参加黑客松,不过并不是去参赛,而是作为微软 MVP 去客串评委。

黑客松

在很长一段时间里,我都不知道「黑客松」是什么,直到看见「黑客马拉松」这个译法,才恍然大悟,明白是「Hackathon」的音译。虽然「创客马拉松」和「黑客马拉松」更通俗易懂,但我还是更喜欢「黑客松」。

「黑客松」最简洁,而且不会让人误以为是「只有黑客才能参加的马拉松」。虽然是一个奇怪的新词汇,但总有一天,它会变得和沙发、咖啡、巧克力还有图样图森破一样平常(不过「鲁棒」我想我这辈子都接受不了)。更重要的是,「黑客松」作为一个新词汇不会污染现有的信息环境,搜索马拉松的人不会看到黑客松相关的信息,反过来也一样。但是使用「黑客马拉松」的话,就有可能看到黑客和马拉松相关的信息。

顺便说一句,在黑客松的维基百科页面还看到「Sprint」被翻译成「短跑」,这种翻译就太难理解了:今年 XX 技术大会有短跑活动你要不要参加?如果意译会带来混淆,这时不如保持用英文。

报队名

在黑客松里,创意很重要,那么队名可以算是创意的第一个部分。第一天上午,主要的环节就是报参赛队名领姓名卡和餐券,发现了很多有意思的队名。当时我在想,假如把这个环节放到学校礼堂,让某个严肃的校领导来念队名,应该会很有意思。下面是所有的参赛队队名:

  • 宁说的都队
  • 六点起床
  • 知道伐
  • 济人同创
  • 三个三本
  • 菜鸡互啄
  • 北泽棒球社
  • Hello, world
  • 同航
  • 结束了去吃火锅还是烤肉呢
  • 仪科19
  • 好好学习
  • 白给??白给
  • TDZ
  • 脱发先锋
  • 我的代码怎么穿着品如的衣服
  • NJUSE菜鸡小朋友
  • 3Rookies
  • 修仙小队
  • 舍友和女朋友视频聊天声音能不能轻点队
  • 南猿不折北
  • 数理基础不扎实队
  • 三傻大闹微机房
  • HAPE
  • 阿撒托斯小队
  • 基普乔格队
  • HackContingent
  • mortal coding
  • 南上加南加南
  • 老年开发团队
  • 肥宅快乐队
  • sdas
  • 做不出来不改名
  • 仗键天涯
  • NULL
  • 不知道叫什么队
  • 现代哲学研讨小组
  • 我体系结构写完了
  • 我只想躺着赢奖杯队
  • 辣鸡队
  • 业界新手队
  • 挺秃然的
  • 菜?落泪
  • 送给最好的ta
  • ?
  • 你说的都队
  • NULL
  • 对对对
  • 问题不会求解队
  • FPXnb队
  • 哈哈哈哈
  • 脱发zzz
  • 南京航空航天大学计算机科学与技术学院
  • 我躺好了
  • 半学期没敲代码
  • warning&error&fault
  • 看什么看你个小秃头
  • include
  • 萘乙芴
  • 为了伯伦希尔的荣耀

blank

一共有 61 队,第一天来签到的人快坐满了一间大教室。不过,第二天下午评审的时候只剩下 24 队,跑掉了三分之二……

评审

有些人是第一次公开展示,有些人因为时间太短准备不足,所以大部分都没能在规定的五分钟时间里完整的展示自己的项目。而我也是第一次做评委,发现打分好难。有些项目没有给出足够的信息,有些项目还和对应的评分点不相关。评分表也太细致了,一共分了六大类 13 个评分点,每个评分点都有各自的分值。如果换我来设计的话,只要分六大类就好了,每个大类分值统一为 10 分,最后取平均值。

每个展示后还有两分钟的提问环节,我对一些感兴趣的项目提了一些问题。遗憾的是,提问的时候忘记说赞美的话,比如三个三本队的展示视频做的很棒,辣鸡队的 GitHub 注释扩展也很有实用价值。另外我误解了一个 Markdown to revealjs 项目的实现,以为是 revealjs 的竞品,后来才明白是从 Markdown 文件生成 revealjs HTML 文件的工具。不过不知道是不是搞错了,因为 revealjs 本身也支持使用 Markdown,而且可以引入外部 Markdown 文件……

最终评分结果里,其实第六名和第七八九名都很接近,七八九名分别是我只想躺着赢奖杯队、修仙小队和数理基础不扎实队。退一步讲,即使没获奖也没关系,毕竟奖品和奖金似乎也不是很有吸引力……如果能在这个过程里体会到快乐,那就足够了。

项目

我比较感兴趣的项目有下面几个:

第一个项目类似 Stack Overflow,不过可以通过 Docker+code-server 给每一个提问者提供一个在线的代码编辑和运行环境,回答者也可以相应的复现环境,对代码进行修正和调试。我当时的想法是这会给提问者带来比较高的门槛,而且有些问题并不一定有可以运行的代码。这个项目或许可以做成类似 JSFiddle 这种工具,然后开放给 Stack Overflow 这类网站集成使用。

好吧,其实我对深度学习完全不了解,只是单纯觉得第二个项目完成度很高,很喜欢这种在线可视化教程。

第三个项目很期待可以被实际做出来,不过显示注释的形式建议不要在代码右侧显示,而是单独在页面左侧或右侧显示一个浮动的边栏。

第四个项目没有参加评审,是偶然在 GitHub 上发现的。它可以实现通过扫描图片上的活动日期自动生成日历。有一个爬取教务系统信息生成课表导入日历的 Super_iCal 项目(看什么看你个小秃头队)类似,也很实用。

有一些只针对校园场景的 App 也不错,但还有些似乎少点东西,感觉更像是外包项目。知道伐队的全景照片生成项目 OneDay 实现的很完整也很漂亮,但是可能只会吸引小众群体。另外就是各种智能项目,大部分并不够实用。比如智能冰箱,反正我是不想要一个会发出语音提示「放入苹果」的冰箱,我也不想每次放进去什么东西还要手动在手机上设定过期时间提醒。还有智能垃圾分类垃圾箱,垃圾箱可以自己判断投入的垃圾种类并进行分类,也就是说我可以随便扔垃圾进去,那么这样会不会让人们更不愿意养成垃圾分类的习惯呢?

本来以为会看到很多很傻但很有意思的小游戏,像是仗键天涯队的 HackerGo 这种,不过好像就这一个。因为没有设计,采用全手绘风格。这是他们夜里人工骑自行车踩点采集坐标画出来的学校地图:

blank

因为手里只有队名列表,但是没有对应的项目列表和介绍,其他的都忘得差不多了。大部分项目都上传到了这个 GitHub 组织,感兴趣的话可以去转转。

氛围

虽然我没参加过黑客松,还是感觉这次的黑客松氛围不是很足。我想有一部分原因是会务上面投入的精力(和钱)不够。当然,组织活动没有收入,而且场地、经费也处处受限,参与过 PyCon China 的筹办,这些我能够理解,也很感谢东大微软学生俱乐部的同学辛苦组织。从我的角度来看,有一些想法和小建议:

  • 不论是导师演讲,还是最后的项目展示,都要有录像,会后放到网上分享
  • 需要一个贯穿全流程的主持人来引导现场秩序
  • 每一个参赛者都应该发一份小小的纪念品,比如 T 恤
  • 虽然场地条件有限制,但还是应该尽可能的解决 WiFi 和电源接口问题

其他

把今年参加几次活动收集的贴纸拿去送人了,不过 Flask 贴纸没多少人要,剩下的几张又被我带回来了 :/

blank

这次还认识了很多前辈们:微软的高明珠老师、陈昊老师和吴含宇老师;南京本地 Office 方向 MVP 方洁影老师,从广东来真的是老师的 AI 方向 MVP 卢建晖老师,从苏州来在做微信小程序开发的 .NET 方向 MVP 苏盛巍老师。

blank

(合影里少了第二天到的吴含宇老师)

尴尬的是,虽然同为微软 MVP,但是我对微软技术栈并不是很熟悉,所以聊天的时候基本插不上话……不过第二天下午去地铁的路上和苏盛巍老师聊了很多,苏老师跟我分享了他的创业经历,给了我很多建议,很受启发。

这是一段很有意思的经历,也许明年会去真正参加一次黑客松。


这篇文章本来会更早一点写完,拖到现在一方面是因为要赶新书的稿子没时间写,另一方面是因为意外丢掉了花了 2 小时 19 分钟写的草稿,所以只能重写一遍  :(

  • 写作耗时:7h 49m
  • 图片来源:活动组委会(东南大学微软学生俱乐部)

《Flask 入门教程》第二版发布

Flask 入门教程》第二版发布了!新版本主要有以下变化(详见 commit 列表):

  • 去掉了 Pipenv 的介绍,改为使用 venv/virtualenv+pip
  • 所有提示和注意段落使用引用样式标注
  • 修正了大量笔误
  • 调整前言和后记内容
  • 调整部分措辞

访问本书主页下载 PDF 或在线阅读。