☰
免验证码话费余额查询:HTML+API接口实现跨运营商快速查询
2026/10/21 5:18:16 网站建设 项目流程

简介:面向有自助查询话费余额需求的开发者和普通用户,这套移动、电信、联通话费余额查询HTML源码与API接口资源,无需验证码即可实时返回结果,并支持自动识别运营商;遇到携号转网号码时手动选择运营商即可。压缩包共6个文件,以HTML页面为主,配CSS样式、JS逻辑和2张PNG示意图片,整体约133KB;页面入口直观,逻辑、样式与页面结构分离,便于直接部署或二次开发。资源目前已吸引612人学习,适合快速搭建话费查询demo、接入个人网站,也可作为运营商查询接口的调用范例。开发者只需将源码中的apiKey替换为自己注册获取的密钥,即可继续使用平台赠送的免费查询次数;即便不熟悉配置,也可借助评论区技术支持完成体验,使用门槛较低。

1. 免验证码话费余额查询:一个HTML加接口搞定的跨运营商小工具

客服系统里要查一个号码的话费余额,传统做法是往用户手机上发条短信验证码,等用户回填,一次查询没个一分钟下不来。而这份移动电信联通手机话费余额查询HTML源码加配套的API接口,把三大运营商的查询通道聚合在同一个页面里,免去了验证码环节,拿到号码点下查询就能看到余额。对做客服后台、话费报销核对、个人自用工具的人来说,它省掉的是最磨人的等待环节,留下的是一个能快速接入现有系统的轻量接口。整份资源由前端页面、查询接口和运营商通道配置三部分组成,想快速搭一套查询工具、又不想重新造轮子的开发者可以直接拿来做底座。

2. 技术构成与整体架构:前端号段识别、查询接口与通道配置怎么协作

这份资源的核心结构很收敛,就是“前端页面 + 一个查询接口 + 多个上游通道配置”。前端负责收集手机号和展示结果,接口负责把号码路由到对应的运营商通道,上游返回原始数据后,接口再做一次清洗,统一成固定 JSON 吐给页面。整个过程不依赖数据库,也不绑定复杂框架,用 Python 或 Node 都能把接口部分跑起来。

为什么把结构设计得这么简单?因为话费查询这种工具型页面,热点在“查得快、结果准”,不在页面特效。多套一层服务就多一个故障点,把核心逻辑收敛成一条单链路,后续接监控、加缓存、换通道都更方便。下面按前端、后端、目录配置三个维度拆开看。

2.1 号码输入与运营商初判:号段表在前端的作用

用户在输入框里填入 11 位手机号后,前端第一件事不是立刻发请求,而是先用号段表判断这个号码属于移动、电信还是联通。这样做有两个好处:一是能提前拦住明显非法的号码,二是可以在界面上直接展示运营商名称,让用户确认后再发起查询,减少误查。

项目里的号段判断逻辑通常放在query.js中,常见写法是根据号码前三位匹配运营商前缀表:

// 常见号段前缀,实际使用时以项目里最新的 number_prefix.json 为准 const OPERATOR_PREFIX = { cmcc: ['134', '135', '136', '137', '138', '139', '150', '151', '152', '157', '158', '159', '172', '178', '182', '183', '184', '187', '188', '195', '197', '198'], ctcc: ['133', '149', '153', '173', '174', '177', '180', '181', '189', '190', '191', '193', '199'], cucc: ['130', '131', '132', '145', '146', '155', '156', '166', '167', '171', '175', '176', '185', '186', '196'] }; function detectOperator(phone) { const prefix = phone.slice(0, 3); for (const op in OPERATOR_PREFIX) { if (OPERATOR_PREFIX[op].includes(prefix)) return op; } return 'unknown'; }

这段代码的核心是phone.slice(0, 3)截取号码前三位,再在三个运营商的号段数组里做查找。参数phone要求是 11 位数字字符串;返回值cmcc、ctcc、cucc分别对应移动、电信、联通,unknown表示号段表里没有匹配项。注意这里的前缀表不是全量号段,实际应全部替换为项目内置的最新表。

需要特别说明的是,前端号段识别只承担“初判”职责,真正路由时以后端接口结果为准。原因是号段表是静态的,而号码可能已经携号转网,前端识别结果只用于展示和提示。接着页面通过fetch发起查询:

