手机验证码的自动化处理

前面我们了解了一些验证码的识别流程,这些验证码有一个共同的特点,就是通常只在 PC 上即可识别通过,例如在 PC 上出现了一个图形验证码,那么在 PC 上直接识别就好了,所有流程都在 PC 上完成。

但还有一种验证码和这些验证码不同,就是手机验证码,如果在 PC 上出现了一个手机验证码,需要先在 PC 上输入手机号,然后把短信验证码发到手机上,再在 PC 上输入收到的验证码,才能通过验证。

遇到这种情况,如何才能将识别流程自动化呢?

短信验证码的收发

通常而言,我们的自动化脚本运行在 PC 上,例如打开一个网页,然后模拟输入手机号,点击获取验证码,接下来就需要输入验证码了。我们可以非常容易地把前三个流程自动化,但验证码是发送到手机上了,怎么把它转给 PC 呢?

自动化验证码的整个收发流程,可以这么实现一一当手机接收到一条短信时,自动将这条短信转发至某处,例如转发至一台远程服务器或者直接发给 PC,在 PC 上我们可以通过一些方法获取短信内容并提取验证码,再自动化填充到输入的地方即可。

关键其实就是下面这两步:

  • 监听手机接收到短信的事件;

  • 将短信内容转发至指定的位置。

这两步缺一不可,而且都需要在手机上完成。解决思路其实很简单,以 Android 手机为例,如果有 Android 开发经验,这两个功能实现起来还是蛮简单的。

这里我们仅仅简单介绍基本的思路,不会详细介绍具体的代码实现,感兴趣的话可以自行尝试。

首先如何监听手机接收到短信呢?在 Android 开发中,分为三个必要环节。

  • 注册读取短信的权限:在一个 Android App 中,读取短信需要具备特定的权限,所以需要在 Android App 的 AndroidManifest.xml 文件中将读取短信的权限配置好,例如: <uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>

  • 注册广播事件:Android 有一个基本组件叫作 BroadcastReceiver,是广播接收者的意思,我们可以用它监听来自系统的各种事件广播,例如系统电量不足的广播、系统来电的广播,那系统接收到短信的广播自然也不在话下。这类似于注册一个监听器来监听系统接收到短信的事件。这里我们在 AndroidManifest.xml 文件中注册一个 BroadcastReceiver,叫作 SmsReceiver:

    <receiver android:name=".receive.SmsReceiver">
        <intent-filter android:priority="999">
            <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
        </intent-filter>
    </receiver>
  • 实现短信广播的接收:这里就需真正实现短信接收的逻辑了,只需要实现一个 SmsReceiver 类,它继承 BroadcastReceiver 类,然后实现其 onReceive 方法即可,其中 intent 参数里就包含了我们想要的短信内容,实现如下:

    public class SmsReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle bundle = intent.getExtras();
            SmsMessage msg = null;
            if (bundle != null) {
                Object[] smsObj = (Object[]) bundle.get("pdus");
                for (Object object : smsObj) {
                    msg = SmsMessage.createFromPdu((byte[]) object);
                    Log.e("短信号码", "" + msg.getOriginatingAddress());
                    Log.e("短信内容", "" + msg.getDisplayMessageBody());
                    Log.e("短信时间", "" + msg.getTimestampMillis());
                }
            }
        }
    }

如此一来,我们便实现了短信的接收。

收到短信之后,发送自然也很简单了,例如服务器提供一个 API,请求该 API 即可实现数据的发送,Android 的一些 HTTP 请求库就可以实现这个逻辑,例如利用 OkHttp 构造一个 HTTP 请求,这里就不再资述了。

不过总的来说,整个流程其实还需要花费一些开发成本,对于如此常用的功能,有没有现成的解决方案呢?自然是有的。我们完全可以借助一些开源实现,这样就没必要重复造轮子了。

介绍一个开源软件 SmsForwarder,中文叫作短信转发器,其 GitHub 地址为 https://github.com/pppscn/SmsForwarder 。它的基本架构如图 8-49 所示。

图8-49 SmsForwarder 的基本架构

SmsForwarder 的架构非常清晰,它可以监听收到短信的事件,获取短信的来源号码、接收卡槽,短信内容、接收时间等,然后将这些内容通过一定的规则转发出去,支持转发到邮箱、企业微信群机器人、企业微信应用、Telegram 机器人和 Webhook 等。例如可以配置这样的转发规则,如图 8-50 所示。

又如当手机号符合一定规则时就把获取的内容转发到 QQ 邮箱;当内容包含 “报警” 字样时就把获取的内容转发到阿里企业邮箱:当内容开头是 “测试” 时就把获取的内容发送给叫作 TSMS 的 Webhook。其中的 QQ 邮箱和阿里企业邮箱是我们已经配置好的发送方,都属于邮箱类型,TSMS 也是一种发送方,属于 Webhook 类型,发送方如图 8-51 所示,

图8-50 配置的转发规则 图8-51 发送方

我们也可以点击图 8-51 下方的 “添加发送方” 按钮添加想要的发送方,可以选择对应的发送方类型,例如这里添加邮箱类型的发送方,App 会弹出 “设置邮箱” 的提示框,如图 8-52 所示,我们可以设置 SMTP 端口、发件账号、登录密码/授权码等内容。

