OSINT 侦探工具:一个用户名揭开全网身份
你只需要一个用户名,Maigret 就能在 3000+ 个网站上找到那个人的数字足迹 —— 不需要任何 API Key,完全免费。
想象你是侦探,用户名就是指纹
每个人在互联网上都会留下痕迹:注册过社交媒体、发过帖子、留下过头像和昵称。Maigret 做的事情,就像一个自动化侦探 —— 你给它一个用户名,它就去数千个网站上比对:这个名字在这里存在吗?如果能找到,它还会把公开的个人资料信息全部收集起来。
3000+ 网站覆盖
从主流社交平台到小众论坛,内置庞大的网站数据库,默认扫描 500 个高流量站点
递归追踪
发现新用户名?自动追踪下去。一个名字可能牵出整个身份网络
AI 智能分析
可选 AI 模式,自动把原始发现整理成易读的调查摘要
多格式报告
PDF、HTML、XMind 思维导图、CSV 表格,任你选择
就是这么简单:一行命令启动调查
安装只需要一条 pip 命令,运行也只需要提供用户名:
# 安装 Maigret
pip install maigret
# 搜索一个用户名
maigret john_doe
# 扫描全部 3000+ 网站(默认只扫 500 个)
maigret john_doe -a
# 只扫描特定分类的网站
maigret john_doe --tags photo,video
用 pip 从 PyPI 安装 Maigret 包...
搜索用户名 john_doe,在 500 个高流量网站上查找...
加 -a 标志表示扫描所有 3000+ 个网站...
用 --tags 过滤只搜索照片和视频类网站...
Maigret 不需要任何 API Key 或账号登录。它模拟浏览器直接访问公开页面,通过分析页面内容来判断用户是否存在。这是一种"被动 OSINT"技术 —— 只看不破坏。
Maigret 的内部组成:侦探团队分工
Maigret 不是一个大文件,而是一个由专业模块组成的团队。每个模块就像侦探事务所的一个部门:
核心引擎
支撑系统
追踪一次搜索的完整旅程
当你输入 maigret john_doe 后,数据在各个模块之间如何流转?点击"下一步"观看:
源码文件结构一览
如果你想用 Maigret 搜索某个用户名,但没有任何 API Key 或账号,你能正常使用吗?
Maigret 能同时向数百个网站发起请求。哪个模块负责控制'同时能发多少个请求'这个并发上限?
网站数据库引擎:3000 个网站的情报手册
每个网站的结构都不一样 —— 用户页面在哪、怎么判断用户是否存在、需要什么特殊的 HTTP 头。Maigret 把这些差异全部编码成数据,让检测逻辑只需关注一件事。
每个网站都是一张"档案卡"
想象你是一个私人侦探,有一个巨大的卡片柜,每张卡片记录了一个网站的"性格":用户页面长什么样、用什么地址模板、哪种回复代表"找不到人"。Maigret 里的 MaigretSite 类就是这张卡片:
class MaigretSite:
# 已确认存在的用户名(用于自检)
username_claimed = ""
# 已确认不存在的用户名
username_unclaimed = ""
# URL 模板,{username} 会被替换
url = ""
# 网站分类标签
tags: List[str] = []
# 标识符类型
type = "username"
# 页面中"用户存在"的线索文字
presense_strs: List[str] = []
# 页面中"用户不存在"的线索文字
absence_strs: List[str] = []
定义一个网站的数据模型...
存储一个已知存在的用户名,用来测试检测逻辑是否正常...
存储一个已知不存在的用户名,也是自检用的...
URL 模板,比如 https://twitter.com/{username},花括号会被实际名字替换...
标签分类,比如 photo、video、social,方便按类别筛选...
标识符类型,大多数网站用 username,但也有用 Yandex ID、Steam ID 等...
存在线索:如果页面包含这些文字,说明用户名存在...
不存在线索:如果页面包含这些文字,说明用户名没人用...
不只是用户名:多种身份标识符
很多人以为 Maigret 只搜用户名,其实它支持 9 种不同的身份标识符:
username
最常见的用户名,如 john_doe
yandex_public_id
Yandex 平台的公开 ID
gaia_id
Google 账号的内部标识符
vk_id
俄罗斯社交网络 VK 的用户 ID
steam_id
Steam 游戏平台的用户 ID
ok_id
俄罗斯社交网络 Odnoklassniki 的 ID
# checking.py 中定义的所有支持的 ID 类型
SUPPORTED_IDS = (
"username",
"yandex_public_id",
"gaia_id",
"vk_id",
"ok_id",
"wikimapia_uid",
"steam_id",
"uidme_uguid",
"yelp_userid",
)
这是一个元组(不可变列表),列出所有 Maigret 能搜索的身份标识符类型...
大多数网站用 username,但有些平台有自己的 ID 系统...
这个列表也用于从页面中提取新的标识符,实现递归搜索...
一个人在 Twitter 上叫 john_doe,但在 Steam 上可能叫 JD_Steam。如果你只有 Steam ID,直接搜用户名就找不到。支持多种 ID 类型,让 Maigret 能从更多角度切入追踪。
自动更新的情报库
Maigret 的网站数据存在 JSON 文件里,每次运行时会自动检查 GitHub 上是否有更新版本(24小时检查一次)。如果离线,就用内置的数据库。这个机制就像导航软件的离线地图 —— 联网时自动更新,断网也能用。
每 24 小时从 GitHub 拉取最新网站数据库,新网站上线、旧网站变更都能及时跟进
如果无法联网获取更新,使用安装时内置的数据库继续工作
按国家、分类过滤网站,比如只搜 --tags cn 扫描中国网站
把网站属性拖到正确的位置
MaigretSite 的每个属性都有特定用途。试试把属性拖到正确的描述上:
定义用户页面地址模式,如 https://example.com/users/{username}
页面中出现这些文字,说明用户名已被占用(存在)
网站分类标签,用于按类别筛选扫描范围
需要特殊认证才能访问的网站(如获取临时 token)
如果你一周前安装了 Maigret,今天第一次运行。网站数据库会是最新版本吗?
如果某个网站返回的 HTML 中包含 "User not found",Maigret 的哪个属性会包含这个字符串?
并发检测流水线:同时向 500 个网站发请求
如果逐个网站检查,500 个站点可能要等 30 分钟。Maigret 用 asyncio 并发技术,几分钟搞定。但并发不是越多越好 —— 需要精巧的控制。
交通灯控制:并发不是无限制的
想象一个繁忙的高速公路收费站。如果所有车同时涌向收费口,反而会堵死。最好的方式是让一批车先通过,再放下一批。Maigret 的 Semaphore 就是这个收费站的控制灯:
class AsyncioSimpleExecutor:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 用信号量控制最大并发数
self.semaphore = asyncio.Semaphore(
kwargs.get('in_parallel', 100)
)
async def _run(self, tasks):
async def sem_task(f, args, kwargs):
# 获取信号量 —— 等待空位
async with self.semaphore:
return await f(*args, **kwargs)
futures = [
sem_task(f, args, kwargs)
for f, args, kwargs in tasks
]
# 同时启动所有任务,但信号量会限制并发
return await asyncio.gather(*futures)
定义一个异步执行器类...
初始化时创建一个信号量,默认最多允许 100 个并发...
sem_task 是一个包装函数...
async with semaphore 表示:如果有空位就继续,否则排队等待...
拿到空位后执行实际任务...
为所有任务创建带信号量控制的包装...
gather 同时启动全部,但信号量确保最多 N 个同时在跑...
并发太高会被目标网站封 IP(看起来像 DDoS 攻击),太低又浪费时间。100 是经过实战验证的经验值,兼顾速度和隐蔽性。用户可以通过 -n 参数调整。
检测员家族:不同的网站需要不同的检测方式
不是所有网站都能用同一种方式检查。Maigret 有多个"检测员"(Checker),每个擅长应对不同的防护措施:
SimpleAiohttpChecker
基础检测员。用 aiohttp 发 HTTP 请求,适用于大多数普通网站
CurlCffiChecker
伪装检测员。模拟浏览器的 TLS 指纹,绕过高级 WAF 防护
CloudflareBypass
Cloudflare 专家。专门突破 Cloudflare 的人机验证和防火墙拦截
AiodnsDomainResolver
域名检测员。不查网页,而是查域名是否存在(DNS 解析),用于域名搜索模式
检测过程中的组件对话
当 Maigret 开始检查一个网站时,内部组件之间是如何协作的?看这段"对话":
HTTP 请求的生命周期
每个检查员最终都要发 HTTP 请求。看看 SimpleAiohttpChecker 是怎么构建请求的:
async def _make_request(
self, session, url, headers,
allow_redirects, timeout, method, logger,
payload=None
):
if method.lower() == 'get':
request_method = session.get
elif method.lower() == 'post':
request_method = session.post
kwargs = {
'url': url,
'headers': headers,
'allow_redirects': allow_redirects,
'timeout': timeout,
}
async with request_method(**kwargs) as response:
status_code = response.status
content = await response.content.read()
decoded = content.decode(
response.charset or "utf-8", "ignore"
)
return decoded, status_code, error
定义异步请求方法,接收 session、URL、请求头等参数...
根据 method 决定用 GET 还是 POST...
把所有参数打包成字典...
用 async with 发送请求,等待响应...
获取 HTTP 状态码(200=成功,404=未找到)...
读取响应内容并解码为字符串...
返回:页面内容、状态码、错误信息...
Spot the Bug Challenge
下面这段代码模拟了请求异常处理的一部分。其中有一行存在隐患 —— 试着找出来:
找到这行代码中的隐患:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
connector = TCPConnector(ssl=ssl_context)
如果 Semaphore 设置为 100,Maigret 有 500 个网站要检查。以下哪个描述是正确的?
某个网站使用了高级 WAF 防护,普通 HTTP 请求直接被拦截。应该用哪个 Checker 来突破?
结果判定与报告:把发现变成证据
找到了用户名只是第一步。Maigret 还要从页面中提取个人信息、判断可信度、生成多格式报告 —— 把原始数据变成可用的情报。
四种判决:存在、不存在、未知、非法
每个网站的检查结果都会被归类为四种状态之一。就像法官的四种判决:
class MaigretCheckStatus(Enum):
# 用户名已被占用(找到了!)
CLAIMED = "Claimed"
# 用户名没人用(没找到)
AVAILABLE = "Available"
# 检查过程出错(不确定)
UNKNOWN = "Unknown"
# 用户名格式不合法
ILLEGAL = "Illegal"
用枚举(Enum)定义四种检查状态...
CLAIMED = 用户名在这个网站上存在,已被注册...
AVAILABLE = 这个用户名没人用,可以注册...
UNKNOWN = 出了问题(超时、被拦截),无法判断...
ILLEGAL = 这个网站不允许这种格式的用户名...
页面正常返回且包含存在线索文字,或 HTTP 状态码为 200 且没有不存在线索。这是最有价值的结果。
页面明确包含"用户不存在"的标志,或返回 404 状态码。说明这个用户名在该网站上没人用。
请求超时、被防火墙拦截、SSL 错误等。需要重试或换检测方式。未知结果不计入最终统计。
用户名包含该网站不允许的字符(比如用户名带 # 号),根本没法在这个网站上存在。
socid_extractor:从页面中榨取信息
判断用户存在只是开始。Maigret 还会调用 socid_extractor 从页面中提取额外信息 —— 姓名、头像、地理位置、关联的其他账号。这就是" dossier "(档案)的来源。
真实姓名、昵称、性别、年龄、个人简介
所在城市、国家、时区信息
个人主页上链接的其他社交平台用户名
Yandex ID、Google Gaia ID 等平台内部标识符
# 从 maigret.py 中的信息提取逻辑
from socid_extractor import extract, parse
def extract_ids_from_page(url, logger, timeout=5):
results = {}
page, _ = parse(url, cookies_str='',
headers=None,
timeout=timeout)
info = extract(page)
for k, v in info.items():
if 'username' in k:
results[v] = 'username'
if k in SUPPORTED_IDS:
results[v] = k
return results
导入信息提取工具库...
解析 URL 对应的网页内容...
从页面中提取所有可识别的个人信息...
如果提取到用户名类型的字段,加入结果字典...
如果提取到支持的 ID 类型,也加入结果...
返回所有发现的身份标识符
七种报告格式:总有一款适合你
数据收集完了,怎么呈现?Maigret 支持从纯文本到思维导图的多种输出方式:
HTML 报告
交互式网页,可点击查看每个网站的详细结果和提取数据
PDF 报告
正式文档格式,适合归档和分享给其他人
XMind 思维导图
以用户名为中心的放射状图谱,直观展示身份关联
CSV / JSON
结构化数据,方便导入 Excel 或其他分析工具
Graph 关系图
可视化关系网络,展示用户名之间的关联
Markdown
纯文本格式,适合在 GitHub 或笔记软件中查看
从原始结果到最终报告的完整流程
Maigret 检查某个网站时,请求超时了(服务器没响应)。结果会被标记为什么状态?
Maigret 从网页中提取用户真实姓名、头像链接、关联账号等信息。这个工作由谁完成?
如果你搜索后得到了 500 条结果,其中 50 条找到了用户,Maigret 默认按什么顺序排列这些结果?
递归搜索与 AI 增强:从一条线索展开整张网
最强大的不是搜一个用户名,而是搜一个名字就能自动找到关联的所有身份。再加上 AI 分析,让发现自己说话。
一条线索展开整张网:递归搜索
你搜索用户名 john_doe,在 Twitter 上找到了这个人。他的个人主页里写着"我是 johnd on Instagram"。Maigret 发现了这个关联,自动搜索 johnd,又在 Instagram 上找到了另一个账号,那里又关联了一个 Steam ID... 这就是递归搜索 —— 像滚雪球一样越滚越大。
搜索 john_doe
Twitter 发现 Instagram 用户名
自动搜索 Instagram
发现 Steam ID
继续追踪...
# 从 maigret.py 中提取新发现的身份标识符
def extract_ids_from_results(
results, db
):
ids_results = {}
for website_name in results:
dictionary = results[website_name]
if not dictionary:
continue
# 从页面中提取的用户名
new_usernames = dictionary.get(
'ids_usernames'
)
if new_usernames:
for u, utype in \
new_usernames.items():
ids_results[u] = utype
# 从链接中提取的 ID
for url in dictionary.get(
'ids_links', []
):
ids_results.update(
db.extract_ids_from_url(url)
)
return ids_results
定义函数,接收所有网站的检查结果和数据库对象...
遍历每个网站的检查结果...
如果这个网站没有返回数据,跳过...
检查是否有从页面中提取到的新用户名...
把每个新发现的用户名和类型加入结果...
检查页面中发现的链接...
从链接中解析出更多身份标识符...
返回所有新发现的身份标识符,用于下一轮搜索
如果不限制递归深度,理论上可能无限追踪下去。Maigret 用 --no-recursion 标志可以关闭递归,默认情况下会自动控制递归深度避免无限循环。
Permutator:用户名的排列组合术
有些人喜欢用不同的用户名变体:john_doe、johndoe、john.doe、john-doe。Maigret 的 Permutator 模块能根据已知信息自动生成这些变体,然后逐一检查:
class Permute:
def __init__(self, elements: dict):
# 支持的分隔符
self.separators = ["", "_", "-", "."]
self.elements = elements
def gather(self, method="strict"):
perms = {}
for i in range(1,
len(self.elements) + 1):
for subset in \
permutations(self.elements, i):
for sep in self.separators:
perm = sep.join(subset)
perms[perm] = \
self.elements[subset[0]]
return perms
Permute 类接收一组名字元素,比如 {"john": "name", "doe": "surname"}...
支持四种分隔符:无分隔、下划线、连字符、点号...
生成所有排列组合...
对每种排列,用不同分隔符组合...
比如 john+doe 会生成 johndoe、john_doe、john-doe、john.doe...
返回所有可能的用户名变体
AI 分析:让大模型帮你写调查报告
Maigret 的 --ai 模式会把所有搜索结果喂给 大语言模型,自动生成调查摘要。看它怎么工作的:
# ai.py 中的 API Key 解析逻辑
def resolve_api_key(settings) -> str | None:
"""Resolve OpenAI API key
Priority: settings > env var"""
key = getattr(settings, "openai_api_key",
None)
if key:
return key
return os.environ.get("OPENAI_API_KEY")
def load_ai_prompt() -> str:
"""Load system prompt"""
maigret_path = os.path.dirname(
os.path.realpath(__file__)
)
prompt_path = os.path.join(
maigret_path, "resources",
"ai_prompt.txt"
)
with open(prompt_path, "r") as f:
return f.read()
解析 AI 服务的 API Key...
优先从 Maigret 配置文件读取,其次从环境变量读取...
加载 AI 系统提示词(告诉 AI 如何分析搜索结果)...
从 resources 目录读取预置的 prompt 文件...
这个 prompt 会指导 AI 提取关键发现、分析关联、评估风险
实战技巧:用好 Maigret 的进阶功能
--tags photo
只搜索照片类网站(Flickr、500px、Instagram 等),缩小范围提高精度
--proxy socks5://127.0.0.1:9050
通过 Tor 代理访问,隐藏你的真实 IP
--timeout 30
增大超时时间,适合网络较慢或目标网站响应慢的场景
--ai
启用 AI 分析模式,需要设置 OPENAI_API_KEY 环境变量
--use-disabled
包含已知不稳定或已弃用的网站(默认跳过这些网站)
--cookie-jar cookies.txt
加载 cookies 文件,用于需要登录才能查看的网站
先用默认设置(500 个网站)快速扫描一轮,看看结果。如果发现了有价值的信息,再用 --tags 精准搜索特定类别,或用 -a 扫描全部 3000+ 网站。最后用 --ai 让 AI 帮你整理成报告。这样的"漏斗式"搜索策略效率最高。
三种使用模式:命令行、库调用、Web 界面
Maigret 不只是命令行工具,它还支持三种不同的使用方式:
命令行模式最简单直接,适合日常使用