东东东 陈煜东的博客

作者: 东东东 ( 1 / 19)

分享一个阻止垃圾机器人提交留言表单算法

spam-evil

每个博客主或者网站都会受到各式各样的垃圾留言骚扰。如果网站不及时清理这些垃圾留言,那么整个网站的可读性将会变得越来越糟糕,最后完全被垃圾浏览给占领了。

现在博客使用的是 WordPress,使用的是一个官方的插件 Akismet,这个程序还不错,通过后台大数据分析来识别垃圾评论。

不过除了 Akismet,我还有安装另外一个插件,这个插件很小巧,并且我觉得思路很不错。通过这两个插件来可以过滤很多的垃圾评论。

下面说说这个插件 anti-spam 使用的算法。

这个算法是基于 2 个方法:不可见的 JS 验证码(invisible js-captcha)不可见的输入框陷阱(invisible input trap)

不可见的 JS 验证码 invisible js-captcha

不可见的 JS 验证码(invisible js-captcha)方法基于一个事实:机器人在他们的在程序中不会执行 JavaScript 代码。

所以默认在输出 HTML 评论框的时候,加入了一个隐藏的问题和输入框,问题是今年是公元 xxxx 年。

如果用户访问网站,这个输入框答案会被 JavaScript 自动回答,并且会被 JavaScript 和css 的手段隐藏起来,不让用户看到。

如果机器人去填写答案没有填写正确,那么就会被判定是垃圾信息。

作者实现很鸡贼,其实这里有两个输入框,一个输入框是正确的答案,一个输入框是错误的答案。

<p class="antispam-group antispam-group-q" style="clear: both;">
    <label>Current ye@r <span class="required">*</span></label>
    <input type="hidden" name="antspm-a" class="antispam-control antispam-control-a" value="'.date('Y').'" />
    <input type="text" name="antspm-q" class="antispam-control antispam-control-q" value="'.ANTISPAM_PLUGIN_VERSION.'" autocomplete="off" />
</p>

如果用户没有开启 JavaScirpt ,那么需要在这里情况内容,输入正确的答案。否者在后台就被判断是无效留言。

JS 自动回答答案

// 对表单答案添加答案,答案已经在 antispam-control-a 提供出来了。
elements = document.querySelectorAll('.antispam-control-q');
len = elements.length;
for (i = 0; i < len; i++) { // set answer into other input instead of user
    elements[i].value = answer;
}

...

// 这里利用 JavaScript 添加一个动态的表单
// 如果没有开启
dynamic_control = document.createElement('input');
dynamic_control.setAttribute('type', 'hidden');
dynamic_control.setAttribute('name', 'antspm-d');
dynamic_control.setAttribute('class', 'antispam-control antispam-control-d');
dynamic_control.setAttribute('value', current_year);
...
elements[i].appendChild(dynamic_control);

后台判断逻辑片段

if ( $antspm_q != date('Y') ) { // year-answer is wrong - it is spam
    if ( $antspm_d != date('Y') ) { // extra js-only check: there is no js added input - it is spam
        $spam_flag = true;
        if (empty($antspm_q)) { // empty answer - it is spam
            $antispam_error_message .= 'Error: empty answer. ['.esc_attr( $antspm_q ).']<br> '.$rn;
        } else {
            $antispam_error_message .= 'Error: answer is wrong. ['.esc_attr( $antspm_q ).']<br> '.$rn;
        }
    }
}

如果用户没有启用 JavaScript 会怎么样呢?

用户会在提交表单中看到今天是公元多少年的问题,然后需要将错误的答案修改成正确的答案。这样在上面的后台检测脚本中就会发现,这里填写是正确的。就不用再关心 JavaScript 动态的答案了。

不可见的输入框陷阱 invisible input trap

不可见的输入框陷阱(invisible input trap)是基于一个事实:大多数机器人遇到 email 或者 url 关键字表单会自动填充一些信息。

因此在评论框中增加一个隐藏字段,正常用户是看不到这个字段,所以也不会去填写它。

