进阶篇:豆瓣东西的 Jupyter Notebook 实践
之前深入介绍了 IPython、Pandas 等工具,那么它们在 Web 开发中又能发挥怎样的作用呢?豆瓣东西是豆瓣的电商导购产品,为了刺激用户消费、提高营收,豆瓣东西会不间断地在一些重要节日做一些专题活动,而“双十一”这一天也不例外。先看一下 2014 年双十一专题页面(https://dongxi.douban.com/special/2014/1111/ )的一部分,如图 12.11 所示。

图 12.11 2014 年双十一专题页面
我们把图 12.11 中发布框之外的部分称为“推荐位”。运营同事会定期根据阿里后台以及我们自己的数据分析后台在管理后台调整推荐位的内容,曝光更多有价值的商品,向天猫带去更多的流量。
这种操作后台有如下 4 个特征。
- 临时性:这种后台有时效性,在一段时间内要用,之后可能就不再使用,或者只是为了快速实现一个 Demo,而未来的需求很可能会发生比较大的变化。
- 增加管理后台负担:产品开发要响应的运营、产品需求非常多,每个产品线都有一个到多个不同目的的管理后台,在产品不断尝试新方向和试错的过程中,会产生大量的后台,经过一段时间后,有些后台由于提需求的同事离职而荒废,有些由于不再跟进而被忽略,久而久之产生很多不知道是用来做什么,不清楚还有没有人用的后台页面,项目越堆越大。
- 完成的起点高:很多需求,Web 开发者希望糙、快、猛地实现,但是如果把它放入管理后台,必然要遵循当前后台使用的技术和用法、需要具备一定的前端能力。
- 改动部署不方便。
这个时候 Jupyter Notebook、Nbviewer 和 Pandas 这 3 个工具就非常有用。
- 使用 Nbviewer 搭建纯静态 HTML 页面应用,运营可以直接访问生成的静态页面查看数据分析结果,还能使用如 Echarts 这样的工具让数据图表化。
- 使用 Pandas 从数据库或者文件中把结果可视化地呈现给需求方,懂技术的产品同事甚至可以直接在 Jupyter Notebook 页面使用 SQL 语句进行一些查询。
- 临时后台使用 Jupyter Notebook 实现。不需要发生错误后去看 Sentry 对应的异常页面,直接在线上调试修复即可。开发者有非常大的自由度,修复问题要及时得多,而且部署方便。
当你熟悉以上 3 种工具,搭好架子,只需要一些前端知识,花少得多的时间就可以快速完成需求。
完成这个后台的第一步是创建一个内核(Kernel)。之前只介绍了官方提供的 Python 2 以及 Python 3 这两个内核,实际上创建一个内核非常容易,一般选择创建新的内核是希望多种需求可以分开管理,互不影响。
首先创建用户级别的内核目录:
> mkdir ~/.ipython/kernels
系统级别的目录包含/usr/share/jupyter/kernels 和/usr/local/share/jupyter/kernels。
我们只需要创建一个子目录,子目录下有个叫作 kernel.json 的文件:
> mkdir~/.ipython/kernels/double11
> cat~/.ipython/kernels/double11/kernel.json
{
"display_name":"Double11",
"language":"python",
"argv":[
"python",
"-m","ipykernel",
"--profile=double11",
"-f","{connection_file}"
],
"env":{
}
}
Jupyter Notebook 并不能在配置中指定使用哪个 IPython 的配置文件,通常是指定自定义内核来实现设置的定制。现在创建 double11 这个配置:
> ipython profile create double11
然后在~/.ipython/profile_double11/startup 下添加启动文件。使用这种启动 JN 就自动加载的程序,目的就是添加安全配置以及把一些代码提前准备好,而不用把大量的代码都放在 Jupyter Notebook 文档里,每次还需要执行。我们创建两个文件,首先看“双十一”推荐位相关的文件 00-double11.py(文件名字开始的 00 表示优先级,数字越小,优先级越高)。程序分为三部分,第一部分是封装 Redis,原因是 Redis 不支持存储复杂对象,所以封装的作用是存储 cPickle 序列化之后的字符串,取出来的时候再反序列化:
from__future__import unicode_literals
import cPickle as pickle
import redis
class RedisWrapper(object):
def __init__(self):
self.r=redis.StrictRedis(host='localhost', port=6379)
def __getattr__(self, attr):
if attr=='r':
return vars(self)['r']
return pickle.loads(self.r.get(attr))
def __setattr__(self, key, value):
if key=='r':
vars(self)['r']=value
else:
pickled_object=pickle.dumps(value)
self.r.set(key, pickled_object)
r=RedisWrapper()
第二部分是抽象两个公用的按钮函数:
def get_btn(desc):
return widgets.Button(description="提交{}修改".format(desc))
def hide_btn(btn):
print('提交完成')
btn.visible=False
sleep(1.5)
btn.visible=True
第三部分就是对应的推荐位模块的逻辑了。通过上面的图片可以看到,推荐位分为三块:三个图文主题(双 11 购物攻略)、三个商品、两个精选豆列,每个豆列 5 个商品。
图文主题的逻辑如下:
def set_review():
'''修改图文'''
def review_func(btn):
_review_ids=[
review_container.children[i].value for i in range(3)]
r.review_ids=_review_ids
hide_btn(btn)
review_container=widgets.Box()
btn=get_btn("图文")
children=[]
for o in range(3):
c=widgets.Text(description="第{}条图文 ID".format(o+1))
c.value=str(r.review_ids[o])
children.append(c)
children.append(btn)
review_container.children=children
btn.on_click(review_func)
display(review_container)
精选豆列逻辑如下:
def set_doulist():
'''修改豆列推荐位'''
def doulist_func(btn):
doulists=[]
for i in range(2):
doulist=[]
doulist_id=doulist_container.children[i].children[0].value
doulist.append(doulist_id)
_stories=[]
for p in range(1, 6):
_stories.append(
doulist_container.children[i].children[p].value)
doulist.append(_stories)
doulists.append(doulist)
r.promo_ids=doulists
hide_btn(btn)
doulist_container=widgets.Box()
btn=get_btn("豆列推荐位")
children=[]
for doulist_id, stories_ids in r.promo_ids:
doulist=widgets.Box()
_children=[]
c=widgets.Text(description="豆列 ID")
c.value=str(doulist_id)
_children.append(c)
for sid in stories_ids:
c=widgets.Text()
c.value=str(sid)
_children.append(c)
doulist.children=_children
children.append(doulist)
children.append(btn)
doulist_container.children=children
btn.on_click(doulist_func)
display(doulist_container)
doulist_container._dom_classes=('doulist',)
通过在按钮的 on_click 事件上加回调函数的方式,实现点击按钮数据就写回到数据库的功能,以达到实时修改的目的。
由于设置商品用法和设置豆列的思路相同,这里就不展示代码了。
现在有个用户体验的问题,怎么让非技术部门的同事可以非常方便地使用这样的管理后台呢?在上面写逻辑的过程中,我使用了“doulist_container._dom_classes=('doulist',)”,也就是给显示出来的 JN 组件的样式加了额外的 CSS 类名,现在添加一些样式:
> mkdir ~/.jupyter/custom/ > cp chapter12/section5/custom.css ~/.jupyter/custom/
启动 JN:
> jupyter notebook --port=9000 --ip=0.0.0.0
看一下页面效果(http://localhost:9000/notebooks/chapter12/section5/double11.ipynb),如图 12.12 所示。

图 12.12 页面效果
其中第一个 Cell 是使用指南。它是一段 Markdown 类型的 Cell,内容是一段 HTML 代码:
<div class="alert alert-success guide">
<p>使用指南:</p>
<p>1.选择你想要操作的 cell(左边有对应的`In [x]:`的区域),点击;这个区域会出现一个
灰色的边框</p>
<p>2.点击左上的按钮<button class='btn'><i class="icon-play"></i></button>(或者使用快
捷键 ctrl+enter)</p>
<p>3.区域下方会出现一个修改框,会有一些选项,直接在里面输入要修改的数据就好了</p>
<p>4.修改完毕点击提交按钮<button class="btn">提交</button>,按钮会短暂消失(防止重
复按),区域下方会看到执行成功的绿色提示,然后去页面就能看到最新的修改啦</p>
</div>
<div class="alert alert-error">请谨慎操作线上数据!!</div>
再看修改商品和豆列推荐位的效果,如图 12.13 和图 12.14 所示。

图 12.13 修改商品和豆列推荐位的效果 1

图 12.14 修改商品和豆列推荐位的效果 2
通过在 custom.css 中添加样式让后台展示的样式风格和专题页对应区域布局一致,运营人员就不会改错了。
如果想在本地运行这个例子,需要先向 Redis 中插入模拟数据:
> python ~/web_develop/chapter12/section5/init_db.py
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论