async function queryBalance(phone) { // _t 参数是时间戳,防止浏览器或中间层缓存 const url = '/query?phone=' + encodeURIComponent(phone) + '&_t=' + Date.now(); const resp = await fetch(url); const json = await resp.json(); if (json.code === 0) { renderResult(json.data); // data 里是余额、运营商、查询时间 } else { renderError(json.msg); } }

这段交互代码把查询请求收敛成一个queryBalance函数,页面里不管是按钮点击还是回车事件,最终都调它。phone参数需要做一次encodeURIComponent,防止特殊字符破坏 URL;_t是防缓存的通用做法,因为部分浏览器对 GET 请求有缓存偏好,加时间戳后能保证每次点击都打到接口。返回结果里code === 0才是成功,其余情况都走错误分支,这个约定在后面接口设计中会保持一致。

2.2 后端查询接口:一个 /query 入口统一返回三类运营商数据

后端只暴露一个接口入口,前端完全不用关心某个号码到底走哪家通道。这种设计的价值在于:将来换了通道地址、加了新的运营商识别规则,前端代码可以不动,只需要后端在server.py里做调整。

接口内部做四件事:参数校验、运营商识别、通道转发、结果清洗。以常见的 Flask 写法为例:

import re from flask import request, jsonify @app.route('/query', methods=['GET']) def query_balance(): phone = request.args.get('phone', '').strip() # 1. 参数校验:必须是以 1 开头的 11 位数字 if not re.match(r'^1\d{10}$', phone): return jsonify(code=400, msg='手机号格式不正确', data=None) # 2. 识别运营商 operator = route_by_number(phone) # 3. 读取对应通道配置并转发请求 channel_url = load_channel_url(operator) raw = forward_channel(channel_url, phone) # 4. 清洗结果,统一返回 return jsonify(code=0, msg='success', data=normalize_result(raw, operator, phone))

接口的参数是 URL Query 里的phone,校验正则^1\d{10}$能挡住空值、非数字、位数不对的输入。route_by_number负责把号码映射成运营商标识,load_channel_url从配置里取对应通道地址,forward_channel真正请求上游,normalize_result在最后做数据统一。四个函数各管一段,后续单独排查哪一步出了问题都方便。

对外返回的 JSON 结构固定为三层:

  • code:0 表示成功,非 0 为错误码,便于前端直接判断
  • msg:人类可读的提示信息,错误时直接展示给用户
  • data:查询数据,包含phone、operator、balance、unit、update_time

这个统一返回格式是整个项目最重要的约定之一。上游通道返回的数据再乱,到了边界层都必须收敛成同一套结构,否则前端就要写一堆针对不同通道的判断,那就违背了这份资源“轻量可改”的初衷。

2.3 目录结构与配置文件:拆开看这份资源由哪些文件组成

整个项目从目录上分成三块:静态页面、接口服务、配置与数据文件。拿到源码后先对照这张表确认文件用途,跑起来会顺手很多:

文件作用
index.html查询页面,包含号码输入框和结果展示区
assets/js/query.js号段识别、请求发送、结果渲染逻辑
server.py接口服务,校验参数、路由号码、转发通道
config.json通道地址、超时、缓存、Mock 开关配置
number_prefix.json号段表,与前端识别逻辑同步维护

config.json是二次开发时最先要碰的文件。典型内容长这样:

{ "mock": true, "channel": { "cmcc": "请替换为移动通道地址", "ctcc": "请替换为电信通道地址", "cucc": "请替换为联通通道地址" }, "timeout": 8, "cache_ttl": 300, "rate_limit": 10 }

各键含义:mock为true时接口不真正请求上游,直接返回模拟余额,方便先跑通页面;channel里填的是各运营商查询通道的网关地址,正式对接时替换成自己账号下的地址;timeout是上游请求超时秒数;cache_ttl是同一个号码的结果缓存时间;rate_limit是接口侧每分钟最大查询数。

我一般会把号段表单独拆成number_prefix.json而不是直接写死在页面里,就是为了让前后端共享同一份数据。改号段时只动一个文件,避免出现前端识别成联通、后端路由成移动这种低级不一致。

3. 部署运行与联调:本地起服务到第一次查出号码余额

很多新手拿到这种“HTML源码+接口”的资源,第一反应是直接双击打开index.html,然后发现页面报错、数据出不来。原因是浏览器直接打开本地文件时,前端脚本请求接口会触发跨域限制。正确做法是先把整个项目放到本地服务下运行,让页面和接口处于可访问状态,再做联调。

3.1 本地启动:标准库静态服务加接口服务的组合跑法

如果机器上已经装了 Python,用标准库就能完成启动,不需要额外pip install。打开两个终端窗口,分别执行:

# 终端一:把前端页面跑在 8080 端口 python -m http.server 8080 --directory ./web # 终端二:把接口服务跑在 8000 端口 python server.py

第一条命令里的-m http.server是 Python 内置的静态文件服务,8080是端口号,--directory ./web指定页面文件所在目录;第二条命令直接运行接口脚本。两条命令的作用不同:前者负责把 HTML、JS 这些静态资源发给浏览器,后者负责处理/query这类动态请求。

如果不想开两个端口,也可以用一个支持静态资源和接口路由的框架把所有请求收在一起。常见做法是用 Node 起一个单文件服务,既托管静态页面,又挂/query路由,天然同源,省掉跨域配置。两种方式效果一样,选自己顺手的即可。

启动后query.js里设置的接口地址要和实际端口对应。方式一般有两种:

// 方式一:跨端口时写全地址 const API_BASE = 'http://127.0.0.1:8000'; // 方式二:同源部署时留空 const API_BASE = '';

两种方式的区别在于请求到底打到哪个服务。跨端口部署时接口地址必须写全,否则浏览器会向页面所在端口的/query发请求,结果就是 404;同源部署时留空即可,让请求自动走当前域名和端口。

3.2 通道配置与 Mock 开关:没有真实通道也能先跑通

真实环境里的运营商查询通道对接通常要处理签名、白名单、内网访问等问题,第一次跑项目最容易卡在这一步。这份资源的处理方式是给config.json增加一个mock开关,mock为true时接口不发起真实请求,直接返回一组固定的模拟余额,让前端流程先完整跑通。

服务端的 Mock 逻辑一般长这样:

if config.get('mock'): return { 'balance': '88.60', 'unit': 'yuan', 'update_time': int(time.time()) }

这段代码的含义是:只要配置里mock是true,forward_channel就直接返回一个写死的余额结果,不再走网络请求。这样在没有拿到上游通道地址之前,也能验证页面渲染、错误提示、号码校验这些逻辑是否正确。

切换真实通道时,把config.json里channel对应键位的地址替换成自己的网关地址,再把mock改成false即可。替换后建议先用curl单独测一遍接口,确认上游通了,再回到页面上做完整操作。

提示:Mock 返回的数据字段务必和真实通道保持一致,否则切换时会漏改前端或清洗逻辑。

3.3 联调验证:curl 命令、浏览器 Network 面板与返回字段检查

服务启动后,先在终端里用curl做一次接口自测,这能帮助你快速判断是接口问题还是页面问题:

curl 'http://127.0.0.1:8000/query?phone=13800138000'

正常情况下会得到一个 JSON,结构类似这样:

{ "code": 0, "msg": "success", "data": { "phone": "13800138000", "operator": "cmcc", "balance": "88.60", "unit": "yuan", "update_time": 1750000000 } }

拿到响应后依次检查三点:code是否为 0;operator是否和号段对应,这里138开头应为cmcc;balance是否是一个合理的数值。update_time是秒级时间戳,确认它和当前时间差距不大,避免拿到缓存过期或上游时间异常的数据。

curl 通了之后再打开浏览器访问页面,输入号码点击查询,按 F12 打开 Network 面板,找到名为query的请求,看它的响应体是否和 curl 结果一致。如果页面报错而 curl 正常,问题一般出在前端代码;如果 curl 就报错,问题在后端或上游通道。用这个顺序排查,大多数联调问题都能在十分钟内定位。

4. 常见问题与避坑:号段误判、接口限流和返回格式差异

这类查询项目用起来不复杂,但真跑到线上,踩坑的点往往集中在号段、限流和返回格式三块。这一章直接把常见问题按“现象 → 原因 → 解决”列出来,每条都是实际开发里会遇到的。

4.1 号段更新不及时导致运营商识别错误

现象:输入一个较新的手机号,页面提示“无法识别运营商”,或者把号码识别成错误的运营商,查询结果自然也不对。

原因:运营商不定期放新号段,而项目里的号段表是静态维护的,一段时间不更新就会出现匹配不到或误匹配的情况。

解决:定期更新number_prefix.json。项目通常会附带一个更新脚本,常见命令是:

python check_operator.py --update-prefix

脚本会从上游拉取最新号段表并覆盖本地文件。如果项目没带脚本,手动编辑 JSON 时注意保持格式一致。另外,前端号段识别只用于展示,路由和查询必须以后端接口为准,不要在页面里把“号段等于运营商”当成绝对规则写死。

4.2 查询频率过高触发通道限制

现象:连续查询五六个号码后,接口返回“请求过于频繁”,上游通道不再响应,过一段时间才恢复。

原因:聚合查询通道对单 IP 或单个账号有频率限制,短时间请求过多会被判定为异常访问。

解决:在后端加本地缓存,同一个号码在cache_ttl时间内直接返回上次结果,不重复请求上游:

cache.set(phone, result, ex=cache_ttl)

同时把rate_limit配置用起来,在接口入口做每秒最大查询数限制,超出时直接返回“请稍后再试”。遇到上游偶尔熔断,可以做一次退避重试,但如果连续失败两次就不要再试了,等窗口期过去。真实项目里因为限流导致的服务不可用,大多不是上游故意刁难,而是自己没做缓存。

4.3 携号转网号码按号段查询失败

现象:一个188开头的号码在页面上被识别成移动,点击查询却提示“号码不存在”,或者直接查询失败。188确实是移动的老号段,但这个号码可能已经携号转网到联通或电信。

原因:识别依据是号段归属,而不是号码当前实际所属网络。携号转网之后,号码的网络归属和号段归属不一致了。

解决:接口侧在按原运营商通道查询失败后,可以再做一次“换通道重试”,并给前端返回一个提示字段:

try: return forward_channel(cmcc_url, phone) except ChannelError: data = forward_channel(ctcc_url, phone) data['hint'] = '该号码可能已携号转网,本次按电信通道查询' return data

对用户体验来说,提示“号码可能已携号转网”要比强行给出错误结果好得多。注意重试会增加一次上游请求,要结合频率限制合理使用,不能无条件重试两三次。

4.4 余额单位不一致和缺失数据

现象:查询不同号码时,返回的balance有时是88.60,有时是8860,个别号码还返回--或空字符串。

原因:不同通道的余额字段单位不一样,有的以元返回,有的以分返回;已经销号或欠费异常的号码则可能返回占位符。

解决:在normalize_result清洗层统一处理。如果unit是fen,就除以 100 转成元;如果balance是--、空字符串或None,直接置为null,并让msg提示“暂无数据”。不要在页面上把--原样渲染给用户看。

5. 核心模块代码拆解:一次余额查询的完整请求链路

前面章节已经把整体结构讲清楚了,这一章深入到实现层,把一次查询从“号码进来”到“结果返回”的完整请求链路拆成三个核心模块:号码路由、请求转发、响应清洗。理解了这三段代码,二次开发基本就有谱了。

5.1 号码路由模块:从号段表到通道选择

号码路由是整个链路的入口,职责是根据手机号前三位判断走哪家运营商的通道。Python 实现常见做法是加载号段表后做交集判断:

def route_by_number(phone: str) -> str: prefix = phone[:3] if prefix in NUMBER_PREFIX['cmcc']: return 'cmcc' if prefix in NUMBER_PREFIX['ctcc']: return 'ctcc' if prefix in NUMBER_PREFIX['cucc']: return 'cucc' raise UnknownOperatorError(f'未识别的号段: {prefix}')

参数phone是校验过格式的 11 位手机号,函数先截取前三位prefix,再依次在三个运营商的号段字典里查找。命中哪个就返回哪个运营商的短标识,三个都不命中的时候抛出异常。

这里有个设计细节值得注意:路由失败时选择抛异常,而不是默认返回cmcc或者其他运营商。原因很简单,一个查不到号段的号码如果被强行路由到错误通道,最后拿到的结果不会是对的,反而浪费一次上游请求,还会让用户看到无效数据。抛出异常后,接口层可以把它转成一个明确的错误提示,比如“无法识别该号码的运营商,请核对号码”,这对排查问题更友好。

5.2 请求转发与超时控制:把上游错误挡在返回层之前

路由完成后,服务端要根据配置的通道地址发起真实请求。这里需要重点处理超时和异常场景,避免上游慢吞吞拖死整个页面。常见写法如下:

def forward_channel(channel_url: str, phone: str, timeout: float = 8.0) -> dict: try: resp = requests.post(channel_url, json={'phone': phone}, timeout=timeout) resp.raise_for_status() return resp.json() except requests.exceptions.Timeout: raise QueryTimeoutError('上游通道超时') except requests.exceptions.HTTPError: raise GatewayError('上游通道返回错误状态码') except requests.exceptions.JSONDecodeError: raise GatewayError('上游返回的不是合法JSON')

参数channel_url是config.json里配置的通道地址,phone是要查询的号码,timeout默认 8 秒。请求用 POST 上传 JSON 体,把手机号放在phone字段里,这是多数查询通道的通用协议。

超时时间设置成 8 秒而不是更长,是因为客服场景下用户等太久就会关闭页面或重复点击。上游正常返回一般在 2~5 秒内,8 秒已经能覆盖绝大多数情况,超过就认定为失败。异常处理上,超时、HTTP 错误码、非法 JSON 三种情况分别抛不同类型异常,接口层可以针对不同异常给出不同提示,也方便后续做监控告警。

实际操作中还有一个细节:不要在这里捕获所有异常然后返回一个空字典。如果上游失败却返回空结构,清洗层会把这个空结构当成正常结果处理,用户看到的是“成功但没数据”的假象,比直接报错更难排查。宁可异常向上抛,也要保证每次成功返回的都是真实数据。

5.3 响应清洗与统一返回:把各家结果规范成同一个JSON

上游返回的数据格式往往五花八门,有的把余额嵌在balance字段,有的嵌在data.balance,最近还要处理单位换算和缺省值。清洗层做的事情就是把这些差异收敛成统一结构:

def normalize_result(raw: dict, operator: str, phone: str) -> dict: balance = raw.get('balance') or raw.get('data', {}).get('balance') unit = raw.get('unit', 'yuan') if balance in (None, '--', ''): balance = None elif unit == 'fen': balance = round(float(balance) / 100, 2) return { 'phone': phone, 'operator': operator, 'balance': balance, 'unit': 'yuan', 'update_time': raw.get('update_time', int(time.time())) }

这段代码里,balance = raw.get('balance') or raw.get('data', {}).get('balance')兼容了两种常见的上游返回结构;unit == 'fen'时把分转成元并保留两位小数;balance为None、'--'、空字符串时统一置为None,避免把无意义字符传给前端。

update_time字段的处理值得展开说:优先使用上游返回的时间戳,只有上游没给时才用服务器当前时间。原因是查询结果具有时效性,如果上游返回的是后台缓存的数据,用服务器当前时间会掩盖数据陈旧的问题;做台账或趋势分析时,时间不对会导致统计错位。

清洗完成后,接口层把结果包进统一 JSON 返回。到这里,一次查询从号码进入到页面展示的完整链路就闭合了:校验、路由、转发、清洗四个环节各自独立,互不耦合,任何一个环节出了问题都能单独定位。

6. 二次开发方向:台账记录、余额预警与更稳的查询习惯

跑通查询功能只是第一步,这类项目真正有价值的地方在于怎么把“查询”变成一个持续使用的服务。两个最常见的改造方向是:把查询结果落库做台账,以及定时查询做低余额预警。

落库是最直接的刚需。客服平台里每次查询都应该留下记录,方便后续核对“谁、什么时候、查了哪个号码、余额是多少”。在项目里加一张简单的 SQLite 表:

CREATE TABLE IF NOT EXISTS balance_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, phone TEXT NOT NULL, operator TEXT, balance REAL, update_time TEXT );

接口每次成功查询后插入一条记录,就能自然形成查询台账。更进一步,可以写一个定时任务,每天凌晨对一组常用号码做批量查询,把结果持续写入这张表,积累一段时间后就能看到余额变化趋势,再配合阈值判断实现低余额提醒。

预警功能可以挂在定时任务后面。批量查询完成后,检测余额低于阈值(比如 30 元)的号码,通过企业微信或钉钉机器人推送一条提醒消息。推送逻辑不复杂,本质是把告警文本 POST 到机器人 Webhook 地址,难的是避免重复告警,所以建议把“已提醒状态”也落到表里,例如追加一个alerted字段,当天提醒过的号码不再重复推送。

说到最后的习惯,我以前在公司内部工具里吃过一亏:上游通道返回的update_time有的是 GMT 字符串,有的是毫秒时间戳,我当时图省事统一用服务器当前时间覆盖,结果月底对账时台账时间全乱了。从那以后我每次接查询类接口,都强制要求响应里带上请求 ID 和统一格式的时间戳,处理完上游数据后先把这两样落库,再谈展示。这个习惯也推荐你保留。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询