// 默认不展示给用户看。
<p class="antispam-group antispam-group-e" style="display: none;">
    <label>Leave this field empty</label>
    <input type="text" name="antspm-e-email-url-website" class="antispam-control antispam-control-e" value="" autocomplete="off" />
</p>

但是机器人是可以看到这个字段的,如果在这里填写了任何东西,那么就会被判定为机器人。

后台的判断逻辑片段

// 如果填写了这个表单,那么就被判定是垃圾信息了
if ( ! empty($antspm_e)) { // trap field is not empty - it is spam
    $spam_flag = true;
    $antispam_error_message .= 'Error: field should be empty. ['.esc_attr( $antspm_e ).']<br> '.$rn;
}

总结一下

目前从后台效果来看,这个插件的效果很好,很少遇到垃圾留言。

目前的一些垃圾机器人无法执行 JavaScript,所以很多利用这个特性就可以做一些排除了。很多的第三方评论都是利用 JS 展示出来了。

不过很优秀的 Disqus 在国内经常抽风,甚至不可用

分类: wordpress

docker 容器自定义 hosts 网络访问

在搭建 Drone CI 服务的时候,拉取git代码出现了网络不通。

+ git init
Initialized empty Git repository in /drone/src/testgit.com/root/demo1/.git/
+ git remote add origin http://testgit.com:10080/root/demo1.git
+ git fetch --no-tags origin +refs/heads/master:
fatal: unable to access 'http://testgit.com:10080/root/demo1.git/': Couldn't resolve host 'testgit.com'
exit status 128

也是到 Drone 的论坛问了 http://discourse.drone.io/t/help-git-clone-with-custom-extra-hosts/217。 看来需要自己设置网络问题,没有办法通过 docker-compose.yml 文件来进行设置。

普通的 Docker 容器解决方案

在 docker-compose.yml 中增加 extra_hosts 关键字就可以将数据写入到容器的 /etc/hosts

version: '2'

services:
  drone-server:
    image: drone/drone:latest
    ports:
      - 8100:8000
    extra_hosts:
      - "mygit.com:10.123.123.130"
    volumes:
      - ./drone:/var/lib/drone/
    restart: always
    environment:
      - DRONE_OPEN=true
      - DRONE_DEBUG=true

Drone CI 的情况

Drone CI这个情况比较麻烦。是在Drone agent容器里面又启动了一个容器,这个时候没有关于extra_hosts设置给子容器。于是子容器无法访问特定的域名。

修改 hosts 文件失败

本来想设置添加一个 hosts 文件到镜像中的。但是发现一只失败,无法写入成功。

FROM plugins/git

ADD ./hosts /etc/
RUN cat /etc/hosts

不过这条路走失败了。

hosts文件其实并不是存储在Docker镜像中的,/etc/hosts, /etc/resolv.conf/etc/hostname,是存在/var/lib/docker/containers/(docker_id)目录下,容器启动时是通过mount将这些文件挂载到容器内部的。因此如果在容器中修改这些文件,修改部分不会存在于容器的top layer,而是直接写入这3个文件中。容器重启后修改内容不存在的原因是每次Docker在启动容器的时候,Docker每次创建新容器时,会根据当前docker0下的所有节点的IP信息重新建立hosts文件。也就是说,你的修改会被Docker给自动覆盖掉。

摘录自:https://wongxingjun.github.io/2016/04/06/Docker%E4%BF%AE%E6%94%B9hosts/

问了同事,一般很多容器的做法是,通过将hosts 文件写入容器,然后在容器启动后, cat /data/hosts > /etc/hosts到,然后再执行命令。

我觉得这个方法不适合我,因此我重新寻找新的方法。后面找到了利用 DNS 服务来规避。

搭建 dnsmaq DNS 服务

将需要配置的 hosts 写入到 /etc/hosts 中。

在虚拟机或者特定服务安装 dnsmaq 服务。

yum install dnsmasq -y

touch /etc/dnsmasq.hosts

# 重启
service dnsmasq restart

# 测试一下是否可以查询到域名
dig @127.0.0.1 testdomain.com

使用 Docker 的 DNS 解决

vim /etc/docker/daemon.json

{
    "dns": ["10.123.12.14", "8.8.8.8"]
}

