01

OSINT 侦探工具:一个用户名揭开全网身份

你只需要一个用户名,Maigret 就能在 3000+ 个网站上找到那个人的数字足迹 —— 不需要任何 API Key,完全免费。

想象你是侦探,用户名就是指纹

每个人在互联网上都会留下痕迹:注册过社交媒体、发过帖子、留下过头像和昵称。Maigret 做的事情,就像一个自动化侦探 —— 你给它一个用户名,它就去数千个网站上比对:这个名字在这里存在吗?如果能找到,它还会把公开的个人资料信息全部收集起来。

🔍

3000+ 网站覆盖

从主流社交平台到小众论坛,内置庞大的网站数据库,默认扫描 500 个高流量站点

🔗

递归追踪

发现新用户名?自动追踪下去。一个名字可能牵出整个身份网络

🧠

AI 智能分析

可选 AI 模式,自动把原始发现整理成易读的调查摘要

📊

多格式报告

PDF、HTML、XMind 思维导图、CSV 表格,任你选择

就是这么简单:一行命令启动调查

安装只需要一条 pip 命令,运行也只需要提供用户名:

TERMINAL

# 安装 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.py 入口
🔎
checking.py 检测
📚
sites.py 网站库

支撑系统

📋
result.py 结果
executors.py 并发
📊
report.py 报告
点击任意组件查看它的职责

追踪一次搜索的完整旅程

当你输入 maigret john_doe 后,数据在各个模块之间如何流转?点击"下一步"观看:

👤
用户输入
📚
网站数据库
并发引擎
🔎
检测逻辑
📊
报告输出
点击"下一步"开始

源码文件结构一览

maigret/ 主包目录
maigret.py 命令行入口,参数解析,搜索编排
checking.py 核心检测逻辑,HTTP 请求,状态判定
sites.py 网站数据模型,数据库加载与查询
result.py 检查结果数据结构定义
executors.py 并发执行引擎,信号量控制
report.py 多格式报告生成(PDF/HTML/XMind 等)
activation.py 特殊网站的认证激活处理
ai.py AI 分析模块,OpenAI 兼容接口
permutator.py 用户名排列组合生成器
settings.py 配置加载(超时、并发数、代理等)
notify.py 终端彩色输出与通知
resources/ 内置网站数据库、AI prompt、默认配置
web/ Web 界面(图形化查看结果)

如果你想用 Maigret 搜索某个用户名,但没有任何 API Key 或账号,你能正常使用吗?

Maigret 能同时向数百个网站发起请求。哪个模块负责控制'同时能发多少个请求'这个并发上限?

02

网站数据库引擎:3000 个网站的情报手册

每个网站的结构都不一样 —— 用户页面在哪、怎么判断用户是否存在、需要什么特殊的 HTTP 头。Maigret 把这些差异全部编码成数据,让检测逻辑只需关注一件事。

每个网站都是一张"档案卡"

想象你是一个私人侦探,有一个巨大的卡片柜,每张卡片记录了一个网站的"性格":用户页面长什么样、用什么地址模板、哪种回复代表"找不到人"。Maigret 里的 MaigretSite 类就是这张卡片:

PYTHON

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
PYTHON

# 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 系统...

这个列表也用于从页面中提取新的标识符,实现递归搜索...

🔑
为什么多种 ID 很重要?

一个人在 Twitter 上叫 john_doe,但在 Steam 上可能叫 JD_Steam。如果你只有 Steam ID,直接搜用户名就找不到。支持多种 ID 类型,让 Maigret 能从更多角度切入追踪。

自动更新的情报库

Maigret 的网站数据存在 JSON 文件里,每次运行时会自动检查 GitHub 上是否有更新版本(24小时检查一次)。如果离线,就用内置的数据库。这个机制就像导航软件的离线地图 —— 联网时自动更新,断网也能用。

📡
自动更新

每 24 小时从 GitHub 拉取最新网站数据库,新网站上线、旧网站变更都能及时跟进

💾
离线回退

如果无法联网获取更新,使用安装时内置的数据库继续工作

🏷️
标签过滤

按国家、分类过滤网站,比如只搜 --tags cn 扫描中国网站

把网站属性拖到正确的位置

MaigretSite 的每个属性都有特定用途。试试把属性拖到正确的描述上:

url(URL 模板)
presense_strs
tags(标签)
activation(激活)

定义用户页面地址模式,如 https://example.com/users/{username}

拖到这里

页面中出现这些文字,说明用户名已被占用(存在)

拖到这里

网站分类标签,用于按类别筛选扫描范围

拖到这里

需要特殊认证才能访问的网站(如获取临时 token)

拖到这里

如果你一周前安装了 Maigret,今天第一次运行。网站数据库会是最新版本吗?

如果某个网站返回的 HTML 中包含 "User not found",Maigret 的哪个属性会包含这个字符串?

03

并发检测流水线:同时向 500 个网站发请求

如果逐个网站检查,500 个站点可能要等 30 分钟。Maigret 用 asyncio 并发技术,几分钟搞定。但并发不是越多越好 —— 需要精巧的控制。

交通灯控制:并发不是无限制的

想象一个繁忙的高速公路收费站。如果所有车同时涌向收费口,反而会堵死。最好的方式是让一批车先通过,再放下一批。Maigret 的 Semaphore 就是这个收费站的控制灯:

PYTHON

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 个同时在跑...

💡
为什么默认 100 而不是 500?

并发太高会被目标网站封 IP(看起来像 DDoS 攻击),太低又浪费时间。100 是经过实战验证的经验值,兼顾速度和隐蔽性。用户可以通过 -n 参数调整。

