简介:通过手机号识别微信性别的 Python 实例代码,面向需要在微信自动化操作中提取用户基础信息的测试、爬虫或运营开发者。代码基于 uiautomator 操作安卓端微信:先定位搜索框输入手机号,再点击搜索并调用 Watcher 监听异常弹窗;针对“用户不存在”“被搜账号状态异常,无法显示”“操作频繁”“用户未设置性别”等情况分别返回对应结果,避免自动化流程中断。除 getGender 核心方法外,还提供 write_excel_xls、write_excel_xls_append、read_excel_xls 等函数,支持将识别结果按 boy、girl、NotSet、NotExist 等分表新建或追加写入 Excel,方便批量处理手机号清单后的数据汇总。资源为 1 个 PDF 文件,压缩包约 51KB,轻量可读,适合有一定 Python 基础的读者对照实现。已有 1394 人浏览学习,可作为理解安卓 UI 自动化、Watcher 异常捕获与表格持久化的入门参考。
2. 手机号识别性别:这个需求是怎么来的,又要怎么落地
先把结论放在前面:用 Python 通过手机号直接识别出对应的微信性别,在正规接口和公开数据层面,做不到。我研究这个标题研究了很久,也翻了不少技术社区里类似的提问,发现这是一个非常典型的“需求描述——技术实现”错位问题。大家在日常产品设计里遇到的真实需求,被简化成了“输入手机号→输出性别”这样一个二元结构,但微信生态下的数据权限模型,并不支持这种查询方式。
那这篇文章到底要聊什么?我不是来讲“做不到”三个字就结束的。我想把这件事拆开,从需求来源、技术边界、可落地的合规方案,再到具体的 Python 实例代码,完整地过一遍。适合谁看?如果你是做用户运营、CRM 系统、活动报名工具、或者任何需要“通过手机号完善用户画像”的产品经理或 Python 开发者,这篇文章能帮你少走很多弯路。特别是那些准备用爬虫去“撞”数据的朋友,我劝你先看完第三部分和第六部分再说。
我会先讲这个需求产生的真实业务背景,再解释为什么微信的数据权限模型决定了手机号和性别之间不存在“可查询”的映射关系,然后给出一套替代方案,最后用完整的 Python 代码示例演示合规的做法。
3. 手机号和性别之间的“信息孤岛”:为什么微信不愿意让两者直接挂钩
3.1 微信生态里的用户身份体系:OpenID、UnionID 和用户授权
想要理解这个问题,先得弄明白微信是怎么管理用户信息的。微信生态里有两套核心的身份标识:OpenID和UnionID。OpenID 是某个公众号或小程序下的用户唯一标识,同一个用户在不同公众号下的 OpenID 不同;UnionID 则是在同一个微信开放平台账号下,绑定多个公众号或小程序后,用来标识同一用户的唯一身份。
关键点来了:无论是 OpenID 还是 UnionID,都是由微信服务器生成的随机字符串,它们和手机号、微信号、手机设备信息之间没有任何公开的映射接口。你想通过手机号反查 OpenID,微信官方没有开放这样的接口;你想通过 OpenID 反查手机号,同样不行。唯一合法的路径是:用户主动授权,允许你的应用读取他的某些基础信息(如头像、昵称、性别),或者通过微信的“手机号快速验证”组件获取用户绑定的手机号。
有一个简单的类比:手机号相当于你家的门牌号,微信性别信息相当于你家里客厅摆放的物品。门牌号是登记在物业那里的,客厅物品只有你自己知道或者你愿意告诉别人。没有任何公共机构会提供一个“输入门牌号就返回客厅摆设清单”的服务,微信也一样。
3.2 “手机号查性别”这个需求,在业务上通常对应什么场景
我在实际工作中遇到过很多次类似的诉求,基本都集中在三个场景:
第一个场景是存量用户信息补全。比如你有一份历史客户名单,里面有手机号、有过购买记录,但当时没有采集性别信息。运营希望批量补上,用于做差异化营销。这个诉求业务上很合理,但技术上没有合规路径——除非用户在小程序或公众号里重新授权。
第二个场景是新用户注册流程优化。App 或小程序里想要下拉手机号自动识别性别,做到“无感”登录的同时把画像建好。这个想法也很正常,但微信的授权粒度和这个需求不匹配。
第三个场景是外部数据查询工具。有些人想做一个商业化的工具,输入手机号就能返回对应的微信资料,用于销售线索评估。这个方向就不用想了,微信规则明确不允许,而且我后面会说,这通常意味着灰色数据交易。
这三个场景里,只有第二个能在合规框架内解决,但解决的思路不是“查”,而是“让用户告诉你”。
3.3 那些声称“能实现”的方案,本质上是什么
搜索引擎里能看到不少宣称“Python 通过手机号识别微信性别”的文章或代码,我建议你别信,也别试。仔细看它们的方法论,无非三种:
第一种是诱导绑定。做一个 H5 页面或者小程序,诱导用户输入手机号、授权登录,然后再把用户填写的性别存到自己的数据库里。这本质上是自己采集数据,不是“识别”。
第二种是撞库。用手机号遍历尝试登录或找回密码,试图通过接口报错差异判断账号是否存在。这是明确违反法律法规的行为,而且微信的风控系统对这种异常请求的检测非常严格。
第三种是利用已泄露的数据。从黑市买到手机号、微信 ID、性别的关联数据包,包装成“识别功能”。这种数据来源非法,买了就涉嫌犯罪。
我把话放这儿:真正能在线上实践“手机号→微信性别”且不违法的路径,一条都没有。如果有人卖你这样的 API 接口,请直接拉黑。不是技术不行,是这条路根本不合法、不合规。
4. 合规替代方案:三个方向,让用户主动告诉你答案
既然不能“查”,那就得换个思路。我在做用户画像类需求时,通常会推荐三种替代方案,它们都基于微信官方能力,不需要走任何灰色路线。
第一种是微信小程序手机号快速验证组件 + 用户自填性别。这是最推荐的方式。小程序里有一个“手机号快速验证”组件,用户点击后,微信会弹窗让用户确认授权手机号。拿到手机号的同时,再引导用户选择性别(或读取用户主动填写的性别字段),一个字段不落全拿到。这里有个细节:用户授权手机号后,你可以拿到的是手机号,性别仍然需要用户主动提供,没有捷径。
第二种是关注公众号后的事件推送 + 用户信息授权。用户关注公众号时,微信会推送一个关注事件,你可以通过接口获取用户的 OpenID,并引导用户点击菜单或回复关键词触发授权链接。在授权页里,你可以申请获取用户的性别信息。这种方式的缺点是路径比较长,用户流失率较高。
第三种是通过 UnionID 打通自有账号体系。如果你的产品已经接入微信开放平台,并且用户之前授权过性别信息,那么同一个用户在不同入口(公众号、小程序、App)下的 UnionID 是一致的。你可以建立一个用户ID→性别的本地映射表,下次再遇到同一个 UnionID,直接从库里读就行,不需要重新询问。这一步需要自己有数据沉淀,本质上是“一次采集,多次复用”。
这三种方案都遵循一个核心原则:用户知情并同意。这不是微信小气,而是对用户隐私的基本尊重——你在设计功能的时候也不希望别人拿你的手机号去反查你的性别吧。
5. 用 Python 实现一个“手机号 + 性别画像”的完整实例
这一部分我们来写代码。我会搭建一个模拟业务场景的 Python 应用,它做的事情是:输入手机号 → 校验手机号格式 → 在本地数据库(模拟)中查找该手机号对应的用户性别 → 如果找不到,则生成一个“待用户授权”的记录,等待前端回调。这个示例完整展示了合规逻辑下,手机号和性别之间的关联是如何建立的。
5.1 环境准备与依赖安装
我建议你用 Python 3.10+,因为新版本对类型注解和数据类的支持更好。需要安装的第三方库也不多:
pip install fastapi uvicorn pydantic phonenumbers安装完成后,你可以通过下面的代码快速验证环境是否正常:
import phonenumbers print(phonenumbers.__version__)如果输出了版本号(例如 8.13.0 左右),说明环境没问题。
5.2 手机号格式校验与归属地解析
手机号识别最基础的一步是判断手机号本身是否合法。很多人会直接写正则表达式,比如^1[3-9]\d{9}$,这确实能匹配中国大陆的手机号格式,但不严谨——它无法区分“号段合法”和“号码真实存在”。在工程实践里,我更推荐用phonenumbers库来做国际化的号码校验。
import phonenumbers from phonenumbers import carrier, geocoder, timezone def parse_phone(phone_str: str, region: str = "CN"): """ 解析并校验手机号 :param phone_str: 原始手机号字符串 :param region: 默认中国大陆 :return: 规范化后的手机号字符串 """ try: number = phonenumbers.parse(phone_str, region) except phonenumbers.NumberParseException: raise ValueError("手机号格式无法解析,请检查输入") if not phonenumbers.is_valid_number(number): raise ValueError("手机号无效,请确认号码是否正确") # 判断是否为移动电话 if not carrier.is_mobile_number(number): raise ValueError("该号码不是移动电话号段") # 获取归属地和时区(用于辅助判断) location = geocoder.description_for_valid_number(number, "zh") zones = timezone.time_zones_for_number(number) # E.164 格式,统一存储格式 normalized = phonenumbers.format_number(number, phonenumbers.PhoneNumberFormat.E164) return normalized, location, list(zones)这段代码做了三件关键事情:格式解析、有效性校验、是否为移动号段。它比正则靠谱得多,因为phonenumbers库内置了各运营商号段数据,可以识别哪些号段真实存在。
5.3 使用 FastAPI 搭建“手机号画像查询”接口
接下来我用 FastAPI 搭建一个简单的 Web 服务,演示“输入手机号→查询本地用户库→返回性别画像”的完整链路。注意,这里不会去微信服务器拉任何数据,而是模拟“用户已经在前端授权并提交了性别信息”之后的后端逻辑。
from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import Optional from datetime import datetime import sqlite3 import json app = FastAPI(title="用户画像服务") class PhoneRequest(BaseModel): phone: str scene: Optional[str] = "default" class UserProfileResponse(BaseModel): phone: str gender: Optional[str] source: str is_bound: bool def get_db_connection(): conn = sqlite3.connect("user_profiles.db") conn.row_factory = sqlite3.Row return conn def init_db(): conn = get_db_connection() conn.execute(""" CREATE TABLE IF NOT EXISTS user_profiles ( phone TEXT PRIMARY KEY, gender TEXT, source TEXT, created_at TEXT, updated_at TEXT ) """) conn.execute(""" CREATE TABLE IF NOT EXISTS user_auth_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, phone TEXT, scene TEXT, auth_status TEXT, created_at TEXT ) """) conn.commit() conn.close() @app.on_event("startup") def startup(): init_db() @app.post("/api/profile", response_model=UserProfileResponse) def get_user_profile(req: PhoneRequest): # 第一步:校验并规范化手机号 try: normalized, location, _ = parse_phone(req.phone) _ = location # 归属地信息可用于业务侧运营分析 except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) # 第二步:查询本地数据库 conn = get_db_connection() row = conn.execute( "SELECT phone, gender, source FROM user_profiles WHERE phone = ?", (normalized,), ).fetchone() if row is None: # 未找到记录:说明该用户尚未授权,需要返回一个标记 # 前端拿到这个标记后,应引导用户完成微信授权 conn.execute( "INSERT INTO user_auth_logs (phone, scene, auth_status, created_at) VALUES (?, ?, ?, ?)", (normalized, req.scene, "pending", datetime.now().isoformat()), ) conn.commit() conn.close() return UserProfileResponse( phone=normalized, gender=None, source="none", is_bound=False, ) conn.close() return UserProfileResponse( phone=row["phone"], gender=row["gender"], source=row["source"], is_bound=True, )这一步有几个值得注意的设计选择:
- 我把“查询不到”和“查询到但无性别”区分开了。前者返回
source="none"和is_bound=False,前端可以据此弹窗让用户去授权;后者意味着用户曾经授权过但没有提供性别,仍然可以引导用户补充。 - 数据库里
user_profiles表专门存放用户主动授权后回传的数据。这张表的数据来源只有一个:微信授权回调,不会是爬虫或其他手段。 - 我还加了一张
user_auth_logs表记录每次查询的状态,方便后续观察转化率。比如你做了“手机号输入→引导授权”的流程,这张日志表能告诉你每一步流失了多少人。
5.4 编写微信小程序端授权回调的数据写入脚本
现在来写前端的对接逻辑。假设你的业务是小程序,用户在页面里输入手机号,点击“微信授权”按钮。前端会调用wx.login获取临时凭证,然后请求你的后端接口,再让用户确认授权。最终微信服务器会把用户的手机号和性别信息通过回调发给你。你需要一个端点在本地记录这些授权回传的数据。
下面代码演示了模拟微信服务器回调的逻辑,方便你在本地演示整个流程:
from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter(prefix="/callback") class GenderCallback(BaseModel): phone: str gender: str source: str = "wechat_oauth" @router.post("/gender") def callback_gender(req: GenderCallback): # 这里要严格校验来源:在生产环境中应验证微信签名 # 防止恶意请求伪造手机号-性别映射 if req.gender not in ("male", "female", "unknown"): raise HTTPException(status_code=400, detail="性别字段不合法") try: normalized, _, _ = parse_phone(req.phone) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) conn = get_db_connection() conn.execute( """ INSERT INTO user_profiles (phone, gender, source, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(phone) DO UPDATE SET gender = excluded.gender, source = excluded.source, updated_at = excluded.updated_at """, (normalized, req.gender, req.source, datetime.now().isoformat(), datetime.now().isoformat()), ) conn.commit() conn.close() return {"code": 0, "message": "回调成功", "phone": normalized, "gender": req.gender}这段代码里我特意做了两个关键处理:
一是校验性别枚举值。微信授权回调返回的性别只有三个值可选:male(男)、female(女)、unknown(未知)。如果你发现一方传了1或2,那也是对的——微信原始接口里1表示男性,2表示女性,0表示未知,但在存储时最好统一转成可读的字符串。
二是使用ON CONFLICT做幂等写入。同一个手机号用户可以多次更换授权信息,用INSERT OR REPLACE会丢历史记录,而ON CONFLICT DO UPDATE能保留created_at、更新updated_at,这个细节在生产环境中很重要。
5.5 用命令行调用示例
服务搭好了,你可以用uvicorn main:app --reload启动,然后模拟一次完整的查询流程:
# 1. 查询一个尚未授权的手机号 curl -X POST http://127.0.0.1:8000/api/profile \ -H "Content-Type: application/json" \ -d '{"phone": "13812345678", "scene": "register"}'预期返回:
{ "phone": "+8613812345678", "gender": null, "source": "none", "is_bound": false }此时数据库里的user_auth_logs表会新增一条pending状态记录。
# 2. 模拟微信授权回调,写入性别信息 curl -X POST http://127.0.0.1:8000/callback/gender \ -H "Content-Type: application/json" \ -d '{"phone": "13812345678", "gender": "female", "source": "wechat_oauth"}'预期返回:
{ "code": 0, "message": "回调成功", "phone": "+8613812345678", "gender": "female" }再查询一次,就能看到完整的画像数据了:
{ "phone": "+8613812345678", "gender": "female", "source": "wechat_oauth", "is_bound": true }整个流程走下来,你会发现:性别是用户主动给你的,而不是你“识别”出来的。但业务效果完全一样——到最后你确实可以通过手机号查到这位用户的微信性别。
6. 常见问题与排查技巧实录
我把自己历次做类似需求时踩过的坑、被问过的问题整理了一下,做成一个速查表,方便你排查问题。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 手机号校验不通过 | phonenumbers 库没有最新号段数据 | 升级到最新版:pip install -U phonenumbers |
| 回调成功但数据库没有写入 | 事务未提交 | 检查是否调用了conn.commit() |
| 同一个用户重复授权后被覆盖 | 缺少幂等设计 | 用ON CONFLICT DO UPDATE而不是直接INSERT |
| 查询接口一直返回未授权 | 前端没有把授权信息回传 | 检查授权回调是否真的被触发,查看user_auth_logs表 |
| 微信接口报 40163 错误 | code 已过期或重复使用 | 确保每个code只能使用一次,重新调用wx.login |
| 用户性别返回 unknown | 用户主动拒绝提供或微信侧没有记录 | 按“未知”处理,不要反复弹窗让用户补充 |
6.1 爬虫方向的边界警示
我相信点开这篇文章的人里,肯定有相当一部分是抱着“用 Python 爬微信数据”的心态来的。我必须把边界说清楚:
第一,公开 API 是唯一合法的数据获取渠道。微信没有任何公开接口支持“手机号反查用户资料”,所有绕过官方接口获取数据的方式,都违反了微信平台规则,也大概率违反《个人信息保护法》。
第二,爬虫能爬到的只有公开可访问的内容。微信朋友圈、公众号文章、视频号信息这类公开内容,理论上可以用爬虫采集,但手机号、微信 ID、性别的关联关系,根本不在公开范围内。它们只存在于微信服务器和用户自有数据里,你爬不到。
第三,风险成本远超收益。微信的风控体系会记录异常的接口调用、异常频率和异常的请求模式。一旦触发风控,轻则封禁接口权限,重则账号被冻结,甚至承担法律责任。做产品要算的是长期账,不是一次性把用户数据薅秃的账。
6.2 高效排除“授权成功但回调失败”的代码细节
最后分享一个高频问题:为什么用户在小程序里明明点了授权,后端却收不到回调?
按照我的排查经验,80% 的情况是服务端没处理好“静默登录”和“授权登录”的区别。正确顺序应该是:
- 前端调用
wx.login()拿到临时code - 后端拿着
code调用微信的code2Session接口,换取openid和session_key - 前端调用
wx.getUserProfile()或手机号快速验证组件,拿到加密数据 - 前端把加密数据回传给后端,后端用
session_key解密,得到手机号和性别
很多初学者在第 1、2 步就以为已经拿到了用户信息,其实那只是登录凭证。用户信息需要单独授权,且授权动作必须由用户主动触发。
7. 写在最后的实操心得
把这套方案完整落地之后,我最大的体会是:技术方案的上限不是代码能力,而是数据合规的边界。很多功能需求说出来让人觉得“有点意思想实现一下”,但真要去接微信生态的数据,必须时刻提醒自己,哪些数据可以碰、哪些数据绝对不能碰。
我在实际项目中还做了一个小优化,可以分享给有同样需求的开发者:在user_auth_logs表里增加一个utm_source字段,记录用户是从哪个渠道进入授权流程的。这样运营侧就能看到“短信引导授权”和“公众号菜单引导授权”两个入口各自的转化率,用来优化引导文案和入口位置。这个字段加得值。
最后再提一句:如果团队里有人在评估“买一个手机号-性别数据库”来填充画像,我非常不建议。这种数据源合规风险极高,而且数据的时效性和准确率并无保障,大概率是花钱买麻烦。合规的方案虽然慢,但每一步都是自己的资产,跑得踏实。
本文还有配套的精品资源,点击获取