将刚刚的 DNS 服务器 IP 写入到配置中,追加一个字段 dns。如果还有其他的 DNS 服务器,那么就写入在数组中写入其他的 IP。

重启一下 Docker 服务。

service docker restart

Docker 容器测试网络

这个是我的容器
docker run --rm -e DRONE_REMOTE_URL=http://mydomain.com:10080/root/demo1.git plugins/git

或者
docker run busybox nslookup google.com

参考文章

分类: DevOps

website impress 网站评分插件

抽空写了个 Google Chrome的插件。github地址 https://github.com/daoiqi/website-impress

Chrome 插件下载地址: https://chrome.google.com/webstore/detail/fjfddiiaeehjogpabflgicipdgdfljof/

在互联网上经常充斥着一些垃圾网站,网站缺少真实的内容或者带有欺骗性的内容,欺骗用户的感情,影响用户的使用。

** 这类网站应该被揪出来,并且不建议逗留。 **

目前无意做一个点评网站,本插件的数据是利用百度口碑的数据来展示的。

如果有什么好的点评数据,欢迎给我留言。

评分说明

从百度口碑获取好评率。好评率理论上从 0-100% 的。 由于百度口碑的打分最低是一星,所以好评率最低是 20%

数字太大不方便看,因此从0-100的分数映射为 0-10分。

  • 0-59 红色,好评率在0-59%之间,实际最低应该是20%。对应就是一星~三星之间。
  • 60-79 橙色,好评率在60%-79%之间。对应在三星~四星之间。
  • 80-100 绿色,好评率在80%-100%之间。对应在四星和五星。

demo

demo2

demo1

分类: Web 开发

rinetd – Linux tcp port forward

在寻找一个 Linux 的TCP 端口转发程序,大部分都是使用 iptables 来使用的,但我觉得 iptables 不是很好用,主要是不熟悉他的命令。

上网找了一个程序 rinetd,写个配置文件,可以后台运行,很简单。

下载地址:https://centos.pkgs.org/6/nux-misc-x86_64/rinetd-0.62-9.el6.nux.x86_64.rpm.html

教程 https://www.boutell.com/rinetd/

配置很简单vim /etc/rinetd.conf

# config rules


# 设置允许的请求来源
# allow 206.125.69.*

# 配置端口转发信息
# bindaddress bindport connectaddress connectport
0.0.0.0 23 206.125.69.81 23

service rinetd restart 重启一下服务。

分类: Linux 软件

利用 Docker 镜像 gogs 搭建一个私有的 git 服务

本文是为了构建一个私有的持续集成服务而准备的。

利用 Docker 来方便构建,不怕环境错乱,同时也帮忙配置好了一些东西。

Docker 安装准备

需要有一个 Docker 环境, 简单的提及一下 Docker 的事项。

yum install docker
pip install doker-compose

更多参考 https://www.chenyudong.com/archives/docker-practice.html

gogs的搭建是参考https://zhuanlan.zhihu.com/p/24896056

说一下文中的

    links:
        - mysql:mysql

在配置 gogs 的时候,输入数据库地址,需要输入 mysql:3306,被这个给绕晕了。

同步 git 仓库

因为不是提供一个稳定的仓库给大家推送,因为网络的原因,我的CI服务器无法直接访问git仓库,因此单独搞一个git服务,那么需要将代码同步过来。

看到 这篇文章,觉得思路不错。

# 首先先下载过来
git clone --mirror git@github.com:git_user/project.git

# 先拉取一下代码
cd project.git
git fetch -q --all -p

# 推送代码到另外一个服务上
git remote add another_git http://git.com/git_user/project.git


# 周期任务
sudo vi /etc/cron.d/sync_git_repos
*/5 * * * * app cd /path/to/project.git && git fetch -q --all -p && git push another_git --all

接下来配合文档服务和CI服务,进行持续集成。

分类: DevOps

ReadTheDocs 私有部署搭建安装

readthedocs 这个网站还真不错,提供了文档托管服务,让很多开源项目的文档变得很完善。

网上很少有Read The Docs 的一些私有部署文章,希望这篇《Read The Docs 私有部署安装》能帮助到大家,也一起交流交流。

