Redis 介绍

Redis (Remote Dictionary Server) 是由 Salvatore Sanfilippo(antirez) 开发的开源数据库,基于内存的 Key-Value 类型的 NoSQL 。目前在 DB Engines Ranking K-V 数据库中排行第一 1

Redis 是 REmote DIctionary Server 远程字典服务的缩写,他以字典结构存储数据,并允许其他应用通过 TCP 协议来读写字典中的内容。

Redis 支持很多的特性:

  • 所有数据存放在内存中
  • 支持数据持久化:AOF 和 RDB 两种类型
  • 支持异步数据复制

Redis Cluster 常用 5 种数据结构 (String, Lists, Sets, Sorted Set, Hash) 以单进程方式处理请求,数据持久化和网络 Socket IO 等工作是异步进程。

安装

源中安装

在 Debian/Ubuntu/Linux Mint 下直接安装即可,但是 redis 对内核有要求,如果安装失败的时候, -uname -a 看一下自己的内核,如果版本太低就升级一下。

sudo apt-get install redis-server

安装成功之后就可以使用

sudo service redis-server status # 查看当前状态
sudo service redis-server stop/start # 等等来控制 redis-server 的状态

最方便最快捷的安装方式,如果使用 docker 也可以使用 docker 中官方的源。

手动安装

官网下载 https://redis.io/download

下载最新的稳定版 Redis,可以从 http://download.redis.io/redis-stable.tar.gz 获取最新稳定版

curl -O http://download.redis.io/redis-stable.tar.gz

解压 tag.gz

tar xzvf redis-stable.tar.gz

进入解压后目录

cd redis-stable

编译和安装,运行 make 命令

make

当二进制文件编译完成之后,运行 test 确保一切都正确

make test

当所有测试跑通过之后安装到系统

sudo make install

运行 test 的时候报了一个错误:

*** [err]: Test replication partial resync: ok psync (diskless: yes, reconnect: 1) in tests/integration/replication-psync.tcl

参考该 issue 使用单核运行 test

taskset -c 1 sudo make test

配置 Redis

在 etc 目录下新建 redis 配置文件目录

sudo mkdir /etc/redis

将默认配置文件拷贝到配置目录

sudo cp redis.conf /etc/redis

编辑配置文件

sudo vim /etc/redis/redis.conf

修改 supervised 为 systemd

# If you run Redis from upstart or systemd, Redis can interact with your
# supervision tree. Options:
#   supervised no      - no supervision interaction
#   supervised upstart - signal upstart by putting Redis into SIGSTOP mode
#   supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
#   supervised auto    - detect upstart or systemd method based on
#                        UPSTART_JOB or NOTIFY_SOCKET environment variables
# Note: these supervision methods only signal "process is ready."
#       They do not enable continuous liveness pings back to your supervisor.
supervised systemd

接下来,寻找 dir 配置, 该参数制定 Redis 存储数据的目录,需要一个 Redis 有写权限的位置,使用 /var/lib/redis.

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir /var/lib/redis

修改完毕,保存关闭。

创建 systemd unit

创建 redis.service 文件

sudo vim /etc/systemd/system/redis.service

[Unit] 单元中提供描述,和启动需要在网络可用之后。[Service] 中定义服务的具体动作,自定义用户 redis,以及 redis-server 的地址。

