双因素认证¶
概述¶
双因素认证(2FA,Two-Factor Authentication)是 Instagram 提供的账号安全功能。 开启后,仅凭用户名和密码无法完成登录,还需要额外提供一个只有账号持有者才能获取的一次性验证码。
Instagram 支持两种 2FA 方式:
| 方式 | 说明 | igapi 支持 |
|---|---|---|
| TOTP(基于时间的一次性密码) | 通过 Google Authenticator 等 App 生成的 6 位验证码,每 30 秒刷新一次 | 全自动(传入密钥)或手动 |
| SMS(短信验证码) | Instagram 向绑定手机发送的 6 位验证码 | 仅支持手动输入 |
重要:双因素认证仅在
WebClient中支持完整流程。Client(Android)遇到 2FA 要求会抛出TwoFactorRequired异常,但不提供verify_two_factor()方法。如果账号开启了 2FA,请使用WebClient登录。
快速开始¶
以下示例展示两种最常见的 2FA 使用场景,后续章节有更详细的说明。
场景一:拥有 TOTP 密钥(推荐)¶
import asyncio
import igapi
async def main():
web = igapi.WebClient()
await web.login(
"your_username",
"your_password",
two_fa_secret="JBSWY3DPEHPK3PXP", # Base32 编码的 TOTP 密钥
)
print("登录成功,session 已就绪")
asyncio.run(main())
场景二:手动输入验证码¶
import asyncio
import igapi
async def main():
web = igapi.WebClient()
try:
await web.login("your_username", "your_password")
except igapi.TwoFactorRequired:
code = input("请输入 6 位验证码: ").strip()
await web.verify_two_factor(code)
print("登录成功")
asyncio.run(main())
自动 2FA(推荐)¶
如果你在设置 2FA 时保存了 Base32 格式的 TOTP 密钥,可以将其传入 login() 的 two_fa_secret 参数。
库会在内部自动生成当前时间窗口的验证码并完成整个 2FA 流程,对调用方完全透明。
import asyncio
import igapi
import os
async def main():
web = igapi.WebClient()
# 全自动登录:库内部生成 TOTP 验证码,无需任何手动操作
await web.login(
os.environ["IG_USERNAME"],
os.environ["IG_PASSWORD"],
two_fa_secret=os.environ["IG_TOTP_SECRET"], # Base32 编码的 TOTP 密钥
)
print(f"登录成功: {web.export_account().username}")
session_str = web.export_account_string()
asyncio.run(main())
自动 2FA 的内部流程¶
- 调用
login(username, password, two_fa_secret=...),服务端返回需要 2FA 的信号 - 库使用传入的
two_fa_secret生成当前时间窗口的 6 位 TOTP 验证码(SHA1 算法,30 秒周期) - 自动调用 2FA 验证端点,提交验证码
- 验证通过后,
login()正常返回,整个过程对调用方透明
完整示例(含错误处理)¶
import asyncio
import igapi
import os
async def auto_login() -> igapi.WebClient:
"""使用 TOTP 密钥自动完成 2FA 登录"""
web = igapi.WebClient()
try:
await web.login(
os.environ["IG_USERNAME"],
os.environ["IG_PASSWORD"],
two_fa_secret=os.environ["IG_TOTP_SECRET"],
)
return web
except ValueError as e:
raise RuntimeError(f"凭证或 TOTP 密钥错误: {e}") from e
except igapi.ChallengeRequired as e:
raise RuntimeError(
f"需要完成安全挑战,请访问: {e.challenge_info.get('checkpoint_url')}"
) from e
except ConnectionError as e:
raise RuntimeError(f"网络连接失败,请检查代理配置: {e}") from e
async def main():
web = await auto_login()
asyncio.run(main())
手动 2FA¶
当未提供 two_fa_secret,或账号使用 SMS 验证码时,login() 会抛出 TwoFactorRequired 异常。
此时需要捕获该异常,引导用户输入验证码,再调用 verify_two_factor() 完成登录。
import asyncio
import igapi
async def main():
web = igapi.WebClient()
try:
await web.login("your_username", "your_password")
print("登录成功(无 2FA)")
except igapi.TwoFactorRequired as e:
# 从异常属性中获取 2FA 信息
info = e.two_factor_info
print(f"账号 {info.get('username')} 需要双因素认证")
# 判断 2FA 方式并提示用户
if info.get("totp_two_factor_on"):
print("请打开 Authenticator App 查看验证码")
elif info.get("sms_two_factor_on"):
print("请查收手机短信验证码")
else:
print("请输入验证码")
code = input("请输入 6 位验证码: ").strip()
try:
await web.verify_two_factor(code)
print("验证成功,已登录")
except ValueError as ve:
print(f"验证码错误: {ve}")
asyncio.run(main())
verify_two_factor() 的工作原理¶
login() 抛出 TwoFactorRequired 时,客户端会在内部暂存以下信息:
- 用户名
- 密码
- 服务端返回的
two_factor_identifier(标识本次 2FA 会话)
调用 verify_two_factor(code) 时,这些信息会被自动读取并组合成最终的验证请求,
无需用户重复传入用户名和密码。验证成功后,内部暂存的登录上下文会被清除。
交互式登录(支持重试)¶
适用于命令行工具或交互式脚本,最多允许重试 3 次:
import asyncio
import igapi
async def interactive_login(username: str, password: str) -> igapi.WebClient:
"""交互式登录,支持手动输入 2FA 验证码,最多重试 3 次"""
web = igapi.WebClient()
try:
await web.login(username, password)
print("登录成功")
return web
except igapi.TwoFactorRequired as e:
info = e.two_factor_info
method = "TOTP" if info.get("totp_two_factor_on") else "SMS"
print(f"账号已开启 2FA({method}),请输入验证码")
for attempt in range(1, 4):
code = input(f"验证码(第 {attempt}/3 次): ").strip()
try:
await web.verify_two_factor(code)
print("2FA 验证成功")
return web
except ValueError:
if attempt < 3:
print("验证码错误,请重试")
else:
raise RuntimeError("验证码连续错误 3 次,登录失败")
except igapi.ChallengeRequired as e:
url = e.challenge_info.get("checkpoint_url", "未知")
raise RuntimeError(f"需要完成安全挑战,请访问: {url}")
except ValueError as e:
raise RuntimeError(f"用户名或密码错误: {e}")
async def main():
web = await interactive_login("your_username", "your_password")
asyncio.run(main())
Android 客户端的 2FA 限制¶
igapi.Client(Android 客户端)在登录流程中遇到 2FA 要求时,会直接抛出 TwoFactorRequired 异常,但 不提供 verify_two_factor() 方法,无法在同一客户端上完成 2FA 验证流程。
import asyncio
import igapi
async def main():
client = igapi.Client()
try:
await client.login("your_username", "your_password")
except igapi.TwoFactorRequired as e:
# Client 不支持完成 2FA,只能获取信息后退出或切换到 WebClient
info = e.two_factor_info
print(f"账号 {info.get('username')} 开启了 2FA,Android 客户端无法完成验证")
print("请改用 igapi.WebClient 完成登录")
raise
asyncio.run(main())
推荐处理方案¶
对于开启了 2FA 的账号,有以下两种推荐方案:
方案一:直接使用 WebClient(推荐)
WebClient 功能完整,支持 2FA,且支持与 AndroidClient 相同的大部分 API。
import asyncio
import igapi
async def main():
web = igapi.WebClient()
await web.login(
"your_username",
"your_password",
two_fa_secret="BASE32_TOTP_SECRET",
)
asyncio.run(main())
方案二:用 WebClient 登录后导出 session,再用 AndroidClient 恢复
适用于必须使用 AndroidClient 的场景,但 session 格式不兼容,此方案不可行。 Android 和 Web 的 session 格式不同,无法互相恢复。两者必须分别维护各自的 session。
| 对比项 | Client(Android) |
WebClient(Web) |
|---|---|---|
| 2FA 支持 | 仅抛出异常,无法完成 | 完整支持(自动/手动) |
verify_two_factor() |
不存在 | 存在 |
| 推荐用于开启了 2FA 的账号 | 否 | 是 |
异常详解¶
TwoFactorRequired¶
igapi.TwoFactorRequired 是 igapi 定义的自定义异常类,继承自 Exception,在登录需要 2FA 验证时抛出。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
two_factor_info |
dict |
服务端返回的 2FA 信息字典 |
two_factor_info 字典结构:
| 字段 | 类型 | 说明 |
|---|---|---|
two_factor_identifier |
str |
本次 2FA 会话的唯一标识符 |
username |
str |
正在登录的用户名 |
sms_two_factor_on |
bool \| None |
账号是否开启了 SMS 2FA |
totp_two_factor_on |
bool \| None |
账号是否开启了 TOTP 2FA |
phone_verification_settings |
dict \| None |
手机验证的详细设置(可选) |
使用示例:
async def main():
try:
await web.login("your_username", "your_password")
except igapi.TwoFactorRequired as e:
info = e.two_factor_info
identifier = info["two_factor_identifier"]
username = info["username"]
has_sms = info.get("sms_two_factor_on", False)
has_totp = info.get("totp_two_factor_on", False)
print(f"用户 {username} 需要 2FA")
print(f" 短信验证: {has_sms}")
print(f" TOTP 验证: {has_totp}")
print(f" 会话标识: {identifier}")
ChallengeRequired¶
igapi.ChallengeRequired 在登录触发 Instagram 安全挑战时抛出(如邮箱验证、手机验证等),与 2FA 是不同的机制。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
challenge_info |
dict |
服务端返回的挑战信息字典,通常包含 checkpoint_url |
处理示例:
async def main():
try:
await web.login("your_username", "your_password")
except igapi.ChallengeRequired as e:
url = e.challenge_info.get("checkpoint_url", "未知")
print(f"需要完成安全挑战,请在浏览器中访问: {url}")
# 完成挑战后,通常需要重新创建客户端并登录
2FA 决策流程图¶
下图展示了 login() 调用后,igapi 内部的 2FA 处理决策路径:
flowchart TD
A([调用 web.login]) --> B{服务端响应}
B -->|登录成功| C([登录完成,正常返回])
B -->|密码错误| D([抛出 ValueError])
B -->|安全挑战| E([抛出 ChallengeRequired])
B -->|需要 2FA| F{是否传入 two_fa_secret}
F -->|是| G[自动生成 TOTP 验证码]
G --> H[调用 2FA 验证端点]
H -->|验证通过| C
H -->|验证码错误| I([抛出 ValueError])
F -->|否| J[暂存登录上下文]
J --> K([抛出 TwoFactorRequired])
K --> L{调用方响应}
L -->|调用 verify_two_factor| M[提交用户输入的验证码]
M -->|验证通过| C
M -->|验证码错误| N([抛出 ValueError])
M -->|标识符过期| O([抛出 RuntimeError,需重新 login])
L -->|未调用| P([登录流程中止])
常见问题¶
Q: 我的账号使用 SMS 验证码,能实现完全自动化登录吗?
A: 无法自动化,因为 SMS 验证码需要手机实时接收。如果需要自动化登录,建议在账号安全设置中将 2FA 方式改为 TOTP(验证器 App),然后保存设置时显示的 Base32 密钥,再使用 two_fa_secret 参数实现全自动登录。
Q: 怎么获取 TOTP 密钥(two_fa_secret)?
A: 在 Instagram 中进入「设置 → 账号安全 → 双因素认证 → 验证器 App」,设置时页面会显示或允许复制一个 Base32 格式的密钥字符串(通常是 16-32 个大写字母和数字组成的字符串)。这就是 two_fa_secret。请务必在首次设置时保存,之后将无法再次查看该密钥。
Q: verify_two_factor() 调用提示 RuntimeError: 请先调用 login(),怎么解决?
A: 这是因为调用 verify_two_factor() 之前,login() 要么没有被调用,要么没有抛出 TwoFactorRequired(意味着账号未开启 2FA 或登录已成功)。请确保调用链为:login() 抛出 TwoFactorRequired → 捕获异常 → 调用 verify_two_factor(code),且三个步骤在同一个 WebClient 实例上完成。
Q: 自动 2FA 失败,提示验证码错误,但手动输入同一 App 显示的验证码可以成功,为什么?
A: 自动 TOTP 依赖系统时钟准确性。如果运行 igapi 的机器时钟与标准时间偏差超过几秒,生成的验证码可能与服务端预期不符。请确保系统时间已通过 NTP 服务同步。
Q: TOTP 密钥(two_fa_secret)应该怎么安全存储?
A: TOTP 密钥的安全级别等同于账号密码,控制了该密钥就相当于控制了账号的 2FA。请通过环境变量或加密密钥库传入,绝对不要硬编码在源代码中或提交到版本控制系统。
Q: 验证码已过期(two_factor_identifier 失效),怎么办?
A: 服务端返回的 two_factor_identifier 通常数分钟内有效。如果用户长时间未输入验证码,verify_two_factor() 可能会失败。此时需要在同一个 WebClient 实例上重新调用 login(),获取新的 two_factor_identifier 后再提交验证码。