想到有一些私有项目,目前还达不到开源的标准,但内部需要有一个文档服务,之前是利用 Nginx + Sphinx 搭建简单的 HTML 网页来展示的,缺少一些版本的控制,因此想引入 Read The Docs 开源版来进行部署。

很棒的是 Read The Docs(下文会有 RTD 来进行缩写) 提供开源的版本,官方也有一些部署的文档,但是只能说马马虎虎,会遇到一些和想象中不那么美好的事情,这里记录一下,给大家填填坑。

安装

一些步骤参考官方的网页https://docs.readthedocs.io/en/latest/install.html

其中说一个很重要的步骤就是

virtualenv rtd
cd rtd
source bin/activate

因为 Read The Docs 会使用大量的 Python 类库,所以这里建议单独独立出一个环境来,当然也可以用 Docker 版本。

我这就出现了 RTD 的 docker-py 和 系统的 docker-compose 冲突的情况。

  File "/root/readthedocs/readthedocs.org/readthedocs/doc_builder/environments.py", line 16, in <module>
    from docker import Client
ImportError: cannot import name Client

HTTP 网络代理

这里是处理起来耗费了大量的时间的。个人不懂 Django,想打印一些日志也好不难,设置了一些 DEBUG 级别,也没有遇到想要的一些日志。

由于机器没有直接的外网访问权限,因此需要配置 http 代理,这里有说明 简单的 http 代理设置

还有需要拉取 git 代码,git的代码也是需要使用代理的。

git clone http://github.com/sphinx/sphinx
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

网上说是git config --global http.postBuffer 524288000,可以我这一直没有成功,我一直在想我是不是我使用的代理有问题,后来换成一个 Squid 的代理 是没有出现了。

艰辛的调试过程

以前网页一直是已触发构建状态,就是一直没有看到什么失败的信息啊,也不动,心里着急,一直尝试了很久了的,都放弃了。

看到日志,加一些日志都不出现,最后是raise BaseException('eee')二分搜索,才在网页上看到异常信息的。

触发编译文档

利用异常,然后发现了报错的地方,居然都是因为服务要进行一次 HTTP 服务,被设置了代理,无法访问,所以把域名给排除代理了。不过此时的编译是阻塞型的,网页无法快速返回。接下来就是要看看要怎么异步,都已经配置了 Celery 的信息了。终于看到编译过程的真身了,兴奋不已,离成功已经不远了。

构建成功

构建成功

Celery 异步触发构建

一直寻找为什么 Celery 没有异步起作用,直到根据代码一层一层深入 Celery,发现有个很重要的变量CELERY_ALWAYS_EAGER

"""
If the :setting:`CELERY_ALWAYS_EAGER` setting is set, it will
        be replaced by a local :func:`apply` call instead.
"""
app = self._get_app()
if app.conf.CELERY_ALWAYS_EAGER:
    return self.apply(args, kwargs, task_id=task_id or uuid(),
                      link=link, link_error=link_error, **options)


之前就一直注意到EAGER这个配置,上网搜索信息,还以为是语法糖效果呢,可以不用显示的使用task.delay()。现在看注释才明白,CELERY_ALWAYS_EAGER如果为True,那么 Celery 不会使用异步模式,而是在本地上下文采用阻塞执行,所以要使用异步执行,那么需要将这个值设置为CELERY_ALWAYS_EAGER = False

配置完 redis 的信息,然后执行一些命令。

C_FORCE_ROOT=1 ./manage.py celeryd -E --loglevel=DEBUG   # 启动 Celery Worker   
./manage.py celerybeat  --verbosity=3   # 启动 Celery 周期任务事件
./manage.py celerycam                   # celery快照?

运行正常

为成果庆祝一下。

构建成功

构建成功

周期任务

待补充

Docker 编译环境

待补充

相关文章

分类: DevOps

Docker 实践记录

记录一下使用的 Docker 的辛苦过程。

Docker 使用的范围越来越广,我最看重的就是环境隔离,和环境整体打包服务,不用再自己的本地环境上编译。

