编写程序的过程中多多少少会用到一些随机数。比如在制作爬虫时会模拟人的行为随机休眠,或者说选择性的休眠;再比如在制作游戏时会有随机概率的抽奖……

常规的反爬虫策略

目前大部分网站的反爬虫策略也还算比较简单,通过对同一时间内来自同一个ip的访问量的统计进行判断,如果访问量过高那么就会被当做爬虫来处理。如何进行反反爬虫就是利用反爬虫的特点就行针对性的避开检测。

最简单的,也是最实用的就是使用当完成一个任务以后就休眠的策略。但部分网站的反爬虫策略会检测间隔的时间是否是线性的,如果是则判断为爬虫,针对这种情况我们可以可以使用随机数应付。当然更有甚者甚至能够针对每次请求后是否有间隔判断是否是爬虫……这里我们具体讲讲如何对付最后一种情况。

使用概率性休眠策略

设计思路

在Python中简单的概率模拟可以使用random.choice函数就行模拟,如下

@property
def is_sleep():
  return random.choice([True, True, False])

random.choice能够实现随机返回一个列表中的值,现在我们在列表中放入三个值,依次是True, True, False

当访问(调用)is_sleep时我们就可以得到一个随机值判断是否休眠,而其中触发概率显而易见的是2/3

算法优化

但是这种方法对于当列表的数比较多时就显得非常低效率了。如我们用这个方法来模拟1/10000的概率就得先需要一个能够装下10000个数字的列表……当然这都不算什么,但如果是1/10^20呢?

这时我们就需要使用随机数加上取模运算了。假设我们手上有随机正整数x(包含0),那么前面的1/10000我们可以使用公式x % 10000 in [0,)这样的方法就行表示。

其中取模运算的结果一定是在0~9999之间,所以x % 10000 in [0,)的概率一定是1/10000

现在我们已经知道了1/N次的概率模拟了,那么如何模拟n/N次呢?其实方法很简单,可能你早就想到了:我们只需要将在0~N - 1范围内可能的结果添加n个在in关键字后的元组里就可以了。

9998/10000可以表示为x % 10000 in [0, 9998),或者你也可以写成x % 10000 in range(0, 9998)

代码实现

这里我们设计一个小游戏,名字就叫做俄罗斯轮盘赌

from sys import stdin
from random import randint

def fire(n, cnt):
  return randint(0, 0xffff) % cnt in range(0, n)

def main():
  while stdin.readline():
    if fire(1, 6):
      print("你死了!")
    else:
      print("幸运儿!")

main()

当然这不是最好的实现,我们可以使用randrange直接获取0~N之间的任意值,因此代码可以改成如下格式。

from sys import stdin
from random import randrange

def fire(n, cnt):
  return randrange(0, cnt) in range(0, n)

def main():
  while stdin.readline():
    if fire(1, 6):
      print("你死了!")
    else:
      print("幸运儿!")

main()

但如果是在C语言中实现,那么可能就需要使用到取模运算符了,因为C语言中实现的随机数大多是不能限制范围的。此外在C语言中或者Python2中可以将x % cnt in range(0, n)in range(0, n)替换成if语句来实现。

from sys import stdin
from random import randrange

def fire(n, cnt):
  return randrange(0, cnt) < n

def main():
  while stdin.readline():
    if fire(1, 6):
      print("你死了!")
    else:
      print("幸运儿!")

main()

因为Python2中range实际上会生成新的列表……

总结与最终简化版

from random import randrange

def yesOrNo(n, cnt):
  return randrange(0, cnt) < n

yesOrNo(1, 4)              # 1/4概率
yesOrNo(9998, 109964)      # 9998/109964概率