Files
HK-IT-Docs/Emby刷新演员头像脚本.md

1013 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Emby刷新演员头像脚本
## 1. 脚本总结
- 用 PersonId 直接请求图片 → 触发Emby下载,不用模拟点击
- 智能生成搜索关键词 → 中文取前1-2个字,英文取前2个字母
- 进度持久化 → 中断后可以继续,不会重复处理
- 限流保护 → 429/403/网络500自动停止
- 只处理有 Primary 标记的 → 避免无效请求
- 适用范围: Emby 4.8.x / 4.9.x 通用
- 需要科学上网的环境(脚本走系统代理)
- 大批量补全演员头像
说明: 整个补齐脚本分为两个阶段:
第一阶段: 补齐打开影片页就可以自动刷新的演员头像;
第二阶段: 补齐需要打开演员详情页才能刷新的演员头像.
脚本需要使用python,请自行安装。
## 2. 第一阶段脚本
```bash
import requests
import time
import json
import os
import sys
import re
# =========================
# 你需要修改的配置
# =========================
EMBY_URL = ""
API_KEY = ""
USER_ID = ""
# 每个搜索词之间等待多少秒
SEARCH_TERM_DELAY_SECONDS = 5
# 同一个搜索词下,每个演员头像之间等待多少秒
IMAGE_DELAY_SECONDS = 4
# 每个搜索词最多请求多少个带头像标记的演员
# 先保守一点,避免一个关键词返回太多人
MAX_IMAGES_PER_TERM = 50
# 每次搜索最多返回多少个演员
SEARCH_LIMIT = 200
# 读取媒体库分页间隔
PAGE_DELAY_SECONDS = 1
# 进度文件:记录已经搜索过的关键词、已经处理过的人物头像
PROGRESS_FILE = r"D:\emby_person_image_warmer_progress.json"
# 只从媒体库中收集演员
TARGET_PERSON_TYPES = {"Actor"}
# =========================
# 以下一般不用修改
# =========================
session = requests.Session()
session.headers.update({
"X-Emby-Token": API_KEY,
"User-Agent": "Mozilla/5.0 Emby-Web-Person-Image-Warmer/1.0"
})
def load_progress():
if not os.path.exists(PROGRESS_FILE):
return {
"terms": {},
"persons": {}
}
try:
with open(PROGRESS_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
if "terms" not in data:
data["terms"] = {}
if "persons" not in data:
data["persons"] = {}
return data
except Exception:
return {
"terms": {},
"persons": {}
}
def save_progress(progress):
with open(PROGRESS_FILE, "w", encoding="utf-8") as f:
json.dump(progress, f, ensure_ascii=False, indent=2)
def wait_seconds(seconds, reason):
print(f"{reason},等待 {seconds} 秒...")
time.sleep(seconds)
def get_all_media_items():
"""
获取所有电影和电视剧,用来收集演员名字。
这里只读取 People,不刷新元数据,不替换图片。
"""
items = []
start = 0
limit = 100
while True:
r = session.get(
f"{EMBY_URL}/emby/Users/{USER_ID}/Items",
params={
"UserId": USER_ID,
"Recursive": "true",
"IncludeItemTypes": "Movie,Series",
"Fields": "People",
"StartIndex": start,
"Limit": limit
},
timeout=60
)
r.raise_for_status()
data = r.json()
batch = data.get("Items", [])
items.extend(batch)
print(f"已读取媒体条目:{len(items)} / {data.get('TotalRecordCount', '?')}")
if len(batch) < limit:
break
start += limit
wait_seconds(PAGE_DELAY_SECONDS, "读取下一批媒体前")
return items
def collect_actor_names(items):
"""
从媒体条目中收集演员名字。
"""
names = set()
for item in items:
for p in item.get("People", []) or []:
if p.get("Type") not in TARGET_PERSON_TYPES:
continue
name = p.get("Name")
if not name:
continue
name = name.strip()
if name:
names.add(name)
return sorted(names)
def is_cjk_char(ch):
"""
判断是否中日韩字符。
"""
return (
"\u4e00" <= ch <= "\u9fff" or
"\u3040" <= ch <= "\u30ff" or
"\uac00" <= ch <= "\ud7af"
)
def make_search_terms_from_name(name):
"""
从演员名字生成搜索关键词。
例:
刘若英 -> 刘, 刘若
刘诗诗 -> 刘, 刘诗
Li Ying -> li, yi
Liu Shishi -> li, sh
Aoi Tsukasa -> ao, ts
葵司 -> 葵, 葵司
"""
terms = set()
clean = name.strip()
if not clean:
return terms
# 中日韩名字:取第一个字、前两个字
cjk_chars = [ch for ch in clean if is_cjk_char(ch)]
if cjk_chars:
terms.add(cjk_chars[0])
if len(cjk_chars) >= 2:
terms.add("".join(cjk_chars[:2]))
# 拉丁字母名字:每个单词取前两个字母
words = re.findall(r"[A-Za-zÀ-ÖØ-öø-ÿ]+", clean)
for w in words:
w = w.lower()
if len(w) >= 2:
terms.add(w[:2])
elif len(w) == 1:
terms.add(w)
return terms
def build_search_terms(actor_names):
"""
根据所有演员名字生成搜索词列表。
"""
terms = set()
for name in actor_names:
terms.update(make_search_terms_from_name(name))
# 去掉太短且可能结果太杂的英文单字母
cleaned = set()
for t in terms:
t = t.strip()
if not t:
continue
# 英文单字母容易返回太多,跳过
if re.fullmatch(r"[a-zA-Z]", t):
continue
cleaned.add(t)
return sorted(cleaned)
def search_persons(term):
"""
模拟 Emby 搜索演员。
"""
try:
r = session.get(
f"{EMBY_URL}/emby/Users/{USER_ID}/Items",
params={
"UserId": USER_ID,
"Recursive": "true",
"IncludeItemTypes": "Person",
"SearchTerm": term,
"Fields": "ImageTags,PrimaryImageAspectRatio,ProviderIds",
"Limit": SEARCH_LIMIT
},
timeout=90
)
if r.status_code == 200:
data = r.json()
items = data.get("Items", [])
total = data.get("TotalRecordCount", len(items))
print(f"[搜索OK] 关键词={term} | 返回={len(items)} / 总数={total}")
return "ok", items, total
if r.status_code == 429:
retry_after = r.headers.get("Retry-After")
print(f"[429 限流] 关键词={term}")
if retry_after:
print(f"服务端要求等待 {retry_after} 秒。现在停止脚本。")
else:
print("检测到限流。现在停止脚本,建议稍后再跑。")
return "rate_limited", [], 0
if r.status_code == 403:
print(f"[403 禁止访问] 关键词={term}")
print("检测到可能的封锁/权限问题。现在停止脚本。")
return "blocked", [], 0
if r.status_code == 500:
print(f"[搜索500] 关键词={term} | {r.text[:200]}")
return "server_error_500", [], 0
print(f"[搜索{r.status_code}] 关键词={term} | {r.text[:200]}")
return "fail", [], 0
except requests.exceptions.Timeout:
print(f"[搜索超时] 关键词={term}")
return "timeout", [], 0
except Exception as e:
print(f"[搜索错误] 关键词={term}: {e}")
return "fail", [], 0
def request_person_image(person):
"""
请求搜索结果里带 ImageTags.Primary 的演员头像。
这是关键:
/emby/Items/{PersonId}/Images/Primary?tag=xxx
不刷新元数据。
不替换图片。
不删除图片。
"""
name = person.get("Name")
person_id = str(person.get("Id"))
image_tags = person.get("ImageTags") or {}
primary_tag = image_tags.get("Primary")
if not primary_tag:
print(f" [跳过无头像标记] {name} | PersonId={person_id}")
return "skip_no_tag"
url = f"{EMBY_URL}/emby/Items/{person_id}/Images/Primary"
params = {
"maxWidth": 400,
"quality": 90,
"tag": primary_tag
}
try:
r = session.get(url, params=params, timeout=90)
content_type = r.headers.get("Content-Type", "")
if r.status_code == 200:
print(f" [图片OK] {name} | PersonId={person_id} | {len(r.content)} bytes | {content_type}")
return "ok"
if r.status_code == 404:
print(f" [图片404] {name} | PersonId={person_id}")
return "not_found"
if r.status_code == 429:
print(f" [429 限流] {name} | PersonId={person_id}")
return "rate_limited"
if r.status_code == 403:
print(f" [403 禁止访问] {name} | PersonId={person_id}")
return "blocked"
if r.status_code == 500:
text = r.text[:300]
print(f" [图片500] {name} | PersonId={person_id} | {text}")
# 网络/代理/上游超时类 500,不记为成功,后面可以重试
lower_text = text.lower()
if (
"ssl connection" in lower_text or
"previous timeout" in lower_text or
"timeout" in lower_text or
"connection" in lower_text
):
return "network_500"
# Emby内部空引用一类,记录但不终止
if "object reference" in lower_text:
return "server_error_500"
return "server_error_500"
print(f" [图片{r.status_code}] {name} | PersonId={person_id} | {r.text[:200]}")
return "fail"
except requests.exceptions.Timeout:
print(f" [图片超时] {name} | PersonId={person_id}")
return "timeout"
except Exception as e:
print(f" [图片请求错误] {name} | PersonId={person_id} | {e}")
return "fail"
if __name__ == "__main__":
progress = load_progress()
print("开始读取媒体库,用来收集演员名字...")
items = get_all_media_items()
print("\n开始收集演员名字...")
actor_names = collect_actor_names(items)
print(f"共收集到演员数量:{len(actor_names)}")
search_terms = build_search_terms(actor_names)
print(f"生成搜索关键词数量:{len(search_terms)}")
print("前 50 个搜索关键词预览:")
for t in search_terms[:50]:
print("-", t)
print("\n开始正式处理演员头像...\n")
for term_index, term in enumerate(search_terms, 1):
if term in progress["terms"]:
old_status = progress["terms"][term].get("status")
print(f"{term_index}/{len(search_terms)} 跳过关键词:{term} | 已处理状态={old_status}")
continue
print("=" * 70)
print(f"{term_index}/{len(search_terms)} 搜索关键词:{term}")
search_status, people, total = search_persons(term)
if search_status in {"rate_limited", "blocked"}:
save_progress(progress)
print("\n检测到限流/封锁风险,脚本停止。")
sys.exit(1)
if search_status != "ok":
print("本关键词搜索失败,不写入关键词进度,下次会重试。")
wait_seconds(SEARCH_TERM_DELAY_SECONDS, "搜索词之间间隔")
continue
# 只处理带 Primary 图片标记的人
people_with_primary = []
skipped_no_tag = 0
skipped_done = 0
for p in people:
person_id = str(p.get("Id"))
image_tags = p.get("ImageTags") or {}
primary_tag = image_tags.get("Primary")
if not primary_tag:
skipped_no_tag += 1
continue
# 已经处理过这个人物头像,跳过
if person_id in progress["persons"]:
skipped_done += 1
continue
people_with_primary.append(p)
print(f"本关键词返回人物:{len(people)}")
print(f"无头像标记跳过:{skipped_no_tag}")
print(f"已处理人物跳过:{skipped_done}")
print(f"本次需要请求头像:{len(people_with_primary)}")
processed_count = 0
ok_count = 0
fail_count = 0
network_500_count = 0
server_500_count = 0
for idx, person in enumerate(people_with_primary[:MAX_IMAGES_PER_TERM], 1):
person_id = str(person.get("Id"))
name = person.get("Name")
print(f" {idx}/{min(len(people_with_primary), MAX_IMAGES_PER_TERM)} 请求头像:{name} | PersonId={person_id}")
result = request_person_image(person)
processed_count += 1
if result == "ok":
ok_count += 1
progress["persons"][person_id] = {
"name": name,
"status": "ok",
"term": term,
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_progress(progress)
elif result in {"not_found", "skip_no_tag", "server_error_500"}:
if result == "server_error_500":
server_500_count += 1
else:
fail_count += 1
# 这类不是网络临时问题,可以记录,避免反复请求
progress["persons"][person_id] = {
"name": name,
"status": result,
"term": term,
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_progress(progress)
elif result == "network_500":
network_500_count += 1
print(" 检测到网络/SSL/timeout 类 500,本人物不写入进度,下次可重试。")
# 连续/多次网络失败时,建议停止,不要继续刺激上游
if network_500_count >= 3:
save_progress(progress)
print("\n本关键词内出现 3 次网络类 500,脚本停止。请确认代理/Emby网络后再运行。")
sys.exit(1)
elif result in {"rate_limited", "blocked"}:
save_progress(progress)
print("\n检测到限流/封锁风险,脚本停止。")
sys.exit(1)
else:
fail_count += 1
print(" 本次失败,不写入人物进度,下次会重试。")
if idx < min(len(people_with_primary), MAX_IMAGES_PER_TERM):
wait_seconds(IMAGE_DELAY_SECONDS, "同一关键词下演员头像之间间隔")
# 这个关键词已经搜索并处理过一轮,记录关键词进度
progress["terms"][term] = {
"status": "done",
"returned": len(people),
"total": total,
"processed_images": processed_count,
"ok": ok_count,
"network_500": network_500_count,
"server_500": server_500_count,
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_progress(progress)
print(f"关键词 {term} 完成:图片OK={ok_count},网络500={network_500_count},服务端500={server_500_count}")
if term_index < len(search_terms):
wait_seconds(SEARCH_TERM_DELAY_SECONDS, "搜索词之间间隔")
print("\n全部处理完成。")
print(f"进度文件已保存:{PROGRESS_FILE}")
```
# 3. 第二阶段脚本
```bash
import requests
import time
import json
import os
import sys
# =========================
# 你需要修改的配置
# =========================
EMBY_URL = ""
API_KEY = ""
USER_ID = ""
# 第一阶段脚本的进度文件(如果存在,就会跳过已经成功的人)
PHASE1_PROGRESS_FILE = r"D:\emby_person_image_warmer_progress.json"
# 第二阶段自己的进度文件
PHASE2_PROGRESS_FILE = r"D:\emby_person_detail_warmer_phase2_progress.json"
# 每次拉取多少个人物
PAGE_LIMIT = 200
# 打开人物详情页后,等几秒再重新检查
DETAIL_RECHECK_DELAY_SECONDS = 2
# 每处理一个人物后等待几秒
PERSON_DELAY_SECONDS = 3
# 每处理多少个人物,休息一下
BATCH_SIZE = 100
BATCH_REST_SECONDS = 60
# =========================
# 以下一般不用修改
# =========================
session = requests.Session()
session.headers.update({
"X-Emby-Token": API_KEY,
"User-Agent": "Mozilla/5.0 Emby-Person-Detail-Warmer-Phase2/1.0"
})
def load_json_file(path, default_value):
if not os.path.exists(path):
return default_value
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return default_value
def save_json_file(path, data):
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def load_phase1_ok_persons():
"""
读取第一阶段已成功的人物,避免重复处理。
"""
data = load_json_file(PHASE1_PROGRESS_FILE, {"terms": {}, "persons": {}})
ok_persons = set()
persons = data.get("persons", {})
for person_id, info in persons.items():
if info.get("status") == "ok":
ok_persons.add(str(person_id))
return ok_persons
def load_phase2_progress():
data = load_json_file(PHASE2_PROGRESS_FILE, {"persons": {}})
if "persons" not in data:
data["persons"] = {}
return data
def save_phase2_progress(progress):
save_json_file(PHASE2_PROGRESS_FILE, progress)
def wait_seconds(seconds, reason):
print(f"{reason},等待 {seconds} 秒...")
time.sleep(seconds)
def get_all_persons():
"""
直接读取 Emby 全部 Person 条目。
"""
persons = []
start = 0
while True:
r = session.get(
f"{EMBY_URL}/emby/Users/{USER_ID}/Items",
params={
"UserId": USER_ID,
"Recursive": "true",
"IncludeItemTypes": "Person",
"Fields": "ImageTags,PrimaryImageAspectRatio,ProviderIds",
"StartIndex": start,
"Limit": PAGE_LIMIT
},
timeout=90
)
r.raise_for_status()
data = r.json()
batch = data.get("Items", [])
total = data.get("TotalRecordCount", "?")
persons.extend(batch)
print(f"已读取人物:{len(persons)} / {total}")
if len(batch) < PAGE_LIMIT:
break
start += PAGE_LIMIT
time.sleep(0.5)
return persons
def get_person_detail(person_id):
"""
模拟打开人物详情页。
"""
try:
r = session.get(
f"{EMBY_URL}/emby/Users/{USER_ID}/Items/{person_id}",
params={
"Fields": "ProviderIds,ImageTags,PrimaryImageAspectRatio,Overview"
},
timeout=90
)
if r.status_code == 200:
return "ok", r.json()
if r.status_code == 429:
return "rate_limited", None
if r.status_code == 403:
return "blocked", None
if r.status_code == 500:
text = r.text[:300].lower()
if (
"ssl connection" in text or
"previous timeout" in text or
"timeout" in text or
"connection" in text
):
return "network_500", None
return "server_error_500", None
return "fail", None
except requests.exceptions.Timeout:
return "timeout", None
except Exception:
return "fail", None
def request_person_image(person_id, name, primary_tag):
"""
请求人物头像:
/emby/Items/{PersonId}/Images/Primary?tag=xxx
"""
try:
r = session.get(
f"{EMBY_URL}/emby/Items/{person_id}/Images/Primary",
params={
"maxWidth": 400,
"quality": 90,
"tag": primary_tag
},
timeout=90
)
if r.status_code == 200:
print(f" [图片OK] {name} | PersonId={person_id} | {len(r.content)} bytes")
return "ok"
if r.status_code == 404:
print(f" [图片404] {name} | PersonId={person_id}")
return "not_found"
if r.status_code == 429:
print(f" [429 限流] {name} | PersonId={person_id}")
return "rate_limited"
if r.status_code == 403:
print(f" [403 禁止访问] {name} | PersonId={person_id}")
return "blocked"
if r.status_code == 500:
text = r.text[:300]
print(f" [图片500] {name} | PersonId={person_id} | {text}")
lower_text = text.lower()
if (
"ssl connection" in lower_text or
"previous timeout" in lower_text or
"timeout" in lower_text or
"connection" in lower_text
):
return "network_500"
if "object reference" in lower_text:
return "server_error_500"
return "server_error_500"
print(f" [图片{r.status_code}] {name} | PersonId={person_id} | {r.text[:200]}")
return "fail"
except requests.exceptions.Timeout:
print(f" [图片超时] {name} | PersonId={person_id}")
return "timeout"
except Exception as e:
print(f" [图片请求错误] {name} | PersonId={person_id} | {e}")
return "fail"
if __name__ == "__main__":
print("当前运行目录:", os.getcwd())
print("第一阶段进度文件:", PHASE1_PROGRESS_FILE)
print("第二阶段进度文件:", PHASE2_PROGRESS_FILE)
phase1_ok_persons = load_phase1_ok_persons()
phase2_progress = load_phase2_progress()
print(f"\n第一阶段已成功人物数量:{len(phase1_ok_persons)}")
print("\n开始读取 Emby 全部人物...")
persons = get_all_persons()
print(f"\n共读取到人物数量:{len(persons)}")
need_process = []
skipped_phase1_ok = 0
skipped_phase2_done = 0
skipped_already_has_primary = 0
for p in persons:
person_id = str(p.get("Id"))
name = p.get("Name", "")
image_tags = p.get("ImageTags") or {}
primary_tag = image_tags.get("Primary")
# 第一阶段已经成功刷过
if person_id in phase1_ok_persons:
skipped_phase1_ok += 1
continue
# 第二阶段已经处理过
if person_id in phase2_progress["persons"]:
skipped_phase2_done += 1
continue
# 当前列表里已经有头像标记的,也先跳过
# 因为第二阶段目标就是补“没有头像标记但点详情会出来”的人
if primary_tag:
skipped_already_has_primary += 1
continue
need_process.append({
"Id": person_id,
"Name": name
})
print(f"第一阶段已成功,跳过:{skipped_phase1_ok}")
print(f"第二阶段已处理,跳过:{skipped_phase2_done}")
print(f"当前本身已有头像标记,跳过:{skipped_already_has_primary}")
print(f"第二阶段需要处理:{len(need_process)}\n")
processed = 0
detail_success_count = 0
image_ok_count = 0
still_no_primary_count = 0
network_500_count = 0
server_500_count = 0
fail_count = 0
for idx, person in enumerate(need_process, 1):
person_id = person["Id"]
name = person["Name"]
print("=" * 70)
print(f"{idx}/{len(need_process)} 处理人物:{name} | PersonId={person_id}")
# 第一次打开人物详情
status1, detail1 = get_person_detail(person_id)
if status1 in {"rate_limited", "blocked"}:
save_phase2_progress(phase2_progress)
print("\n检测到限流/封锁风险,脚本停止。")
sys.exit(1)
if status1 == "network_500":
network_500_count += 1
print(" 打开人物详情时发生网络类 500,本次不写进度,下次重试。")
if network_500_count >= 3:
save_phase2_progress(phase2_progress)
print("\n连续出现 3 次网络类 500,脚本停止。请确认代理/Emby 后再运行。")
sys.exit(1)
if idx < len(need_process):
wait_seconds(PERSON_DELAY_SECONDS, "人物之间间隔")
continue
if status1 == "server_error_500":
server_500_count += 1
print(" 打开人物详情时发生服务端 500,记录后跳过。")
phase2_progress["persons"][person_id] = {
"name": name,
"status": "server_error_500_on_detail",
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_phase2_progress(phase2_progress)
if idx < len(need_process):
wait_seconds(PERSON_DELAY_SECONDS, "人物之间间隔")
continue
if status1 != "ok":
fail_count += 1
print(" 打开人物详情失败,本次不写进度,下次重试。")
if idx < len(need_process):
wait_seconds(PERSON_DELAY_SECONDS, "人物之间间隔")
continue
detail_success_count += 1
# 打开详情页后,等一会儿再重新读取一次详情
wait_seconds(DETAIL_RECHECK_DELAY_SECONDS, "已打开人物详情,重新检查头像标记前")
status2, detail2 = get_person_detail(person_id)
if status2 in {"rate_limited", "blocked"}:
save_phase2_progress(phase2_progress)
print("\n检测到限流/封锁风险,脚本停止。")
sys.exit(1)
if status2 == "network_500":
network_500_count += 1
print(" 二次读取人物详情时发生网络类 500,本次不写进度,下次重试。")
if network_500_count >= 3:
save_phase2_progress(phase2_progress)
print("\n连续出现 3 次网络类 500,脚本停止。请确认代理/Emby 后再运行。")
sys.exit(1)
if idx < len(need_process):
wait_seconds(PERSON_DELAY_SECONDS, "人物之间间隔")
continue
if status2 == "server_error_500":
server_500_count += 1
print(" 二次读取人物详情时发生服务端 500,记录后跳过。")
phase2_progress["persons"][person_id] = {
"name": name,
"status": "server_error_500_on_recheck",
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_phase2_progress(phase2_progress)
if idx < len(need_process):
wait_seconds(PERSON_DELAY_SECONDS, "人物之间间隔")
continue
if status2 != "ok":
fail_count += 1
print(" 二次读取人物详情失败,本次不写进度,下次重试。")
if idx < len(need_process):
wait_seconds(PERSON_DELAY_SECONDS, "人物之间间隔")
continue
image_tags = detail2.get("ImageTags") or {}
primary_tag = image_tags.get("Primary")
if not primary_tag:
still_no_primary_count += 1
print(" 打开人物详情后仍然没有头像标记,记录为 still_no_primary。")
phase2_progress["persons"][person_id] = {
"name": name,
"status": "still_no_primary",
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_phase2_progress(phase2_progress)
if idx < len(need_process):
wait_seconds(PERSON_DELAY_SECONDS, "人物之间间隔")
continue
# 已经拿到头像标记,正式请求头像
print(f" 详情补全成功,发现头像标记:{primary_tag}")
image_result = request_person_image(person_id, name, primary_tag)
if image_result == "ok":
image_ok_count += 1
phase2_progress["persons"][person_id] = {
"name": name,
"status": "ok",
"primary_tag": primary_tag,
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_phase2_progress(phase2_progress)
elif image_result == "network_500":
network_500_count += 1
print(" 请求头像时发生网络类 500,不写进度,下次重试。")
if network_500_count >= 3:
save_phase2_progress(phase2_progress)
print("\n连续出现 3 次网络类 500,脚本停止。请确认代理/Emby 后再运行。")
sys.exit(1)
elif image_result == "server_error_500":
server_500_count += 1
phase2_progress["persons"][person_id] = {
"name": name,
"status": "server_error_500_on_image",
"primary_tag": primary_tag,
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_phase2_progress(phase2_progress)
elif image_result in {"not_found"}:
phase2_progress["persons"][person_id] = {
"name": name,
"status": image_result,
"primary_tag": primary_tag,
"time": time.strftime("%Y-%m-%d %H:%M:%S")
}
save_phase2_progress(phase2_progress)
elif image_result in {"rate_limited", "blocked"}:
save_phase2_progress(phase2_progress)
print("\n检测到限流/封锁风险,脚本停止。")
sys.exit(1)
else:
fail_count += 1
print(" 请求头像失败,本次不写进度,下次重试。")
processed += 1
if idx < len(need_process):
wait_seconds(PERSON_DELAY_SECONDS, "人物之间间隔")
if processed > 0 and processed % BATCH_SIZE == 0:
save_phase2_progress(phase2_progress)
wait_seconds(BATCH_REST_SECONDS, f"已处理 {processed} 个人物,本批休息")
save_phase2_progress(phase2_progress)
print("\n全部处理完成。")
print(f"成功打开详情:{detail_success_count}")
print(f"成功刷出头像:{image_ok_count}")
print(f"打开详情后仍无头像标记:{still_no_primary_count}")
print(f"网络类500{network_500_count}")
print(f"服务端500{server_500_count}")
print(f"其他失败:{fail_count}")
print(f"第二阶段进度文件:{PHASE2_PROGRESS_FILE}")
```