Mar 12, 2017 - Tornado & SPA(Single Page Application)

前端领域SPA(Single Page Application)如火如荼,一个SPA也包含了路由的概念和简单的数据处理过程。简单来说就是一个单文件比如index.html,包含了所有的网站内容。它自身的路由使得你可以看到浏览器中url的变化,但实际并没有发生网络请求。因为第一次打开网站时就加载了所有组件。这样在网站内部来回穿梭就如丝般顺滑,是极佳的体验。遇到这样的网站切勿点击刷新。

回到开发上来,前后端分离使得开发相对独立,后端致力于提供API返回数据,后端致力于数据的展现。而在部署时,目前流行的方式似乎是前后端各开一个独立的服务进程,但对一个全栈来说调试起来未免有点复杂。前端再如何进化,最后的产品始终是HMTL,CSS和Javascript的结合,完全可以作为静态文件在浏览器中渲染。所以在后端中加一个路由用来渲染前端生成的文件就可以了。

问题

问题在于,SPA有自己独立的路由,后端也有数据接口的路由,二者很容易搞混。 比如下边的路由设置:

router = [
     (r"/api/(.*)", APIHandler)
     (r"/(.*)", StaticFileHandler)  # StaticFileHandler 是一个渲染静态文件的类
]

其中/api是后端的数据接口,其他是SPA的前端页面。

访问/api/...时会调用后端的数据接口APIHandler,其他情况下调用StaticFileHandler渲染静态文件。而SPA有自己的路由,比如/class/course1。然而当访问这个路由时,会首先被后端拦截并匹配到相应的StaticFileHandler,去寻找class文件夹中的course1文件。这个目录当然是不存在的,于是返回404错误。

理想状态应该是所有非/api开头的路由,都转发到SPA相应的页面中去。注意这里就千万不要在SPA中也设置api路由了,否则永远也转不过去。

Django方案

这篇博客URL Routing for a Decoupled App, with Angular 2 and Django介绍了如何将Django的后端与Angular2的前端无缝结合。

tornado方案

在tornado中,有StaticFileHandler模块可以渲染静态文件。基于这个模块定义一个子类,将所有非js和css文件的路由都重定向到主页面。

class Ag2Handler(StaticFileHandler):
    @gen.coroutine
    def get(self, path, *args, **kwargs):
        if path.strip('/').split('.')[-1] not in ['js', 'css']:
            path = 'index.html'
        yield super(Ag2Handler, self).get(path, *args, **kwargs)

router = [
    (r"/api/(.*)", APIHandler),
    (r"/(.*)", Ag2Handler, {'default_filename': 'index.html', 'path': 'XXXXXX'})
]

访问/class/resume页面时,在后端被最后一个路由r"/(.*)"匹配到。使用Ag2Handler渲染,强行重定向到主页index.html,完工。

Feb 27, 2017 - xaringan定制主题

xaringan是基于remark.js开发的幻灯片写作工具。

  • 优势在于可能更方便的嵌入R语言代码和相应的输出。其实也是可以支持Python,SQL和Bash等语言的,如果你使用RStudio的话。

  • 劣势在于,相对传统的PPT或者Keynote来说,在样式的调整上稍微有点门槛。你需要懂点HTML,CSS和Javascript才能游刃有余。

使用xaringan之后,一些简单的样式调整都可以通过CSS来实现。突发奇想的一个需求是,有没有办法在每一页幻灯片加上一个Logo?

查看一个编译后的幻灯片源码,你会发现每张片子都包含在一个class为remark-slide-container的div元素。而所有的正文都在class为remark-slide-content的div元素里。

那如何自动在所有这些div生成时加入一个DOM元素?这里的元素可以是logo或者任意其他东西,遗憾的是目前似乎没办法。回过头看remark.js的文档,目前并不支持自定义幻灯片的模板(也可能是我没找到)。但我们知道Javascript最是擅长操作DOM元素的增删改,所以可以在编译后的幻灯片里做些手脚,插入一些有趣的东西。