本文以 CentOS 7 来作为记录过程,其他的发行版类似,就一些系统级别的命令可能不合适。

安装

yum insatll docker

重启

systemctl daemon-reload
systemctl restart docker

本地镜像加速

出现

root@VM_96_130_centos tmp]# docker pull mysql:5.7
Trying to pull repository docker.io/library/mysql ... 
Pulling repository docker.io/library/mysql
Error while pulling image: Get https://index.docker.io/v1/repositories/library/mysql/images: dial tcp 52.207.59.176:443: getsockopt: network is unreachable
Error while pulling image: Get https://index.docker.io/v1/repositories/library/mysql/images: dial tcp 52.207.59.176:443: getsockopt: network is unreachable

我这里使用的是腾讯云,因此我这里的是镜像加速和下载

vim /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/docker-current daemon \
          --exec-opt native.cgroupdriver=systemd \
          --registry-mirror=https://mirror.ccs.tencentyun.com \
          $OPTIONS \
          $DOCKER_STORAGE_OPTIONS \
          $DOCKER_NETWORK_OPTIONS \
          $ADD_REGISTRY \
          $BLOCK_REGISTRY \
          $INSECURE_REGISTRY

网上也有说 /etc/default/docker为啥不起作用的

HTTP 代理

利用 docker build -t xxx . 出现一些错误

Ign http://archive.ubuntu.com trusty InRelease
Ign http://archive.ubuntu.com trusty-updates InRelease
Ign http://archive.ubuntu.com trusty-security InRelease
Err http://archive.ubuntu.com trusty Release.gpg
  Cannot initiate the connection to archive.ubuntu.com:80 (2001:67c:1360:8001::21). - connect (101: Network is unreachable) [IP: 2001:67c:1360:8001::21 80]
Err http://archive.ubuntu.com trusty-updates Release.gpg
  Cannot initiate the connection to archive.ubuntu.com:80 (2001:67c:1360:8001::21). - connect (101: Network is unreachable) [IP: 2001:67c:1360:8001::21 80]
Err http://archive.ubuntu.com trusty-security Release.gpg
  Cannot initiate the connection to archive.ubuntu.com:80 (2001:67c:1360:8001::21). - connect (101: Network is unreachable) [IP: 2001:67c:1360:8001::21 80]

修改 Dockerfile ,增加http代理

FROM ubuntu:14.04
MAINTAINER Kevin Littlejohn <kevin@littlejohn.id.au>, \
    Alex Fraser <alex@vpac-innovations.com.au>

# 这个是可用的
ENV http_proxy=http://xx.xx.xx.xx:6788/
ENV https_proxy=http://xx.xx.xx.xx:6788/


# 网上有这么说的,这个是不能用于 ubuntu的agt-get。
ENV HTTP_PROXY=http://xx.xx.xx.xx:6788/
ENV HTTPS_PROXY=http://xx.xx.xx.xx:6788/

目前没有找到一个说法,http_proxy 是否应该大写。应该是不同的程序读取的环境变量不一样,我看大部分都是小写,最好的方法是都设置。

有一些讨论:http://unix.stackexchange.com/questions/212894/whats-the-right-format-for-the-http-proxy-environment-variable-caps-or-no-ca

常用的命令记录

docker build

# 编写 Dockerfile 文件
docker build -t docker-proxy .

docker images

查看镜像

docker exec

# 进入容器内部
docker exec -it 容器id /bin/bash

docker run

# 打开镜像看看
# 因为有时候直接启动会被退出了。
docker run -i -t ubuntu:14.04 /bin/bash

docker-compose

pip install docker-compose
# 编写 docker-compose.yml 文件
docker-compose up -d 

其他的系统也有依赖 docker 的,出现了

  File "/root/readthedocs/readthedocs.org/readthedocs/doc_builder/environments.py", line 16, in <module>
    from docker import Client
ImportError: cannot import name Client

那么其他使用 virtualenv 来解决单独的部署问题,让默认的系统能够正常使用。

分类: DevOps

git bash 遇到的一些问题

使用了 git windows 客户端遇到的一些问题记录。

$ git --version
git version 2.11.0.windows.1

