本文为《爬着学Python》系列第十二篇文章。
本来打算周更的,鸽了好几个星期。原因一个是面向对象那篇实在有点难写,另一个是我去弄了个Web项目-Qotes,GitHub地址。之前跟着书和实验楼的在线课试过几次Flask,这次是真正的实战。
体验还算不错。
于是本专题说好了讲爬虫的,结果第一个大实践内容是web项目实战。本文就算在介绍Flask框架之前先给个实例,以后根据此实例展开讲细节吧。
Qotes开发初衷
再贴一下链接Qotes。
其实项目的开发初衷在网站中写了。我本来在简书写这个专题是为了整理知识的,因为简书的Markdown编辑器很好用。但是我后来发现一个问题,简书采取了笔记类应用的文档管理方式,就是一个笔记簿里面可以加一篇篇的笔记,但是笔记里面不能再加笔记,也不能在笔记簿里面再创建笔记簿。这给我带来了很大的困扰
我解释一下我困扰的地方在哪里。
- 可以看到我的文集里面小说分了正文和设定两个部分,要是可以把这两个文集放在一个文件夹“小说”中不就和上面的其他文集逻辑一致了吗,但是这做不到,除非我把小说设定的一系列文章混在小说正文文集的头或者尾部。
- 文集内部也会存在问题。比如说“日记本”里面是我的一些杂乱的零碎的随时笔记。但是单一的结构让我很难将这些笔记进行整理。比如《mongodb tips》这里面其实可以分好几篇文章,但是如果分开来,那么又和该文集中的其他文章逻辑不一致了。
总结起来就是:1文集不能包含2文章不能包含。
我后来想过别的方案。最靠谱的是这个:用Typora在Github上写学习笔记。
但是总觉得有些大材小用了,而且Github打开的速度不太理想(大多数时候正常,有时要2s左右,很难受),更大问题在于Github的private repository需要收费,收费就算了还不方便。功能更符合这个应用场景的GitHub pages又被墙,体验实在算不上好。于是还是决定用简书。于是还是有些不舒服。
后来觉得还是自己写一个吧。于是我就试着写了。
Qotes的特点
卡片
首先要解决的是文章和文集的包含问题。最后我的解决方案是这样的:
- 所有的笔记都是卡片形式存在
- 所有卡片在数据库中地位是同样的,保存在同一个collection中
- 卡片可以互相包含,每个卡片不一定有子卡片,但一定会有父结点
- 如果父结点是用户,那么该卡片就可视为卡片集
- 当然,每个卡片都可以看成是某卡片集的子卡片集
总结起来就是,每个卡片既是卡片又是卡片集。
在数据库中等价就好像是树结点在内存中散布但是通过链接来形成树结构,唯一的不同是root根结点有的时候是用户,而且树结构一般链接记录子结点信息,但是本实例中卡片通过父结点信息完成结构的链接。这样做的原因是:
- 为了方便查询,给相关字段设置索引以后,查询速度倒比根据某个结点的子结点表中的id一个个查询来得更快(理论上)
- 为了简化IO操作。每个结点只能有一个父结点但是可以有许多个子结点。如果记录子结点信息要对列表进行操作,而且卡片所属关系的时候会涉及新旧父结点两个卡片,当前逻辑下只需要把该卡片的父结点信息改掉就可以。
这样做也带来一些问题。比如说一个卡片不能属于多个卡片,但是我所参考的传统树结构也是不支持一个子结点有多个父结点的。我后来开发过程中想了下,整体思路上其实和Linux的文件系统一切皆文件的设计有些类似,但是实现方式上有一些区别。
更多的实现细节在以后的文章中分析吧。
Markdown
另一个特点就是Markdown了。首先语类上基本上要比简书全,但是不如GFM。当然原则是保持Markdown的精神,用纯文本快速构建文档结构。因此卡片的标题直接就是卡片内容中的<h1></h1>
亦即# title
,二级标题h2
和## subtitle
作为卡片内容概要。这些基本内容作为卡片预览时候的信息。
比如说这个卡片编辑的时候不用写标题, 标题和概要会自动生成。
说起来简书的文章需要标题,文章里面又可以用<h1></h1>
其实困扰了我很久。如果不写,文章内容没有一级标题直接用二级标题是不符合Markdown规范的,但是如果写了,从网页来看文章有两个标题其实是不符合HTML规范的。在markdown文件中不存在这个问题在于文件名并不算在文件内容中,所以markdown文件可以完全符合markdown规范填写,给且仅给一个一级标题。
您也别笑我迂腐,HTML规范改到HTML5以后强调了HTML只负责内容分级,把布局和格式交给CSS和JS来做。其实就是这种精神,而Markdown和HTML一样是标记语言,老前辈都在学习进步,见贤思齐是应该的。
另外值得说的就是首页的快速编辑模式。
右边的这个是一个编辑器。是的,除了输入内容和提交按钮什么都没有,实时输入,实时转化成HTML预览,和Typora类似。这个其实也是Markdown的精神,简化输入的同时完成格式。
技术选型
后端,为了快速开发,选择了Flask。为了方便迭代,数据库选择了MongoDB。当然,还有别的原因,比如我比较熟悉Flask,我想尝试一下NoSQL。
前端没什么好说的,Bootstrap简直是我的救星。中间学了半天Javascript和jQuery写了几个简单的交互。两种Markdown编辑器都是从Github找的开源编辑器,说实话找这两个编辑器的时间比我自己写前面提到的几个脚本的时间要长。
其实我有考虑在RESTful API写完,把前端改成AJAX以后,UI优化一下,可能会用Golang重新写一遍这个项目。就目前来说Flask完全够支持业务,虽然没做压力测试,但是我设置的最大同时连接数才1000,不出编码上的太大的意外肯定是足以应付的。这不是我对自己的技术自信,是MongoDB实在太强大了。想重构代码不过就是我想试一试Golang的体验。
其实选MongoDB是我心血来潮一时之间的决定。Flask的MongoDB外部库ODM没一个好用的,真是心累,可能是我被SQLAlchemy和flask-sqlalchemy惯坏了吧。总之我后来直接用pymongo自己写简单映射了,代码可能不太好看但是效果还可以,中间不断改的时候发现可迭代性非常理想。在这里夸一下pymongo的文档,简直是模范,太羡慕了,是我见过的除了PythonHOWTOs以外体验最好的文档。我现在回来看,如果用MySQL并不会更适合我的数据结构设计不说,开发可能还是会更麻烦的。
目前的一些坑
private card还没做。这是我目前最紧急的TODO。
由于备案的关系,我索性关闭了用户之间交流信息的接口来规避风险,设计上是一般用户只能创建自己的卡片,只能浏览自己的以及我的卡片。这样基本算最大程度上利用个人博客能做的业务了。用户可以把它当小工具,但是就备案性质来说它还是个人博客,没有越界。
关注功能也索性直接搁置了,评论功能是从一开始就没打算做。别人的笔记,哪怕公开的,但就别BB了。当然,如果想体验比较完整的功能也可以发邮件给我,邮箱去站点找吧,贴在这里怕被爬到时候一堆垃圾邮件。简书的反爬实在太鸡肋了,限制数量的做法未免有点自欺欺人。
大写加粗的移动端体验不佳。这个其实对我来说不算太大的问题。我从来没用过简书app编辑文章,原因可能是SE的屏幕太小了。而且作为几年的WP用户,我已经习惯不用手机完成电话、短信和看新闻、天气以外的功能了(我真的不是在黑windowphone)。电脑完成工作更方便,手机码不了代码,我的手机上除了简书装了不怎么用,OneNote也是不常用的。以前用WP还会用,iOS端OneNote简直蠢,还不如打开电脑操作。
但是我仔细一思索,对于我不是问题但是对于其他人不一定适用。虽然Bootstrap对移动端的显示已经尽力了,但是我写的主要的交互方式拖拽在手机浏览器上真的很难有用武之地,只能希望什么时候我能像个劳模还会乐意做个app吧。
于是这篇文章其实和Flask关系不大
不是这样的。这篇文章只是先介绍一下实践项目。中间还是有非常多的经验值得交流的。Flask和MongoDB的协同工作就很值得讲,应用部署方面各种坑也是值得分享的。比如说阿里从今年8月开始封禁ECS出25端口就浪费了我很多时间来排查问题。比如centos用yum装supervisor是有多么难受,比如flask-wtform WTForms和flask-bootstrap quick_form到底该如何取舍。至于光头哥Flask Web Development那本书里面的各种时代坑就不谈了。
链接
- Qotes
- 项目GitHub地址
- 其他等更新技术相关文章的时候根据不同内容再写吧。