回到xaringan来,它依然是使用rmarkdown这个包来编译的,编译时有includes参数可以在文档的header标签里或者body标签前后插入元素。

output:
  xaringan::moon_reader:
    seal: FALSE
    lib_dir: libs
    css: header.css
    includes:
      after_body: header.js

这里我们使用自定义的header.css文件,并在body标签最后插入一个header.js文件,内容如下:

<script src=jquery.min.js></script>
<script>
(function () {
  $('.remark-slide-content').prepend('<div class="nyc-header" />');
})();
</script>

其中先是引入Jquery模块,接着使用Jquery的能力在所有remark-slide-content标签头部插入一个class为nyc-header的元素。这个元素就用来承载我们的Logo,接下来就全都是CSS的事情了。

header.css文件中定义nyc-header的样式,包括背景色,背景图片和位置等:

.nyc-header{
  background-color: #00a3af;
  background-image: url(logo.png);
  background-position: center center;
  background-repeat: no-repeat;
  background-size: auto;
  width: 100%;
  height: 8%;
  position: absolute;
  left: 0px;
  top: 0px;
}

最后的效果如下图,每页都会有顶层的logo。

Nov 30, 2016 - 爬虫利器

做过一小段时间的爬虫,大部分网站都是直接下载网页或者找到后端的API接口,连Cookie验证的都很少。一旦有验证,虽然也有对应的套路,但相对来说也麻烦的很。

目前小菜鸟所掌握的无非就是以下的套路:

  1. 在Chrome开发者模式下找到数据来源的API,查看请求中的Cookie或者Token

  2. 在所有请求里,追本溯源找这些Cookie或Token是在哪一步返回的

  3. 模拟请求

通常来说都会先上第三步,有错误再走前两步。其中第三步模拟请求的很简单也很无聊,基本就是配置参数:

requests.get(url, headers, coookie, **kargs)

本文介绍的利器就是解放第三步的无聊工作的。利器组合:Chrome + CurlWget + uncurl

Chrome不必多说。CurlWget是一个扩展,也称”Copy as URLs”,一键模拟curl的参数(curl是终端下的浏览器)。uncurl是一个Python包,将curl的命令参数转换成requests的命令。

一个简单的例子,打开Chrome的开发者模式,点开微博首页。查看Network选项卡:

鼠标这么一点,粘贴到终端,一个回车下去就能看到你微博首页的内容了。用uncurl工具很容易就转成requests的命令。

$ uncurl "拷贝进来curl的命令"
requests.get("http://weibo.com/u/xxxxxxxxx/home?topnav=1&wvr=5",
    headers={
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "Accept-Encoding": "gzip, deflate, sdch",
        "Accept-Language": "zh-CN,zh;q=0.8,en;q=0.6,de;q=0.4",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "DNT": "1",
        "Pragma": "no-cache",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36"
    },
   cookies={
        "xxxxxx": "xxxxx",  # 我是不会暴露自己的cookie的...
        "_s_tentry": "login.sina.com.cn",
        "wvr": "6"
    },
)

随意感受一下吧。


2017.2.19: 更新,R语言用户也可以使用curl2r这个小工具把curl的命令转化成R语言的命令。

安装并配置完之后,就可以试试

$ curl2r 拷贝进来curl的命令  # 此处不要用双引号括住!


library(httr)
GET("https://www.baidu.com/",
    add_headers(c(Pragma = "no-cache",
        DNT = "1", `Accept-Encoding` = "gzip, deflate, sdch, br",
        `Accept-Language` = "zh-CN,zh;q=0.8,en;q=0.6,de;q=0.4",
        `Upgrade-Insecure-Requests` = "1",
        `User-Agent` = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
        Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        `Cache-Control` = "no-cache",
        Connection = "keep-alive")),
    set_cookies(XXX))