检测员家族:不同的网站需要不同的检测方式

不是所有网站都能用同一种方式检查。Maigret 有多个"检测员"(Checker),每个擅长应对不同的防护措施:

🌐

SimpleAiohttpChecker

基础检测员。用 aiohttp 发 HTTP 请求,适用于大多数普通网站

🔒

CurlCffiChecker

伪装检测员。模拟浏览器的 TLS 指纹,绕过高级 WAF 防护

🏰

CloudflareBypass

Cloudflare 专家。专门突破 Cloudflare 的人机验证和防火墙拦截

🌍

AiodnsDomainResolver

域名检测员。不查网页,而是查域名是否存在(DNS 解析),用于域名搜索模式

检测过程中的组件对话

当 Maigret 开始检查一个网站时,内部组件之间是如何协作的?看这段"对话":

HTTP 请求的生命周期

每个检查员最终都要发 HTTP 请求。看看 SimpleAiohttpChecker 是怎么构建请求的:

PYTHON

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

下面这段代码模拟了请求异常处理的一部分。其中有一行存在隐患 —— 试着找出来:

找到这行代码中的隐患:

1 ssl_context = ssl.create_default_context()
2 ssl_context.check_hostname = False
3 ssl_context.verify_mode = ssl.CERT_NONE
4 connector = TCPConnector(ssl=ssl_context)

如果 Semaphore 设置为 100,Maigret 有 500 个网站要检查。以下哪个描述是正确的?

某个网站使用了高级 WAF 防护,普通 HTTP 请求直接被拦截。应该用哪个 Checker 来突破?

04

结果判定与报告:把发现变成证据

找到了用户名只是第一步。Maigret 还要从页面中提取个人信息、判断可信度、生成多格式报告 —— 把原始数据变成可用的情报。

四种判决:存在、不存在、未知、非法

每个网站的检查结果都会被归类为四种状态之一。就像法官的四种判决:

PYTHON

class MaigretCheckStatus(Enum):
    # 用户名已被占用(找到了!)
    CLAIMED = "Claimed"
    # 用户名没人用(没找到)
    AVAILABLE = "Available"
    # 检查过程出错(不确定)
    UNKNOWN = "Unknown"
    # 用户名格式不合法
    ILLEGAL = "Illegal"
            
中文解释

用枚举(Enum)定义四种检查状态...

CLAIMED = 用户名在这个网站上存在,已被注册...

AVAILABLE = 这个用户名没人用,可以注册...

UNKNOWN = 出了问题(超时、被拦截),无法判断...

ILLEGAL = 这个网站不允许这种格式的用户名...

1
CLAIMED(找到了)

页面正常返回且包含存在线索文字,或 HTTP 状态码为 200 且没有不存在线索。这是最有价值的结果。

2
AVAILABLE(没找到)

页面明确包含"用户不存在"的标志,或返回 404 状态码。说明这个用户名在该网站上没人用。

3
UNKNOWN(不确定)

请求超时、被防火墙拦截、SSL 错误等。需要重试或换检测方式。未知结果不计入最终统计。

4
ILLEGAL(不合法)

用户名包含该网站不允许的字符(比如用户名带 # 号),根本没法在这个网站上存在。

socid_extractor:从页面中榨取信息

判断用户存在只是开始。Maigret 还会调用 socid_extractor 从页面中提取额外信息 —— 姓名、头像、地理位置、关联的其他账号。这就是" dossier "(档案)的来源。

👤
个人资料

真实姓名、昵称、性别、年龄、个人简介

🌍
地理位置

所在城市、国家、时区信息

🔗
关联账号

个人主页上链接的其他社交平台用户名

🔑
内部 ID

Yandex ID、Google Gaia ID 等平台内部标识符

PYTHON

# 从 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 默认按什么顺序排列这些结果?

05

递归搜索与 AI 增强:从一条线索展开整张网

最强大的不是搜一个用户名,而是搜一个名字就能自动找到关联的所有身份。再加上 AI 分析,让发现自己说话。

一条线索展开整张网:递归搜索

你搜索用户名 john_doe,在 Twitter 上找到了这个人。他的个人主页里写着"我是 johnd on Instagram"。Maigret 发现了这个关联,自动搜索 johnd,又在 Instagram 上找到了另一个账号,那里又关联了一个 Steam ID... 这就是递归搜索 —— 像滚雪球一样越滚越大。

1

搜索 john_doe

2

Twitter 发现 Instagram 用户名

3

自动搜索 Instagram

4

发现 Steam ID

5

继续追踪...

PYTHON

# 从 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 模块能根据已知信息自动生成这些变体,然后逐一检查:

PYTHON

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 模式会把所有搜索结果喂给 大语言模型,自动生成调查摘要。看它怎么工作的:

PYTHON

# 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 不只是命令行工具,它还支持三种不同的使用方式:

TERMINAL

# 基础搜索
maigret username

# 带报告输出
maigret username --html --pdf

# AI 分析 + 全站扫描
maigret username -a --ai
                  
说明

最简单的使用方式,一条命令搞定...

同时生成 HTML 和 PDF 格式的报告...

扫描全部网站并用 AI 生成分析报告

命令行模式最简单直接,适合日常使用

你在搜索用户名 john_doe 时,在 GitHub 个人主页中发现了一个 Instagram 链接,里面有一个新用户名 j_doe_photos。Maigret 会怎么做?

Permutator 模块会把 john 和 doe 组合成哪些用户名变体?

如果要用 Maigret 的 --ai 模式进行 AI 分析,你需要准备什么?