退格键无法工作

升级了一下git的 Windows 客户端,默认操作都是使用 git-bash.exe 来进行命令行的使用的。但是例如 PyCharm 里面带的 Terminal 用的是 git-bash.bat 这里面的效果。遇到了很尴尬的问题,方向键和退格键都无法使用。

看了这个帖子修改了一下可以使用方向键和退格键了。 http://superuser.com/a/683405

git-bash.bat 开头的一些地方加了下面的一段话。

SET TERM=cygwin

git bash 中文乱码

ls 文件的时候出现

''$'\345\217\221\344\277\241\346\201\257''.txt'

类似的编码,使用一下的东西,好像也没怎么改动。在git-bash.exe 中就中文显示正常了,但是 PyCharm 里面的那个还是有问题。

# disable/enable 8bit input
set input-meta on
#set output-meta on
set output-meta on
set convert-meta off
#set convert-meta off

分类: 实用技巧

一个有趣的微信公众账号 浦发褥积分红包

浦发信用卡有一个在线消费送红包的活动,红包里面有浦发信用卡的积分。一个红包和分享给五个好友,每天最多可以获取好友的15个红包。

面对这个活动,周围的好友都是采用拉微信群,群里面有15个好友,加上自己是16个人。一时间感觉大家好有头脑,褥羊毛的姿势还挺厉害。

期间,我一直想利用微信机器人,利用web微信的协议来点击群里的浦发红包,但是一直需要“在微信的客户端里打开”给折磨了很久,把cookie搞过来了也不管用。还得再研究研究。

后来有人推荐了一个公众账号《爱卡爱羊毛》,我觉得她的思路不错。她只做一个转发平台,有人给他发一个红包链接(可以被5个用户打开),她会给用户发送5个红包链接。理想情况下,只要假设用户稳定,每天分享的和收红包的应该是收支平衡的,因为想要获得红包,必须先分享红包。

什么场景会打破平衡?

用户不断的分享红包,但是忘记抢别人的红包了。这种情况,只是把红包给浪费了,后台应该无法记录红包是否有被打开过,如果根据是否有点击来判断,然后来跳转网址,工作量会比较大一些,但应该还是可以做的。可能还是要结合业务场景。

用户提前将红包分享给了其他朋友,然后又给公众账号发送了一个红包(可能只剩下4个了),但是却获得5个红包。这个时候就会破坏平衡了。面对这个问题,她的做法是增加举报功能,让这个用户被封号。

好想法

我觉得这个公众账号的思路非常棒,相当于一个平台的共享经济。她的这个功能感觉也有复用的空间能力,看看能不能联系上开发人员,看看能否一起开源维护。

分类: 生活

Python 的 json 在 Nuitka 下性能好差

在商业项目中使用了 Nuitka 来进行源码的编译和保护。结果用户反馈使用的 HTTP 请求好慢,数据量一大就超时了。

案发现场