[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
User=redis
Group=redis
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/bin/redis-cli shutdown
Restart=always

[Install]
WantedBy=multi-user.target

创建 redis 用户,组

创建用户,组

sudo adduser --system --group --no-create-home redis

创建文件夹

sudo mkdir /var/lib/redis

给予权限

sudo chown redis:redis /var/lib/redis

修改权限,普通用户无法访问

sudo chmod 770 /var/lib/redis

运行 Redis

启动

sudo systemctl start redis

查看状态

sudo systemctl status redis

显示

sudo service redis status
● redis.service - Redis In-Memory Data Store
   Loaded: loaded (/etc/systemd/system/redis.service; disabled; vendor preset: enabled)
   Active: active (running) since Sat 2017-04-22 18:59:56 CST; 2s ago
 Main PID: 28750 (redis-server)
   CGroup: /system.slice/redis.service
           └─28750 /usr/local/bin/redis-server 127.0.0.1:6379

Apr 22 18:59:56 ev redis-server[28750]:   `-._    `-._`-.__.-'_.-'    _.-'
Apr 22 18:59:56 ev redis-server[28750]:       `-._    `-.__.-'    _.-'
Apr 22 18:59:56 ev redis-server[28750]:           `-._        _.-'
Apr 22 18:59:56 ev redis-server[28750]:               `-.__.-'
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower valu
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 # Server started, Redis version 3.2.8
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.ov
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 * DB loaded from disk: 0.000 seconds
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 * The server is now ready to accept connections on port 6379

使用 redis-cli 客户端测试。

redis-cli

然后运行 ping ,会得到 PONG。

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set test "It's working"
OK
127.0.0.1:6379> get test
"It's working"
127.0.0.1:6379> exit

然后重启 redis

sudo systemctl restart redis.service

然后进入 redis-cli:

127.0.0.1:6379> get test
"It's working"

如果能够获得,就证明配置好了。

开机启动

sudo systemctl enable redis

在启动了 redis 之后就可以再熟悉一下他的命令 了。

多数据库支持

Redis 实例提供了多个用来存储数据库的字典,客户端可以用来指定将数据存储在哪个数据库中,类似关系型数据库可以新建很多个数据库,可以将 Redis 的每一个字典都理解成为一个数据库。

每个数据库对外都是以一个从 0 开始的递增数字命名, Redis 默认支持 16 个数据库。 客户端与 Redis 建立连接之后会自动选择 0 号数据库,不过随时可以使用 SELECT 命令来更换数据库,比如选择 1 号数据库 SELECT 1.

注意:Redis 不支持自定义数据库名,每个数据库都以编号命名;Redis 也不支持为每一个数据库设置不同的访问密码;多个数据库之间并不是完全隔离, FLUSHALL 命令可以清空 Redis 实例中所有数据库数据。

reference

参考: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-redis-on-ubuntu-16-04


2017-04-20 redis , database , nosql , 学习笔记

每天学习一个命令:df 查看磁盘剩余空间

之前也介绍过 di disk information,不过系统默认不带,需要自己安装,如果遇到没有权限安装时,就可以使用 df 来查看当前机器剩余磁盘空间。

df 全称 disk filesystem,用于显示 Linux 系统磁盘利用率,通常也用来查看磁盘占用空间。

命令格式

df [OPTIONS] [FILE]

直接使用不加任何参数会显示所有当前被挂载的文件系统的可用空间。默认会以 1KB 为单位显示。

选项:

-a      全部文件系统列表
-h      方便阅读方式显示
-H      等于“-h”,但是计算式,1K=1000,而不是 1K=1024
-i      显示 inode 信息
-k      区块为 1024 字节
-l      只显示本地文件系统
-m      区块为 1048576 字节
--no-sync 忽略 sync 命令
-P      输出格式为 POSIX
--sync  在取得磁盘信息前,先执行 sync 命令
-T      展示文件系统类型,比如 ext4, tmpfs, 等等

使用实例

直接使用

直接使用 df ,显示设备名称、总块数、总磁盘空间、已用磁盘空间、可用磁盘空间和文件系统上的挂载点。

Filesystem     1K-blocks      Used Available Use% Mounted on
udev             8126360         0   8126360   0% /dev
tmpfs            1629376     75432   1553944   5% /run
/dev/sdb1      240230912 185617700  42387064  82% /
tmpfs            8146864    546884   7599980   7% /dev/shm
tmpfs               5120         4      5116   1% /run/lock
tmpfs            8146864         0   8146864   0% /sys/fs/cgroup
/dev/loop1         83712     83712         0 100% /snap/core/4206
/dev/loop4        259584    259584         0 100% /snap/electronic-wechat/7
cgmfs                100         0       100   0% /run/cgmanager/fs
tmpfs            1629376        72   1629304   1% /run/user/1000
/dev/sda3      723180576     70584 686351464   1% /media/user/add8bd89-da2a-4573-ac6e-7ec44f8c5414
/dev/loop5         84096     84096         0 100% /snap/core/4327
/dev/loop3         95872     95872         0 100% /snap/slack/6
/dev/loop6         88704     88704         0 100% /snap/core/4407

df 命令输出:

  • 第一列是文件系统对应的设备文件路径名,一般是硬盘分区名
  • 第二列是分区包含的数据块数目
  • 第三、四列分别表示已用和可用的数据块数目,三、四列块数之和不等于第二列块数,缺省每个分区都会预留少量空间给系统管理员使用
  • 第五列,Use% 表示普通用户空间占用百分比
  • 最后一列,表示文件系统挂载点

优化输出,以更加易读的方式输出结果

df -h 可以显示比较友好的输出

Filesystem      Size  Used Avail Use% Mounted on
udev            7.8G     0  7.8G   0% /dev
tmpfs           1.6G   74M  1.5G   5% /run
/dev/sdb1       230G  178G   41G  82% /
tmpfs           7.8G  534M  7.3G   7% /dev/shm
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           7.8G     0  7.8G   0% /sys/fs/cgroup
/dev/loop1       82M   82M     0 100% /snap/core/4206
/dev/loop4      254M  254M     0 100% /snap/electronic-wechat/7
cgmfs           100K     0  100K   0% /run/cgmanager/fs
tmpfs           1.6G   72K  1.6G   1% /run/user/1000
/dev/sda3       690G   69M  655G   1% /media/mi/add8bd89-da2a-4573-ac6e-7ec44f8c5414
/dev/loop5       83M   83M     0 100% /snap/core/4327
/dev/loop3       94M   94M     0 100% /snap/slack/6
/dev/loop6       87M   87M     0 100% /snap/core/4407

df -hT 其中 -T 参数显示文件类型 ext4 等等

Filesystem     Type      Size  Used Avail Use% Mounted on
udev           devtmpfs  7.8G     0  7.8G   0% /dev
tmpfs          tmpfs     1.6G   74M  1.5G   5% /run
/dev/sdb1      ext4      230G  178G   41G  82% /

说明:

  • -h 目前磁盘空间和使用情况 以更易读的方式显示
  • -H 和上面的 -h 参数相同,换算时采用 1000 而不是 1024 进行容量转换
  • -k 以单位 1K 显示磁盘的使用情况

用 inode 方式显示磁盘使用情况

使用 -i 参数

df -i

输出结果为

Filesystem       Inodes   IUsed    IFree IUse% Mounted on
udev            2031887     539  2031348    1% /dev
tmpfs           2036709     941  2035768    1% /run
/dev/sdb1      15269888 1896147 13373741   13% /
tmpfs           2036709     497  2036212    1% /dev/shm
tmpfs           2036709       4  2036705    1% /run/lock
tmpfs           2036709      18  2036691    1% /sys/fs/cgroup
/dev/loop0        28782   28782        0  100% /snap/electronic-wechat/7
cgmfs           2036709      14  2036695    1% /run/cgmanager/fs
tmpfs           2036709     132  2036577    1% /run/user/1000
/dev/loop8        12810   12810        0  100% /snap/core/6034
/dev/sda1      14082048 1673302 12408746   12% /media/mi/3d1b7e3e-c184-4664-9555-2b088997f2c8
/dev/sda3      45932544      11 45932533    1% /media/mi/8803a3c6-1561-4957-b9b3-e60d5688d1a6
/dev/sdc       12229708      20 12229688    1% /media/mi/data

inode (index node) 是一个在类 Unix 文件系统下用来描述文件系统对象(文件或者目录)的数据结构。每一个 indoe 保存对象数据的属性和磁盘块地址。文件类型对象属性包括 metadata(修改时间,访问属性等)和文件的所有者以及文件权限。

df -ih 显示 inodes

Filesystem     Inodes IUsed IFree IUse% Mounted on
udev             2.0M   520  2.0M    1% /dev
tmpfs            2.0M   888  2.0M    1% /run
/dev/sdb1         15M  1.8M   13M   12% /

输出磁盘文件系统

常见的文件系统有 Windows 下的 FAT32,NTFS,Unix 系统下的 ext3, ext4,添加 -T 参数在输出结果中增加一列来表示当前分区的文件系统。

df -T

而如果要在结果中筛选特定文件系统的分区可以使用 -t ext4 ,比如要过滤出只显示 ext4 分区

df -t ext4

相关

查看磁盘占用 du

外延

假如你的 250G 的系统盘即将存满,下面的方式可以缓解一下硬盘压力。

移除不再使用的 package

sudo apt autoremove
sudo apt-get autoclean

查看系统日志占用:

journalctl --disk-usage
sudo journalctl --vacuum-time=3d

查看 SNAP 占用

du -h /var/lib/snapd/snaps
snap list --all
snap remove some-package

reference

  • man df

2017-04-12 linux , df , disk , 磁盘空间 , command

Celery 使用介绍

Celery 简单来说就是一个分布式[[消息队列]]。简单、灵活且可靠,能够处理大量消息,它是一个专注于实时处理的任务队列,同时也支持异步任务调度。Celery 不仅可以单机运行,也能够同时在多台机器上运行,甚至可以跨数据中心。

Celery 中比较关键的概念:

  • worker: worker 是一个独立的进程,任务执行单元,它持续监视队列中是否有需要处理的任务;
  • broker: broker 消息传输中间件,任务调度队列,接收生产者发出的消息,将任务存入队列,broker 负责协调客户端和 worker 的沟通。客户端向队列添加消息,broker 负责把消息派发给 worker。
  • 任务模块:包含异步任务和定时任务,异步任务通常在业务逻辑中被触发并发往任务队列,定时任务由 celery beat 进程周期性发送
  • 任务结果 backend:backend 存储任务执行结果,同消息中间件一样,需要由其他存储系统提供支持

一个典型的 Celery 使用场景就是,当用户在网站注册时,请求可以立即返回而不用等待发送注册激活邮件之后返回,网站可以将发送邮件这样的耗时不影响主要流程的操作放到消息队列中,Celery 就提供了这样的便捷。

安装 Celery

直接使用 python 工具 pip 或者 easy_install 来安装:

$ pip install celery

安装 Broker

Celery 支持多种 broker, 但主要以 RabbitMQ 和 Redis 为主,其他都是试验性的,虽然也可以使用, 但是没有专门的维护者。如何在 RabbitMQ 和 Redis 之间选择呢?

RabbitMQ is feature-complete, stable, durable and easy to install. It’s an excellent choice for a production environment.

Redis is also feature-complete, but is more susceptible to data loss in the event of abrupt termination or power failures.

Celery 官方明确表示推荐在生产环境下使用 RabbitMQ,Redis 存在丢数据的问题。所以如果你的业务可以容忍 worker crash 或者电源故障导致的任务丢失,采用 redis 是个不错的选择,本篇就以 redis 为例来介绍。

Celery 对于 redis 的支持需要安装相关的依赖,以下命令可以同时安装 celery 和 redis 相关的依赖,但是 redis server 还是必须单独安装的。

$ pip install -U celery[redis]  # -U 的意思是把所有指定的包都升级到最新的版本

Celery 的配置和使用

Celery 本身的配置项是很多的,但是如果要让它跑起来,你只需要加一行配置:

BROKER_URL = 'redis://localhost:6379/0'

这一行就是告诉 celery broker 的地址和选择的 redis db,默认是 0。接下来用个很简单的例子来介绍 celery 是如何使用的:

# task.py
from celery import Celery

broker = 'redis://127.0.0.1:6379/0'

app = Celery('tasks', broker=broker)

@app.task()
def add(x, y):
   return x + y

上述代码创建了一个 celery 的实例 app,可以通过它来创建任务和管理 workers。在上面的例子中,我们创建了一个简单的任务 task,它返回了两个数相加后的结果。然后启动 celery 服务,通过它来监听是否有任务要处理。

$ celery worker -A task -l info
  • -A 选项指定 celery 实例 app 的位置,本例中 task.py 中自动寻找,当然可以直接指定 celery worker -A task.app -l info
  • -l 选项指定日志级别, -l--loglevel 的缩略形式

其他更多选项通过 celery worker –-help 查看

调用任务或者发送消息

然后我们再打开一个 shell 窗口,进入 python 控制台去调用 add 任务:

>>> from task import add
>>> add.delay(1, 2)
<AsyncResult: 42ade14e-c7ed-4b8d-894c-1ca1ec7ca192>

delay()apply_async 的简写,用于一个任务消息(task message)。我们发现 add 任务并没有返回结果 3,而是一个对象 AsyncResult,它的作用是被用来检查任务状态,等待任务执行完毕或获取任务结果,如果任务失败,它会返回异常信息或者调用栈。

我们先尝试获取任务的执行结果:

>>> result = add.delay(1, 2)
>>> result.get()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/celery/result.py", line 169, in get
    no_ack=no_ack,
  File "/usr/local/lib/python2.7/dist-packages/celery/backends/base.py", line 604, in _is_disabled
    'No result backend configured.  '
NotImplementedError: No result backend configured.  Please see the documentation for more information.

报错了:No result backend configured. 错误信息告诉我们没有配置 result backend。因为 celery 会将任务的 状态或结果保存在 result backend,result backend 的选择也有很多,本例中依然选用 redis 作为 result backend。

我们修改 task.py 的代码,添加上 result backend 的设置,保存后重启 celery worker。

# task.py
...
app = Celery('tasks', backend='redis://localhost', broker='redis://localhost//')
...

然后重新调用 add task:

>>> from task import add
>>> result = add.delay(1,2)
>>> result.get()
3

Celery Flower

flower 是一个 celery 的监控工具,它提供了一个图形用户界面,可以极大的方便我们监控任务的执行过程, 执行细节及历史记录,还提供了统计功能。

flower 安装

pip install flower

or:

easy_install flower

flower 使用简介,首先启动通过命令行启动 flower 进程:

flower -A proj --port=5555

然后打开浏览器 http://localhost:5555/

celery flower

Celery 任务类型

apply_async

调用一个异步任务,这也是最常用的任务类型之一,delay 与它的作用相同,只是 delay 不支持 apply_async 中额外的参数。该方法有几个比较重要的参数,在实际应用中会经常用到:

countdown: 任务延迟执行的秒数,默认立即执行; eta:任务被执行的绝对时间

crontab 计划任务

Celery 同样也支持定时任务:

from datetime import timedelta
from celery.schedules import crontab

app.conf.beat_schedule = {
    # Executes every Monday morning at 7:30 A.M
    'add-every-monday-morning': {
        'task': 'tasks.add',
        'schedule': crontab(hour=7, minute=30, day_of_week=1),
        'args': (20, 20),
    },
    # execute every ten minutes
   'every_ten_minutes': {
        'task': 'worker.cntv.cntv_test',
        'schedule': timedelta(minutes=10),
        'args': ('args1',),
        'options': {
            'queue': 'queue_name'
        }
    },
}

要启动定时任务,需要启动一个心跳进程,假设

celery beat -A celery_app.celery_config -s /path/to/celerybeat-schedule -l info

其中 -s 参数指定 celerybeat 文件保存的位置。beat 主要的功能就是将 task 下发到 broker 中,让 worker 去消费。

取消队列中任务

取消队列中任务,可以使用命令行,也可以导入 celery app 然后使用 control()

celery -A proj -Q queue_name purge      # 取消队列 queue_name 中的任务
# or
from proj.celery import app
app.control.purge()

From:stackoverflow

celery 在 supervisor 中 root 不能启动问题

Celery 不能用 root 用户启动,所以在 supervisor 中启动时会报错:

If you really want to continue then you have to set the C_FORCE_ROOT
environment variable (but please think about this before you do).

User information: uid=0 euid=0 gid=0 egid=0

解决办法

from celery import Celery, platforms
platforms.C_FORCE_ROOT = True

或者 supervisor 配置中

environment=C_FORCE_ROOT="true"

reference


2017-04-10 celery , python , queue , task , distribution

电影网站评分机制

年前的时候喉舌媒体批评豆瓣,猫眼等评分太低影响了票房,而导致 16 年的年度票房目标没有达到,广电很生气,后果很严重。可是豆瓣存在了那么多年,那么多的电影,在院线上映的,还是不上映的,从来也没有听说过 IMDB 或者 烂番茄的评分会影响到总体的票房。虽然得分的多少或多或少的会对票房有所影响,可这难道是豆瓣,或者 IMDB 或 烂番茄这样的影评网站应该承担的责任吗? 制片公司,发行商,甚至细化到导演,演员,剧本,在国内甚至可以拉上审查来负责,动不动删掉个 14 分钟,谁还愿意花了冤枉钱去大荧幕看一个不完整的片子呢?真正的影迷 大概会愿意花个机票钱去看一个完整版吧。

当然也不想过多的吐槽,或许被“认证”也才能证明豆瓣的评分也算良心吧。这里就看看国内玩几家影评站对网站打分的计分规则。其实早在很早就将计算的公式记录在了记事本里面,一直没有整理。而现在想要来整理一下,也是感觉豆瓣评分在一定程度上没有想象的真实,看过一部被恶意差评的国产片,看后感觉并不是 5 分多的水平,后来看评论才知道其中的某一位演员的黑粉恶意差评才导致这样的结果,而看一些长评论确实客观很多。或许是差评的人,没那么多的时间来写长评吧。所以就像那篇评论中说的那样,“中国电影市场的正常发展,不仅需要好的导演,好的编剧,好的演员,还需要好的观众”。

BGM,找资料时偶得,为某一期奥斯卡缅怀逝去的人时的背景音乐

豆瓣

先来说一说我使用最多的豆瓣,豆瓣也是评分规则中最简单的,豆瓣不人工干预评分,而一部电影的最终得分就是由每个用户的打分的加权平均,举个例子,一个用户打 5 星,一个用户打 3 星,一个用户打 1 星,那么这部片子就是(5+3+1)/3 也就是 3 星,6 分。

豆瓣最后得分的具体公式 1

\[\frac{x_1*1+x_2*2+x_3*3+x_4*4+x_5*5}{x_1+x_2+x_3+x_4+x_5}*2\]

其中, $x_1$ 表示打 1 颗星的人数,$x_2$ 表示打 2 颗星的人数,以此类推。由该公式能够看出,豆瓣的评分是很简单的计算,而至少一颗星(2 分)的最低评分,也无形中提高了影片的评分,因为豆瓣根本不存在 0 分的电影,哦,不,还是有的。其实,豆瓣一直是一个满分 8 分的机制,那些超过 8 分的电影,是一定不会差的。所以曾经有段时间,找不到片子看的时候就直接找 8 分以上的片子看。

screenshot-area-2017-04-08-154156

豆瓣的评分机制简单粗暴,在降低用户打分思考的时候,也会造成用户对一部影片的看法截然不同,尤其是在恶意刷分时,会导致最后的评分波动较大。曾经有人开过玩笑说过豆瓣的评分图案,r 型(5 星占大多数)的为口碑爆棚的好片,P 型为普通好片,b 型为普通烂片,而 C 型是水军刷出来的烂片,还有 L 型是多少水军都刷不出来的超级烂片。现在想来还是依然非常好玩。

时光网

时光网的存在感近两年被慢慢的抹去,但还依然半死不活的存在,时光网和豆瓣的评分机制一样,都是加权平均,只是时光网采用的是 10 分制,也就是用户有 10 个选择,用户需要话时间在评分的分数上,更多的选择,使得绝大部分用户选择中间段进行评分,因而导致最终的评分呈现中庸状态,同样无法真正体现出一部电影的真正得分。

而这样的十分制同样会导致在遭受大规模恶意打分(无论是好评还是差评)之后直接在最终结果中明显体现。

IMDB

IMDB 是国外最大的电影资料站,大家经常提到的 IMDB TOP 250,也就是在该站上评分最高的 250 名。他采用贝叶斯算法,具体的公式 2

\[WR=\frac{v}{v+m}*R+\frac{m}{v+m}*C\]

其中:

  • WR,加权得分 weighted rating
  • R,本影片的平均得分 rating
  • v,评分人数 votes
  • m,基准票数,进入 IMDB Top 250 的最小票数
  • C,站点所有电影的平均分

这个公式的目的是为了让得分更加偏向于平均分,如果投票越多,评分就越接近真实的平均分,否则就越接近所有电影的平均分。而这个公式的唯一人为设定的参数就是基准票数。而这个参数的设定也正是为了解决如何让冷门和热门影片在得分上具有可比性。冷门片不会因为爱好者而导致评分异常高,这个问题也是豆瓣经常遇到的问题,一些冷门韩综,日剧,韩剧在评分上都有一定的偏高。

而关于 IMDB 这个公式是怎么防止恶意刷分,有兴趣可以了解一下当年《蝙蝠侠》和《教父》的往事:

烂番茄

烂番茄主要是专业影评人士评价汇总,和 IMDB 和 豆瓣这样单纯由网名进行投票的评分制度有些不同。而烂番茄通过新鲜度来对电影进行评价,而这里的新鲜度并不是实际意义上的评分,而是由影评人对该影片正面打分的比例来决定的,若正面的评价超过 60% 以上,该部作品将会被认为是“新鲜”(fresh)。如果正面评价超过 75 % ,那么该作品会得到“Certified Fresh” 的评价,而如果一部作品的正面评价低于 60%,那么该作品会被标示为“腐烂”(rotten)。影评人只有两个选项,正面和反面。

烂番茄和其他影评网站的最大区别是,他突出的是人群对一部电影持有的主流观点,而不是一个让每个人都感同身受的数值。

Metacritic

Metacritic 是一个综合性评定网站,影评只是该网站其中的一个小模块,该网站上影评人多以纸媒为主。 Metacritic 的评分主要从主流媒体和专业影评机构聚合而来,这些影评人和其供职的机构大多在影评方面具有公信力,比如《卫报》、《纽约时报》、《时代周刊》等等。但是并不是每一个机构和影评人都给出一个确切的分数。 Metacritic 具体做法是,如果来源有具体评分则使用来源评分,来源有星级打分则换算成百分制,如果来源影评只提供文字,然后他们自己去找人阅读影评,根据读完的感受给分。3

比较

各家网站都有各家的好坏,豆瓣的评分机制是最简单高效的,这也是绝大多数的系统惯常的做法。但正是这样的机制使得刷分异常容易,大批量的差评或者好评能在短时间内影响影片总体的分数。另外一个比较严重的问题就是,无法在冷门片和热门片之间比较,这也是豆瓣官方博客在文章中提及的,热门影片能在短时间内获得几十万的评分,但是一些冷门片,或者一些上映时间比较久远的电影可能难以达到这么多的评分,这样就会导致热门片和冷门片在评分上无法比较。口碑比较好的热门片可能因为观众口味不一而导致评分稍中庸,而冷门片可能因为资深影迷而导致评分过高。因此在豆瓣看评分时,一般还需要看一下评分人数。而最近我也会看一下长评论,毕竟愿意花时间来评价一部影片,远比花 1 秒打个评分要来的认真。

而 IMDB 的评分方式一定程度上解决了冷门影片和热门影片评分上的差异,但是选择基准票数却也需要经过不断的调整,IMDB 历史上也经历过变化,根据该数据,阈值从 3000 票提升到了 25000 票,这次变换也相应的造成了最后得分的变化,尤其是影响了得到 25000 票以下,并且得分较高的影片。可以说只有当影片的评分人数足够多时,基准票数的影响才会减至最小,而对于票数比较少的影片,就相当于一次洗牌。

而对于烂番茄和国内的猫眼专家评分,其实一定意义上说代表着专业领域的人士意见,这些评论都值得一读,但是更多的需要自己的看法,只有最后形成自己的世界观那部分东西才真正属于自己。所以豆瓣和 IMDB 对于我来说,一方面提供给我足够的信息,包括导演,演员,编剧等等,另一方面也是让我远离烂片,毕竟看一部烂片浪费的是自己的时间。

最后,引用数位时代中的一句话,“在美国,佳片会收到它应得的票房和好评作为奖赏,烂片就算进了电影院也不可能躲得开差评 —- 无论在报纸、电台还是在网络上。在美国,对电影的批评,也是言论自由保护的一部分”。

若差评不自由,则高分无意义。

乱想

在我的 Trello 中 2018 年 12 月份的时候,写下了一个问句,“豆瓣评分的权威性”,我想可能是当时脑袋里突然想起来的一个疑问,经过上面这么多的讨论,应该都知道豆瓣评分的计算方式了,虽然这种方式有其优势 —- 简单,但也有其劣势 —- 容易被恶意操作。然而这么多年过去了豆瓣还是依然是那个豆瓣,也越来越多的媒体引用豆瓣的评分来做宣传,那么豆瓣评分的权威性来自哪里?我想了很久,难道正是因为一人一票这种最简单的模式?豆瓣的评分严格来说只有打分的人达到一定的基数后其评分才真正有参考价值,也只有一部影片被放到公共领域被讨论,最后众人给出的评分才是能代表绝大多数的分数。这也就是豆瓣评分权威性的由来,当你参与一个影片的[[公共讨论]]成为其一部分的时候,最后的结果才是能被大多数人认可的。并且你参与的过程有且只有一次,你的打分权重不会比别人高,也不会比别人低,人人平等。那么最后的那个分数就是权威的。(当然这里讨论的豆瓣评分已经说的很清楚了,不包括哪些小众的内容)


2017-04-08 Movie , Douban , IMDB

每天学习一个命令:lsof 列出打开的文件

lsof 用于列出当前系统打开的文件 (list open files),在 Linux 中,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以比如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为 lsof 命令需要访问核心内存和各种文件,所以需要 root 用户执行。

简单使用

比如可以使用 lsof 来查看当前系统中 80 端口是否被占用

sudo lsof -i tcp:80
COMMAND     PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
docker-pr 14863 root    4u  IPv6 38693061      0t0  TCP *:http (LISTEN)

然后获取到 PID 之后可以用 lsof 来查看进程

sudo lsof -p 14863
COMMAND     PID USER   FD      TYPE   DEVICE SIZE/OFF       NODE NAME
docker-pr 14863 root  cwd       DIR      8,0     4096          2 /
docker-pr 14863 root  rtd       DIR      8,0     4096          2 /
docker-pr 14863 root  txt       REG      8,0  3329080      17531 /usr/bin/docker-proxy
docker-pr 14863 root  mem       REG      8,0  1868984      20743 /lib/x86_64-linux-gnu/libc-2.23.so
docker-pr 14863 root  mem       REG      8,0   138696      11625 /lib/x86_64-linux-gnu/libpthread-2.23.so
docker-pr 14863 root  mem       REG      8,0   162632      10738 /lib/x86_64-linux-gnu/ld-2.23.so
docker-pr 14863 root    0r      CHR      1,3      0t0       8085 /dev/null
docker-pr 14863 root    1w      CHR      1,3      0t0       8085 /dev/null
docker-pr 14863 root    2w      CHR      1,3      0t0       8085 /dev/null
docker-pr 14863 root    4u     IPv6 38693061      0t0        TCP *:http (LISTEN)
docker-pr 14863 root    5u  a_inode     0,12        0       8082 [eventpoll]
docker-pr 14863 root   12r      REG      0,3        0 4026531993 net

由以上的信息就能看出来我的机器中的 80 端口是 docker 占用的,docker 的位置在系统中的 /usr/bin/docker-proxy

lsof 输出各列信息的意义如下:

  • COMMAND:进程的名称
  • PID:进程标识符
  • PPID:父进程标识符(需要指定 -R 参数)
  • USER:进程所有者
  • PGID:进程所属组
  • FD:文件描述符,应用程序通过文件描述符识别该文件。如 cwd、txt 等

FD 的取值

  • cwd:表示 current work dirctory,即:应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改
  • txt :该类型的文件是程序代码,如应用程序二进制文件本身或共享库,如上列表中显示的 /sbin/init 程序
  • lnn:library references (AIX);
  • er:FD information error (see NAME column);
  • jld:jail directory (FreeBSD);
  • ltx:shared library text (code and data);
  • mxx :hex memory-mapped type number xx.
  • m86:DOS Merge mapped file;
  • mem:memory-mapped file;
  • mmap:memory-mapped device;
  • pd:parent directory;
  • rtd:root directory;
  • tr:kernel trace file (OpenBSD);
  • v86 VP/ix mapped file;
  • 0:表示标准输出
  • 1:表示标准输入
  • 2:表示标准错误

一般在标准输出、标准错误、标准输入后还跟着文件状态模式:r、w、u 等

  • u:表示该文件被打开并处于读取 / 写入模式
  • r:表示该文件被打开并处于只读模式
  • w:表示该文件被打开并处于
  • 空格:表示该文件的状态模式为 unknow,且没有锁定
  • -:表示该文件的状态模式为 unknow,且被锁定

介绍

在有了基本的概念之后来看 lsof 的参数

lsof  [ -?abChKlnNOPRtUvVX ] [ -A A ] [ -c c ] [ +c c ] [ +|-d d ] [ +|-D D ] [ +|-e s ] [ +|-E ] [ +|-f [cfgGn] ] [ -F [f] ] [ -g [s] ] [ -i [i] ] [ -k k ] [ +|-L [l] ] [ +|-m m
   ] [ +|-M ] [ -o [o] ] [ -p s ] [ +|-r [t[m<fmt>]] ] [ -s [p:s] ] [ -S [t] ] [ -T [t] ] [ -u s ] [ +|-w ] [ -x [fl] ] [ -z [z] ] [ -Z [Z] ] [ -- ] [names]

能看到非常多的选项,因此也能看到 lsof 的强大。

-i 选项查看网络连接

默认 : 没有选项,lsof 列出活跃进程的所有打开文件

-i : 列出网络连接

使用 lsof -i 来显示所有的链接,可以用来代替 netstat:

sudo lsof -i
COMMAND     PID     USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
sshd       2972     root    3u  IPv4 18883553      0t0  TCP *:22 (LISTEN)
sshd       2972     root    4u  IPv6 18883562      0t0  TCP *:22 (LISTEN)
docker-pr 14852     root    4u  IPv6 38693034      0t0  TCP *:https (LISTEN)
docker-pr 14863     root    4u  IPv6 38693061      0t0  TCP *:http (LISTEN)

这里输出结果有缩略,但也能看出来 22 的 SSH 端口,然后 http 默认的 80 端口,和 https 使用的 443 端口。如果要查看 IPv6 的流量可以使用 lsof -i 6

同样如果要查看 TCP 或者 UDP 连接信息,可以使用 lsof -iTCPlsof -iUDP

假如已经知道了端口号,想要查看该端口哪一个进程在使用可以使用:

lsof -i:80

再比如查看和本地 22 端口的连接 lsof -i:22

显示来自特定主机的连接,lsof -i@1.2.3.4 ,指定主机和端口 lsof -i@1.2.3.4:22

根据状态过滤

lsof 还可以使用过滤器来过滤连接状态,比如只查看正在监听 TCP 端口的进程:

lsof -iTCP -sTCP:LISTEN

-p 选项查看指定进程

如果已经知道进程的 PID,可以使用 -p 查看指定进程 ID 已打开的内容

lsof -p 10075

列出用户打开的文件

lsof -u einverne

查看 Unix 域套接字

使用 -U 选项来列出系统中正在使用的 Unix 域套接字:

lsof -U

查看 java 项目依赖的 jar

比如说如果系统中依赖的一个 jar 被发现有漏洞,比如说可以查看 fastjson 在系统中有没有使用。

lsof -X | grep fastjson

2017-04-02 lsof , linux , file , command

WEB-INF 目录结构

WEB-INF 是 Java EE Web 程序一个特殊的目录,此目录中的资源不会被列入应用程序根目录可直接访问项。客户端不可直接访问 WEB-INF 中的资源。

根据 Servlet 2.4 specification 中的描述,这个不公开的目录虽然不能被外部访问,但是可以被 servlet 代码 getResource 或者 getResourceAsStream 等方法访问,并且可以暴露给 RequestDispatcher

目录 WEB-INF/web.xml 中保存 web 程序配置文件,XML 格式,描述 servlet 和其他应用组件配置和命名规则。

在 Spring MVC 和其他任何 web 程序中一个好的推荐方式是将所有的 views 或者 JSP 文件存放在 WEB-INF 文件夹中。这些放在 WEB-INF 中的 views 就被称为 internal resource view,这些 views 只能被 servlet 或者 Spring Controller 访问。

reference


2017-04-01 java-web , web , web-inf , spring , spring-mvc

如何找到一首歌的名字

“听歌识曲” 虽然听起来是一个简单的功能,却还依然发展了很多年。在无数的网站评论中看到求求某某片段中的背景音乐,其实绝大部分情况下都可以通过听歌识曲来找到,剩下的也绝大部分可以通过电视,电影的OST找到。所以这篇文章就是介绍下目前市面上我使用过比较好用的一些听歌识曲的应用,这些应用解决了我95%以上,找到喜欢的背景音乐的需求。

授人以鱼不如授人以渔

在电视、综艺、或者大街上听到一首喜欢的背景音乐却不知道歌名的时候,我下意识的会拿出手机来打开网易云音乐,当然这个能够解决一大半的问题,因为经过多年的使用,其实网易的识别还是有些准确的,至少对于绝大部分流行的中日韩欧美歌曲基本都能够识别出来,更甚至之前看请回答系列,识别出来了上世纪六七十年代发行的歌曲。但是听歌识曲有一些小小的弊端,一般情况下需要环境噪声比较小,并且经验给我的感觉是需要一段较长有歌词的完整的片段才能够快速的找到精确的歌。所以如果周围噪音比较大,或者电影中人物有对白时,尽量记住连续一段完整的歌词,然后通过搜索引擎搜索歌词来获取歌名,一般情况下我会加上 lyrics + 记住的歌词 来搜索,基本也能够找到想要的歌。

听歌识曲应用

网易云音乐

网易云音乐

SoundHound

soundhound

Shazam

shazam

Siri

siri

还有其他的酷狗,微信摇一摇啊,就不介绍了。

横向对比

应用名 支持平台 语种 特别功能
网易云音乐 Android/iOS 中韩英 识别出即可播放(当然要排除版权问题)
SoundHound Android/iOS 英中 可以通过哼唱来识别
Shazam Android/iOS 在低功耗情况下一直识别音乐
Siri iOS 随系统自带

2021 年更新

到目前为止,我基本上只会使用 Shazam 和 网易云音乐了,Shazam 被苹果收购之后越来越好用,反而是 SoundHound 可能一直没有找到合适的盈利模式而越来越难用。


2017-03-24 music , collection , bgm , song

vim config vimrc


2017-03-23

Java 查漏补缺之 BigDecimal

在一些对精度要求特别高的系统中,比如会计,金融,Java 中的 double,float 已经不能满足精度需求,谁也不愿意再付款或者计价的时候出现付费 4.4 而账单只有 4.0 元的错误。所以在 Java 中为了更高精度的计算我们需要用到 java.math.BigDecimal.

BigDecimal 需要有两个能力:

  • 指定 scale,也就是小数点后几位
  • 指定舍入模式,当超过精度时如何处理

通常在使用 BigDecimal 时建议使用 传入 String 的构造函数。

BigDecimal bd = new BigDecimal(1.5);
bd = new BigDecimal("1.5");

如果使用 double 的构造函数可能会造成一些问题,比如第一个 bd 可能结果是 1.49999999999999999999

Scale and Rounding

设置小数点(decimal)后面的数字位数,需要使用 .setScale(scale) 方法,与此同时,在设置 scale (数值范围)的时候一并设置 Rounding Mode(舍入模式)被认为是一个比较好的实践方式(Good practice),需要使用 .setScale(scale, roundingMode) 。rounding mode 定义了在损失精度时使用的舍入方式,比如四舍五入,或者 Ceiling 或者 Floor 等等。

为什么需要舍入模式,来看一个例子

bd = new BigDecimal(1.5); // is actually 1.4999....
bd.setScale(1); // throws ArithmeticException

抛出算术异常,因为 BigDecimal 不知道如何处理 1.49999.

有八种定义好的 Rounding Mode,假设保留小数点后两位

ROUND_CEILING: Ceiling function 向天花板舍入

                 0.333  ->   0.34
                -0.333  ->  -0.33

ROUND_DOWN: Round towards zero 向 0 舍入

                 0.333  ->   0.33
                -0.333  ->  -0.33

ROUND_FLOOR: Floor function 往小舍入

                 0.333  ->   0.33
                -0.333  ->  -0.34

ROUND_HALF_UP: Round up if decimal >= .5

                 0.5  ->  1.0
                 0.4  ->  0.0

ROUND_HALF_DOWN: Round up if decimal > .5   最常见的四舍五入

                 0.5  ->  0.0
                 0.6  ->  1.0

ROUND_HALF_EVEN:  当需要舍入的数字是 5 时需要特殊处理,需要看 5 左边的数字奇偶性

 a = new BigDecimal("2.5"); // digit left of 5 is even, so round down
 b = new BigDecimal("1.5"); // digit left of 5 is odd, so round up
 a.setScale(0, BigDecimal.ROUND_HALF_EVEN).toString() // => 2
 b.setScale(0, BigDecimal.ROUND_HALF_EVEN).toString() // => 2

ROUND_UNNECESSARY: 当需要使用一种舍入方式,但是你知道结果不需要舍入的时候,也就意味着如果使用了这种舍入模式,那么如果出现一个不精确的结果比如 1/3,那么会抛出 ArithmeticException。

Immutability and Arithmetic

BigDecimal 是不可变对象,也就意味这如果创建了一个 BigDecimal 是 2.00 那么这个 BigDecimal 会一直是 2.00。在做算术是比如 add()multiply() 方法时会返回一个新的 BigDecimal 对象。

Comparison

Never use .equal() method to compare BigDecimal. Because this equals function will compare the scale. If the scale is different, .equals() will return false, even if they are the same number mathematically.

BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("2.0");
print(a.equals(b)); // false

反之,应该使用 .compareTo() and .signum() 方法

a.compareTo(b);  // returns (-1 if a < b), (0 if a == b), (1 if a > b)
a.signum(); // returns (-1 if a < 0), (0 if a == 0), (1 if a > 0)

何时舍入结果:关于精度的思考

如果要在计算中使用舍入模式,那么用什么精度呢?答案是打算如何使用结果。一般情况下,会知道最后用户需要的结果的精确度。对于那些中间计算过程中出现的数字,你需要增加一位数字来提高精确度。

比如 0.0144 + 0.0143 最后保留两位小数的话,在结果舍入会得到 0.03,而如果将两个数字加法之前就舍入,那么会得到 0.02.

如果最后的结果是乘法得到,那么你应当保留尽可能多的精度。而 Ratios 比率 和 Unit costs 单位价格,不应当舍入。而应当在做完所有计算之后在对结果进行舍入。

reference


2017-03-22 java , double , float , bigdecimal , jdk

使用 mitmdump 记录 HTTP 流量

之前一篇文章 主要是使用 mitmproxy 进行抓包,但是其实 mitmproxy 自带的 feature 远远不止于抓包,使用 mitmdump 可以自定义脚本来修改 response 返回,或者将请求结果 dump 到本地以便于之后的分析。

之前的那篇文章在 mitmdump 的时候只是简单的介绍了一下功能,并没有展开,所以有了这篇文章。mitmdump 可以理解为 mitmproxy 的命令行版本,他提供了 tcpdump 类似的功能来查看,记录,甚至编程改写 HTTP 流量。

保存流量

开启代理模式,并将所有的请求写入文件

mitmdump -w outfile

过滤保存的流量

-n 参数表示不开启代理, -r 表示读入 infile,然后将将所有 match ~m post POST 流量写入 outfile 文件中。

mitmdump -nr infile -w outfile "~m post"

关于过滤的规则,可以具体参考这里

客户端重放请求

使用 -n 参数不开启代理,然后 -c filename 参数进行重放。

mitmdump -nc outfile

甚至是可以重放请求,然后将结果保存到另外的文件中

mitmdump -nc srcfile -w dstfile

添加脚本

可以在启动 mitmdump 时添加自定义的脚本用来改写请求。

mitmdump -s examples/add_header.py

如果脚本文件带有参数,则需要在 -s 参数后面增加双引号,比如 mitmdump -s "add_header.py custom_header"

组合使用

将这些参数组合一起使用

mitmdump -ns examples/add_header.py -r srcfile -w dstfile

从 srcfile 文件中加载流量,然后使用特定的脚本改写,然后将结果写入 dstfile 文件中。

实例

将请求结果保存到本地文件

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import sys

from mitmproxy import flowfilter, http, ctx


# events run ordr: start, request, responseheaders, response, error, done

class Filter:
    MOVIE_TOP250 = '/api/v2/subject_collection/movie_top250/items'

    def __init__(self, path):
        self.folder_path = path
        # 构造一个 HTTP response code
        self.http_code_ok = flowfilter.parse('~c 200')
        if not os.path.exists(self.folder_path):
            os.makedirs(self.folder_path)
        # 构造一个 URL 过滤器
        self.douban_path = flowfilter.parse(
            '~u frodo.douban.com/api/v2/subject_collection/movie_top250/items')

    # @concurrent  # Remove this and see what happens
    def request(self, flow: http.HTTPFlow):
        if flowfilter.match(self.douban_path, flow):
            if flow.request.host:
                ctx.log(
                    "handle request: %s%s" % (
                        flow.request.host, flow.request.path))

    def response(self, flow: http.HTTPFlow):
        if flowfilter.match(self.http_code_ok, flow):
            """只有 200 状态进入"""
            ctx.log('code %s' % flow.response.status_code)
            if flowfilter.match(self.MOVIE_TOP250, flow):
                if flow.response.content:
                    pretty_path = str(flow.request.path.rstrip())
                    pretty_path = pretty_path.replace('/', '_') \
                        .replace(':', '_') \
                        .replace('&', '_')
                    pretty_path = pretty_path[:250] + '.json'
                    res_content = flow.response.content.decode('utf-8')
                    path = os.path.join(self.folder_path, pretty_path)
                    with open(path, 'w+') as f:
                        f.write(res_content)


def start():
    if len(sys.argv) != 2:
        raise ValueError('Usage: -s "save_response.py path"')
    # 保存结果的 folder 路径
    return Filter(sys.argv[1])

将上面的脚本执行

/usr/local/bin/mitmdump -s "save_response.py /tmp/response_result/"

然后在结果路径中就能得到请求的豆瓣 Top250 电影结果,然后再对电影结果进行解析即可。

或者可以将请求的 webp 或者 jpg 的图全都保存到另外的文件夹中

pretty_url = flow.request.pretty_url
if pretty_url.endswith(".webp") or pretty_url.endswith('.jpg'):
    # ctx.log('pretty url %s' % flow.request.pretty_url)
    filename = os.path.join(self.folder_path,
                            os.path.basename(pretty_url))
    with open(filename, 'wb') as f:
        f.write(flow.response.content)

然后只要浏览过的图片就全都保存在本地的文件夹中了。

按规则过滤请求

mitm 的过滤都是依靠 flowfilter.py 来实现的,可以匹配的规则有如下

    The following operators are understood:

        ~q          Request
        ~s          Response

    Headers:

        Patterns are matched against "name: value" strings. Field names are
        all-lowercase.

        ~a          Asset content-type in response. Asset content types are:
                        text/javascript
                        application/x-javascript
                        application/javascript
                        text/css
                        image/*
                        application/x-shockwave-flash
        ~h rex      Header line in either request or response
        ~hq rex     Header in request
        ~hs rex     Header in response

        ~b rex      Expression in the body of either request or response
        ~bq rex     Expression in the body of request
        ~bs rex     Expression in the body of response
        ~t rex      Shortcut for content-type header.

        ~d rex      Request domain
        ~m rex      Method
        ~u rex      URL
        ~c CODE     Response code.
        rex         Equivalent to ~u rex

从这些匹配规则就能看出来过滤规则可以非常精细,比如过滤结果为 500 的请求,比如过滤 header 中 content-type 为某种类型的请求,比如按照正则去匹配 URL 等等。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import sys

from mitmproxy import flowfilter, http, ctx


# events run ordr: start, request, responseheaders, response, error, done

class Filter:
    MOVIE_TOP250 = '/api/v2/subject_collection/movie_top250/items'

    def __init__(self):
        # 构造一个 URL 过滤器
        self.douban_path = flowfilter.parse(
            '~u frodo.douban.com/api/v2/elendil/home_timeline')
        # 构造一个 HTTP response code
        self.http_code_ok = flowfilter.parse('~c 200')
        # Domain
        self.my_domain = flowfilter.parse('~d douban.com')
        # Method
        self.filter_mathod = flowfilter.parse('~m POST')
        # content-type header
        self.filter_content_type = flowfilter.parse('~t json')

    # @concurrent  # Remove this and see what happens
    def request(self, flow: http.HTTPFlow):
        if flowfilter.match(self.douban_path, flow):
            if flow.request.host:
                ctx.log(
                    "handle request: %s%s" % (
                        flow.request.host, flow.request.path))

    def response(self, flow: http.HTTPFlow):
        if flowfilter.match(self.http_code_ok, flow):
            """只有 200 状态进入"""
            ctx.log('code %s' % (flow.response.status_code))
        if flowfilter.match(self.my_domain, flow):
            """只有匹配域名"""
            ctx.log('domain %s' % flow.response.text)
        if flowfilter.match(self.douban_path, flow):
            """只有 特定 url 可以进入"""
            ctx.log('douban text' + flow.response.text)
            ctx.log('douban reason ' + flow.response.reason)
            ctx.log('douban http version ' + flow.response.http_version)
        pretty_url = flow.request.pretty_url
        if flowfilter.match(self.MOVIE_TOP250, flow):
            if flow.response.content:
                res_content = flow.response.content.decode('utf-8')
                ctx.log("content: " + res_content)


def start():
    if len(sys.argv) != 2:
        raise ValueError('Usage: -s "dump.py"')
    return Filter()

reference


2017-03-16 mitmproxy , mitmdump , mitmweb , mitm , charles

电子书

本站提供服务

最近文章

  • 从 Buffer 消费图学习 CCPM 项目管理方法 CCPM(Critical Chain Project Management)中文叫做关键链项目管理方法,是 Eliyahu M. Goldratt 在其著作 Critical Chain 中踢出来的项目管理方法,它侧重于项目执行所需要的资源,通过识别和管理项目关键链的方法来有效的监控项目工期,以及提高项目交付率。
  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。