同样,如果选择添加 Webhook 类型的发送方,App 会弹出 “设置 Webhook” 提示框,如图 8-53 所示,我们可以选择请求方式(POST 或 GET),设置 WebServer 的 URL,设置 Secret。

设置转发规则的页面如图 8-54 所示,支持正则匹配规则和卡槽匹配规则。这里可以设置匹配的卡槽、匹配的字段、匹配的模式,还可以填写正则表达式来设置匹配的值,图 8-54 中就设置了尾号是 4566 的手机号,执行一定的发送操作,收到的短信会发送到钉钉这个发送方。

图 8-52 设置邮箱 图 8-53 设置 Webhook 图 8-54 设置转发规则

实战演示

我们尝试使用 Flask 写一个 API, 实现如下:

from flask import Flask, request, jsonify
from loguru import logger

app = Flask(__name__)

@app.route('/sms', methods=['POST'])
def receive():
    sms_content = request.form.get('content')
    logger.debug(f'received {sms_content}')
    # 解析内容并将其保存到 db 或 mq
    return jsonify(status='success')

if __name__ == '__main__':
    app.run(debug=True)

代码很简单,先设置了一个路由,接收 POST 请求,然后读取了 Request 表单的内容,其中 content 就是短信内容的详情,之后将其打印出来。

将上述代码保存为 server.py, 然后运行:

python3 server.py

运行结果如下:

  • Debug mode: on

  • Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

  • Restarting with stat

  • Debugger is active!

  • Debugger PIN: 269-657-055

为了方便测试,可以用 Ngrok 工具将该服务暴露到公网:

ngrok http 5000

Ngrok 可以方便地将任何非公网的服务暴露到公网访问,并配置特定的临时二级域名,但一个域名有时长限制,所以通常仅供测试使用。试用前请先安装 Ngrok, 具体可以参考 https://ngrok.com/。

运行之后,可以看到如下结果:

可以看到 Ngrok 为我们配置了一个公网地址,例如访问 https://12595939cb974.ngrok.io 就相当于访问我们本地的 http://localhost:5000 服务,这样只需要在手机上配置这个地址就可以将数据发送到 PC 端了。

接下来我们手机上打开 SmsForder, 添加一个 Webhook 类型的发送方,设置详情如图 8-55 所示。其中,我们把 WebServer 的 URL 直接设置成刚才 Ngrok 提供的公网地址,注意要记得在 URL 的后面加上 sms。

接着我们添加一个转发规则,如图 8-56 所示。

图8-55 设置 Webhook 图8-56 设置转发规则

这里我们设置了内容匹配规则,当匹配到以 “测试” 为开头的内容时,就将短信转发到Webhook 这个发送方,即发送到我们刚刚搭建的 Flask 服务器上。

添加完成后,我们可以尝试用另一台手机给当前运行此 App 的手机发送一个验证码信息,内容如下:

测试验证码 593722,一分钟有效。

这时可以发现刚才的 Flask 服务器接收到了这样的结果:

received +8617xxxxxxxx
测试验证码 593722,一分钟有效。
SIM2 China Unicom_
2021-03-27 18:47:54
SM-C9860

可以看到,发送给手机的验证码信息已经成功由手机发送到 PC 了,接着便可以对此信息进行解析和处理,然后存入数据库或者消息队列。爬虫端监听消息队列或者数据库有改动即可将收到的验证码填写到爬取的 Than 网站上并进行一些模拟登录操作,这个过程就不再赘述了。

批量收发

以上介绍的内容针对的是只有一部手机的情况,如果有大量手机和手机卡,则可以实现手机的群控处理,例如统一安装短信接收软件、统一配置相同的转发规则,从而接收和处理大量手机号的验证码。图 8-57 所示的就是一个群控系统。

图8-57 一个群控系统

卡池、猫池

除了上面的方法,当然还有更专业的解决方案,例如用专业的手机卡池、猫池,配以专业的软件设备实现短信监听。例如图 8-58 中的设备支持插 128 张 SIM 卡,这样可以同时监听 128 个手机号的验证码。

具体的技术这里不再阐述,可以自行查询相关的设备供应商了解详情。

接码平台

卡池、猫池的解决方案成本还是比较高的,而且这些方案其实已经不限于简单地接收短信验证码了,就像手机群控系统一般会做手机群控爬虫,卡池也可以用来做 4G/5G蜂窝代理,仅仅做短信收发当然也可以,但未免有些浪费了。

如果不想耗费过多成本,想实现短信验证码的自动化,还有一种方案就是接码平台,其基本思路如下。

  • 平台会维护大量手机号,并可能开放一些 API 或者提供网页供我们调用来获取手机号和查看短信的内容。

  • 我们调用 API 或者爬取网页获取手机号,然后在对应的网站输入该手机号来获取验证码。

  • 通过调用 API 或者爬取网页获取对应手机号的短信内容,并交由爬虫处理。

由于对接码平台的管控比较严格,因此接码平台随时可能会不可用,请自行搜集对应的平台使用。

总结

本节通过一个实战案例介绍了手机验证码的自动化处理流程,同时介绍了现在业界广泛采用的一些用于收发验证码的工具。随着技术的发展,各种新的工具和技术会不断出现,合适又强大的工具会让我们的爬虫开发过程如虎添翼。