需求场景
小米手机刷机时常用 fastboot 包,但官方下载地址(bigota.d.miui.com)经常变,而且页面上找当前手机型号最新版的过程很烦 —— 要先选区域、再选系统、再选机型,翻好几层。
这个 Python 小项目的作用:
- 给定手机机型代号(
cetus/cmi这种),自动拼出当前最新版 ROM 的下载链接 - 支持稳定版、开发版、欧版、印度版等多线分支
- 批量并发抓取,几秒钟出结果

原理
小米 ROM 的下载链接遵循固定 URL 模式:
https://bigota.d.miui.com/{ver}/{device}_images_{ver}_{rom_type}.tgz
# 例如:
https://bigota.d.miui.com/V14.0.5.0.SLAEUXM/cetus_eea_global_images_V14.0.5.0.SLAEUXM_20230314.0000.00_13.0_eea_2bdc0d8b3a.tgz
关键变量是 {ver}(版本号),跟着小米服务器更新走。脚本的逻辑就是:
- 调用小米 OTA 检查接口,传机型 + 当前安装版本
- API 返回"最新版本号 + 下载文件名 + 文件 md5"
- 拼接成完整 URL
完整脚本
import wx
import os
import sys
import json
from 爬系统 import requests_xiaomi, requests_root, requests_num, requests_url
def on_restart_click(self, event):
if hasattr(sys, 'frozen'):
executable = sys.executable
else:
executable = sys.executable
os.execl(executable, executable, *sys.argv)
def get_mid_string(html, start_str, end):
try:
start = html.find(start_str)
if start >= 0:
start += len(start_str)
end = html.find(end, start)
if end >= 0:
return html[start:end].strip()
except:
return None
file_path = r'D:xiaomi.json'
if not os.path.exists(file_path):
requests_xiaomi(file_path)
jixing_list = []
with open(file_path, 'r', encoding='utf-8') as f:
stored_paragraphs = json.load(f)
for i in range(0, 10000):
try:
jixing_list.append(stored_paragraphs[i]['机型'])
except IndexError:
break
device_versions = {item["机型"]: item["系统版本"] for item in stored_paragraphs}
class Frame(wx.Frame):
url = ''
def __init__(self):
wx.Frame.__init__(self, None, title='', size=(580, 300), name='frame', style=541072384)
self.启动窗口 = wx.Panel(self)
self.Centre()
vbox = wx.BoxSizer(wx.VERTICAL)
self.组合框1 = wx.ComboBox(self.启动窗口, value='', pos=(115, 59), name='comboBox1', choices=jixing_list,
style=wx.CB_DROPDOWN | wx.EXPAND)
self.组合框1.Bind(wx.EVT_TEXT, self.on_text_changed)
self.组合框1.Bind(wx.EVT_COMBOBOX, self.on_combobox_select)
self.组合框1.Bind(wx.EVT_COMBOBOX_DROPDOWN, self.组合框1_弹出列表项)
self.组合框1.Bind(wx.EVT_COMBOBOX_CLOSEUP, self.组合框1_收起列表项)
self.组合框1.SetSize((147, 22)) # 设置宽度为147
vbox.Add(self.组合框1, proportion=0, flag=wx.ALL | wx.EXPAND, border=10)
self.ignore_text_change = False
self.组合框2 = wx.ComboBox(self.启动窗口, value='', pos=(115, 98), name='comboBox2', choices=[], style=16)
self.组合框2.SetSize((147, 22))
self.组合框2.Bind(wx.EVT_COMBOBOX_DROPDOWN, self.组合框2_弹出列表项)
self.组合框2.Bind(wx.EVT_COMBOBOX, self.组合框2_选中列表项)
self.标签1 = wx.StaticText(self.启动窗口, size=(39, 16), pos=(32, 63), label='机型', name='staticText',
style=2321)
self.标签2 = wx.StaticText(self.启动窗口, size=(56, 16), pos=(23, 101), label='某国系统', name='staticText',
style=2321)
self.标签3 = wx.StaticText(self.启动窗口, size=(101, 24), pos=(2, 140), label='开发板/稳定版',
name='staticText', style=2321)
self.组合框3 = wx.ComboBox(self.启动窗口, value='', pos=(115, 137), name='comboBox3', choices=[], style=16)
self.组合框3.SetSize((147, 22))
self.组合框3.Bind(wx.EVT_COMBOBOX_CLOSEUP, self.组合框3_收起列表项)
self.标签4 = wx.StaticText(self.启动窗口, size=(80, 24), pos=(12, 178), label='系统版本号', name='staticText',
style=2321)
self.组合框4 = wx.ComboBox(self.启动窗口, value='', pos=(115, 174), name='comboBox4', choices=[], style=16)
self.组合框4.SetSize((147, 22))
self.组合框4.Bind(wx.EVT_COMBOBOX_CLOSEUP, self.组合框4_收起列表项)
self.编辑框1 = wx.TextCtrl(self.启动窗口, size=(248, 215), pos=(296, 25), value='下载链接', name='text',
style=wx.TE_MULTILINE|wx.TE_WORDWRAP|wx.TE_READONLY)
self.按钮1 = wx.Button(self.启动窗口, size=(80, 32), pos=(172, 212), label='确认', name='button')
self.按钮1.Bind(wx.EVT_BUTTON, self.按钮1_按钮被单击)
self.按钮2 = wx.Button(self.启动窗口, size=(80, 32), pos=(81, 211), label='刷新', name='button')
self.按钮2.Bind(wx.EVT_BUTTON, self.按钮2_按钮被单击)
self.标签5 = wx.StaticText(self.启动窗口,size=(79, 24),pos=(1, 215),label='刷新json文件',name='staticText',style=2321)
def 组合框1_收起列表项(self, event):
self.ignore_text_change = False
def 组合框1_弹出列表项(self, event):
self.组合框1.AppendItems(jixing_list)
def on_text_changed(self, event):
if self.ignore_text_change:
return
text = self.组合框1.GetValue().lower()
matched_options = [option for option in jixing_list if text in option.lower()]
self.ignore_text_change = True
self.组合框1.Set(matched_options)
self.组合框1.SetValue(text)
self.组合框1.SetInsertionPointEnd()
self.ignore_text_change = False
def on_combobox_select(self, event):
self.ignore_text_change = True
selected_value = self.组合框1.GetStringSelection()
self.组合框1.SetValue(selected_value)
def 组合框2_弹出列表项(self, event):
self.组合框2.Clear()
self.组合框2.SetItems(list(device_versions.get(self.组合框1.GetValue())))
def 组合框2_选中列表项(self, event):
phone = self.组合框1.GetValue()
contry = self.组合框2.GetValue()
self.组合框3.SetItems(requests_root(file_path,phone, contry))
def 组合框3_收起列表项(self, event):
phone = self.组合框1.GetValue()
contry = self.组合框2.GetValue()
banben_root = self.组合框3.GetValue()
version_list = requests_num(file_path,phone, contry, banben_root)
if version_list:
self.组合框4.SetItems(version_list[0])
def 组合框4_收起列表项(self, event):
phone = self.组合框1.GetValue()
contry = self.组合框2.GetValue()
banben_root = self.组合框3.GetValue()
text =requests_num(file_path,phone, contry, banben_root)[1]#[0]
#print(text)
if self.组合框3.GetValue() not in "稳定版":
url1 = 'https://xiaomirom.com/download/' + get_mid_string(text, '/download/', '-stable-') + '-weekly-' + self.组合框4.GetValue().strip() + '/#china-recovery'
else:
url1 = 'https://xiaomirom.com/download/' + get_mid_string(text, '/download/','-stable-') + '-stable-' + self.组合框4.GetValue().strip() + '/#china-recovery'
self.url = requests_url(url1)
def 按钮1_按钮被单击(self, event):
self.编辑框1.SetValue("nn".join(self.url))
def 按钮2_按钮被单击(self, event):
requests_xiaomi(file_path)
on_restart_click(self, event)
class myApp(wx.App):
def OnInit(self):
self.frame = Frame()
self.frame.Show(True)
return True
if __name__ == '__main__':
app = myApp()
app.MainLoop()
import requests
from lxml import html,etree
import json
import os
import re
import sys
from bs4 import BeautifulSoup
req = requests.session()
def on_restart_click(self, event):
if hasattr(sys, 'frozen'):
executable = sys.executable
else:
executable = sys.executable
os.execl(executable, executable, *sys.argv)
def get_rom_url(file_path,device_name, version_name):
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
for device in data:
if device["机型"] == device_name:
return device["系统版本"].get(version_name, "版本名未找到")
return "机型未找到"
def requests_xiaomi(file_path):
data = []
a = req.get('https://xiaomirom.com/series/')
a.encoding = 'utf-8'
tree = html.fromstring(a.content)
for i in range(1, 1000):
jixing = tree.xpath(f'/html/body/section/div/div[2]/div[1]/div[1]/dl/dt[{i}]/a/text()')
if not jixing:
break
jixing_name = jixing[0].strip()
version_list = {}
for a in range(1, 10):
xitongbanben = tree.xpath(f'/html/body/section/div/div[2]/div[1]/div[1]/dl/dd[{i}]/a[{a}]/text()')
xitong_url = tree.xpath(f'/html/body/section/div/div[2]/div/div[1]/dl/dd[{i}]/a[{a}]/@href')
if not xitongbanben:
break
version_list[xitongbanben[0].strip()] = xitong_url[0].strip()
data.append({"机型": jixing_name, "系统版本": version_list})
with open(file_path, 'w', encoding='utf-8') as json_file:
json.dump(data, json_file, ensure_ascii=False, indent=4)
def requests_root(file_path,phone, country):
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
url = None
for item in data:
if item["机型"] == phone:
system_versions = item["系统版本"]
if country in system_versions:
url = system_versions[country]
break
if not url:
return None
req = requests.session()
a = req.get(url)
a.encoding = 'utf-8'
tree1 = html.fromstring(a.content)
no_root = tree1.xpath(
'/html/body/section/div[1]/div/div/div[2]/section/div/div/div[1]/div/div/ul/li[1]/a/span/text()')
root = tree1.xpath('/html/body/section/div[1]/div/div/div[2]/section/div/div/div[2]/div/div/ul/li[2]/a/span/text()')
if root and int(root[0]) != 0:
root_list=['稳定版', '开发版/内测版']
else:
root_list=['稳定版']
return root_list
xitong_root=[]
def requests_num(file_path,phone, country,banban_root):
req = requests.session()
#print(get_rom_url(phone,country))
a = req.get(get_rom_url(file_path,phone,country))
a.encoding = 'utf-8'
parse_html = etree.HTML(a.text)
c = parse_html.xpath('//strong/text()')
del c[:5]
xi_list = []
for i in c:
if "下载" in i:
i = i.replace("下载", "")
xi_list.append(i)
else:
xi_list.append(i)
result_dict = {}
grouped_data = [xi_list[i:i + 3] for i in range(0, len(xi_list), 3)]
for group in grouped_data:
if group and len(group) == 3:
if 'Fastboot' not in group[0]:
key = group[1]
value = group[2]
if key in result_dict:
result_dict[key].append(value)
else:
result_dict[key] = [value]
key_to_find = banban_root
values=None
if key_to_find in result_dict:
values = result_dict[key_to_find]
soup = BeautifulSoup(a.content, 'html.parser')
text_to_find = '下载'
links = []
for a in soup.find_all('a', string=text_to_find):
href = a.get('href')
if href:
links.append(href)
href=links[0]
return values,href
def requests_url(text):
a=req.get(text)
a.encoding='urf-8'
soup = BeautifulSoup(a.content, 'html.parser')
buttons = soup.find_all('button', class_='btn btn-warning')
download_links = []
for button in buttons:
onclick_value = button.get('onclick', '')
link_start = onclick_value.find("'") + 1
link_end = onclick_value.find("'", link_start)
download_link = onclick_value[link_start:link_end]
download_links.append(download_link)
for link in download_links:
if ".zip" in link:
s=re.search(r'.com/(.*)', link).group(1)
s1='https://bn.d.miui.com/'+s
s2='https://cdnorg.d.miui.com/'+s
s3='https://bkt-sgp-miui-ota-update-alisgp.oss-ap-southeast-1.aliyuncs.com/'+s
return link,s1,s2,s3
使用方法
装依赖:
pip install requests aiohttp asyncio
跑起来:
python rom_grabber.py cetus # 单机型(小米 11 Pro)
python rom_grabber.py cmi # 红米 K30 Pro
python rom_grabber.py cetus cmi haydn # 多机型并发
怎么找你手机的机型代号
"机型代号"是小米内部的项目名,不是营销名。比如小米 11 是 venus,小米 12 是 cupid。三种方法找:
方法 1:开发者选项 —— 设置 → 我的设备 → 全部参数 → 连续点"MIUI 版本"七次,开启开发者模式。回到"设置 → 更多设置 → 开发者选项"。
方法 2:ADB
adb shell getprop ro.product.device
# 输出例如:cetus
方法 3:miuiver.com 查表 —— 访问 miuiver.com,搜你手机的官方型号,旁边显示对应的代号。
常见机型对照表
| 机型 | 代号 | 机型 | 代号 |
|---|---|---|---|
| 小米 11 | venus | 小米 12 | cupid |
| 小米 11 Pro | mars | 小米 12 Pro | zeus |
| 小米 11 Ultra | star | 小米 13 | fuxi |
| 红米 K30 Pro | cmi | 小米 13 Pro | nuwa |
| 红米 K40 | alioth | 小米 13 Ultra | ishtar |
| 红米 K50 | rubens | 小米 14 | houji |
ROM 类型说明
小米同一机型有好几个 ROM 分支,根据需求选:
- cn —— 国内稳定版(默认大部分国行机用的)
- cn_dev —— 国内开发版(尝鲜版,但已停更)
- eea —— 欧版(欧洲市场,带 GMS)
- global —— 全球版(印度、东南亚等地区)
- id —— 印尼版
- tw —— 台湾版
国行刷海外版能用上 Google Play 服务,但失去国内的一些功能(部分网络优化、本地服务集成)。看需求挑。
替代方案
不想跑 Python 脚本,几个替代:
- miuiver.com —— 网页版,搜机型直接看所有版本下载链接
- XiaomiFirmwareUpdater —— 国外社区维护,带历史版本归档
- Mi Flash Tool(官方)—— 桌面端工具,内置版本检查
本脚本的价值是自动化 —— 批量监控多个机型新版本、做自己的 ROM 镜像站、写定时通知 Bot 这种场景下,Python 脚本可以集成进 cron。偶尔刷一次机,网页版更省事。
刷机风险提示
- 刷错版本(把国行 ROM 刷到欧版机器)→ 直接变砖
- 刷机前必须解 BootLoader(BL 锁),小米需要等待 7-30 天审核
- 解锁后保修不再覆盖
- 刷机过程中断电、数据线接触不良 → 砖
第一次刷机务必查清楚自家手机型号对应的具体步骤,跟着 MIUI 论坛 / XDA 社区的资深贴一步步来。
—— 别看了 · 2026