将代码在本地运行,进行 strace 发现好慢。

     0.000136 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 22), ...}) = 0 <0.000008>
     0.000050 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5d83f8000 <0.000019>
     0.000056 write(1, "\r\n", 2)       = 2 <0.000017>
     0.000048 write(1, "\r\n", 2)       = 2 <0.000008>
     0.000042 write(1, "\n", 1)         = 1 <0.000007>
     3.033367 brk(0x2a08000)            = 0x2a08000 <0.000019>
     1.743621 brk(0x2a41000)            = 0x2a41000 <0.000019>
     2.166131 brk(0x2a86000)            = 0x2a86000 <0.000021>
     2.687461 brk(0x2aaf000)            = 0x2aaf000 <0.000017>
     0.266587 brk(0x2ae3000)            = 0x2ae3000 <0.000165>
     4.485236 brk(0x2b3a000)            = 0x2b3a000 <0.000129>
     1.482029 mmap(NULL, 344064, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa596451000 <0.000331>
     1.977664 mremap(0x7fa596451000, 344064, 389120, MREMAP_MAYMOVE) = 0x7fa5963f2000 <0.000284>
     0.781643 munmap(0x7fa5963f2000, 389120) = 0 <0.000252>
     0.053389 mmap(NULL, 1929216, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5962ce000 <0.000183>

一次请求开销 20 秒,发现 brk 之间的耗时有个 14 秒,加上下面的 mmapmremap 开销 2.8 秒。但是在纯 Python 文件下执行只需要 1 秒以内,非常的纳闷。

在使用 Nuitka 是将 Python 文件转化为 so 文件,而不是全部打成一个 so 文件,因此采用了 py 文件替换来进行调试。整个路径都用 py 文件来运行了,当然依赖的一些类库使用的 so 的形式,并没有什么进展。

没办法只能使用 pdb 来进行一行一行的跟踪了。临时写了一个文件做为入口。

python -m pdb test.py

进入后,使用

n 执行这行命令
s 进入函数内部
l 查看上下文的代码

一步一步的执行下后,发现在

class CJsonEncoder(json.JSONEncoder):
    """
        to solve json.dumps datetime.datetime TypeError.
        TypeError: datetime.datetime(2014, 07, 12, 08, 56, 23) is not JSON serializable

        Use:
            json.dumps(data_obj, cls=CJsonEncoder)
    """
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(obj, datetime.date):
            return obj.strftime('%Y-%m-%d')
        elif isinstance(obj, datetime.time):
            return obj.strftime('%H:%M:%S')
        else:
            return json.JSONEncoder.default(self, obj)

...

result = {
    'response':{
        'header':{
            'version':'1.0',
            'returnCode':self._code,
            'errorInfo':msg
            },
        'data':data
    }}
result_str = json.dumps(result, cls=CJsonEncoder)

CJsonEncoder 耗时 4 秒

一个令人惊讶的数值,对比了数据, 一个是 json.dumps(result, cls=CJsonEncoder) 一个是 json.dumps(result) ,耗时分别是 20 秒和 16 秒,光在这个自定义的json解析上居然花费 4 秒的时间。不可思议。

简直无可忍受,感觉这个 Nuitka 解决方案不行呀。

使用 simplejson 类库替换

刚好最近一直在做 json 相关类库的工作,之前也是测试过 Python 自带的 json 类库,性能也确实差,可能和我的 Python 版本有关系,还在使用 Python 2.6 的版本,吐血。

现有的 simplejson,后来 Python 将 simplejson 纳入标准类库了,但是由于 Python 分发出去,就无法更新标准类库了, 但是 simplejson 可以不断的独立更新,所以维护的更好一些,很多的很多的程序也都是用它。

看到 http://stackoverflow.com/a/17823905 上有说 simplejson 看是否有加速过,可以用这个

import simplejson
# If this is True, then c speedups are enabled.
print bool(getattr(simplejson, '_speedups', False))

如果返回 True ,那么就有使用特殊的加速过。所以性能更好一些。

具体替换实现

考虑到 simplejson 是符合 Python 的标准类库的接口,所以可以无缝兼容在使用。在使用了 simplejson 后,这个耗时回到了 1 秒的时间。

def patch_json():
    """
    using simplejson replace json
    """
    import json  # noqa
    import simplejson  # noqa
    sys.modules['json'] = sys.modules['simplejson']

在最开始的地方执行函数,替换掉标准的 json 的类库,让开发人员使用起来无感知,但是要注意做好兼容性测试。也要把这类隐形的替换在开发规范中声明好。

为什么使用了 Nuitka 变慢了

这个是我无法回答的了。因为 Nuitka 将 py 变成了 c++ 语言,再编译成 so 文件。按理不应该这么慢的。首先 strace 进入看,只有一些分配内存的情况系统调用。感觉这边在大量的一些 CPU 操作,不知道是不是 Nuitka 对这个优化不好,或者是老的 json 代码不行。

另外还发现,在 strace 中,一些查找 python 模块也还很耗时。

还有由于我的 Nuitka 解决方案是单个 py 直接变成单个 so,然后 py 和 so 混合使用的。不知道这边的切换会不会导致一些耗时的开销。

分类: Python

较早的文章

Copyright © 2017 东东东 陈煜东的博客 粤ICP备13059639号-1

SITEMAP回到顶部 ↑