feat: 多趟循环下载直到无新增唯一录像,补齐单趟遗漏

- tap_all_thumbnails 返回本趟点击的未打勾卡片数
- 新增 tap_all_thumbnails_multipass:同一 App 会话内反复回顶重扫、
  只补漏点卡,用"设备唯一内容(md5)增量=0"判完整——对蓝勾约4.5%
  假阴性、死录像均免疫("零点击"判据因假阴性永远达不到,已弃用)
- sync_recent_days 接入多趟循环,替代单趟 tap+wait
- 配套:cell_has_card 增亮度判据识别灰色占位卡;到底重试加耐心(6次/7s)

实测 09-19:清理存储后单趟 120→281,合并归档达 285 完整;
多趟复验第 1 趟 21 次假阴性补点、新增唯一 0,正确判完整并 1 趟退出。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yuming
2026-09-23 18:00:52 +08:00
parent 8010aab3a0
commit 548ab3b2f1
+173 -67
View File
@@ -72,6 +72,7 @@ THUMB_LOAD_WAIT = 1.0 # 每轮截图前等待缩略图从"灰占位"加
# 空白列纯色 std≈0。 # 空白列纯色 std≈0。
CELL_STD_WIN = (200, 120) # 判断格子有无卡片的采样窗口(取卡片主体,避开左下角蓝勾角标) CELL_STD_WIN = (200, 120) # 判断格子有无卡片的采样窗口(取卡片主体,避开左下角蓝勾角标)
CELL_STD_LOW = 12 # 窗口灰度 std >= 判为有卡片,< 判为空列(无卡片,跳过不点) CELL_STD_LOW = 12 # 窗口灰度 std >= 判为有卡片,< 判为空列(无卡片,跳过不点)
CELL_BRIGHT_HIGH = 220 # 窗口平均亮度 < 此值也判为有卡:灰色未加载占位卡(实测≈143)比纯白空隙(255)暗
# 滚动真实位移测量参数(用像素行剖面互相关,替代不可靠的 detect_card_rows 中位数)。 # 滚动真实位移测量参数(用像素行剖面互相关,替代不可靠的 detect_card_rows 中位数)。
# detect_card_rows 每次检出的行集合、缩略图加载状态都在变,中位数乱跳,会把"滚了几行" # detect_card_rows 每次检出的行集合、缩略图加载状态都在变,中位数乱跳,会把"滚了几行"
@@ -83,6 +84,11 @@ MIN_SCROLL_PX = 80 # 单次滚动真实位移 < 此值才视为"没滚
# 提前退出→底部06:00~00:00一大段没下。80>底部占位回弹抖动(实测<40px) # 提前退出→底部06:00~00:00一大段没下。80>底部占位回弹抖动(实测<40px)
MAX_SCROLL_PX = 1100 # 单次滚动真实位移 > 此值视为滚过头,向上回滚补偿(保险,实测罕见) MAX_SCROLL_PX = 1100 # 单次滚动真实位移 > 此值视为滚过头,向上回滚补偿(保险,实测罕见)
WATERLINE = 4 # 水位泵:任意时刻最多几个在途下载。CB60 是 4G 电池机、回放下载
# 串行且慢,4 个相对稳健——比旧版"每批死等3个"略高一档以填满流水线
# 提速,又不至于压垮下载队列产生 0 字节失败。在途按"已点但未打勾
# 的格子数"逐格精确统计(见 clear_current_screen)。
def adb(*args, timeout=60): def adb(*args, timeout=60):
"""执行 adb 命令。带超时防止个别命令(尤其 uiautomator dump 在 App 卡死时) """执行 adb 命令。带超时防止个别命令(尤其 uiautomator dump 在 App 卡死时)
@@ -400,8 +406,12 @@ def measure_scroll_shift(before_png, after_png):
def cell_has_card(png_path, gx, cy): def cell_has_card(png_path, gx, cy):
"""判断 (gx,cy) 这个格子处是否真有一张卡片缩略图(区分稀疏行里的空列)。 """判断 (gx,cy) 这个格子处是否真有一张卡片缩略图(区分稀疏行里的空列)。
卡片缩略图有画面纹理 → 局部窗口灰度 std 高;空列是纯色背景 → std≈0。 两条判据并联,满足其一即认为有卡:
见 CELL_STD_WIN/CELL_STD_LOW 说明。出错时保守返回 True(宁可点也不漏下载)。""" ① 有画面纹理 → 局部窗口灰度 std 高(已加载的彩色缩略图);
② 整体够暗 → 平均亮度 < CELL_BRIGHT_HIGH(灰色未加载占位卡,实测≈143,
明显暗于纯白空隙≈255)。这条专治"缩略图没加载完的老录像被当空列漏点"。
空列是纯白背景(亮≈255、std≈0),两条都不满足 → 判为空。
见 CELL_STD_WIN/CELL_STD_LOW/CELL_BRIGHT_HIGH 说明。出错时保守返回 True(宁可点也不漏下载)。"""
w, h = CELL_STD_WIN w, h = CELL_STD_WIN
x0, y0 = gx - w // 2, cy - h // 2 x0, y0 = gx - w // 2, cy - h // 2
try: try:
@@ -412,7 +422,8 @@ def cell_has_card(png_path, gx, cy):
vals = list(small.getdata()) vals = list(small.getdata())
mean = sum(vals) / len(vals) mean = sum(vals) / len(vals)
var = sum((v - mean) ** 2 for v in vals) / len(vals) var = sum((v - mean) ** 2 for v in vals) / len(vals)
return math.sqrt(var) >= CELL_STD_LOW std = math.sqrt(var)
return std >= CELL_STD_LOW or mean < CELL_BRIGHT_HIGH
except Exception: except Exception:
return True return True
@@ -790,27 +801,42 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
print(f"→ 开始批量下载循环(最多 {max_rounds} 轮)") print(f"→ 开始批量下载循环(最多 {max_rounds} 轮)")
dialog_dismissed = False dialog_dismissed = False
total_tapped = 0 # 本趟(一次完整 top→bottom)累计点击的未打勾卡片数,供多趟循环判完整
def clear_current_screen(round_no): def clear_current_screen(round_no):
"""把当前屏所有"真卡片且未下载"的格子分批(每批≤9 防超并发)点击并等打勾, """把当前屏所有"真卡片且未下载"的格子下完,直到整屏全部打勾才允许滚动。
直到本屏没有未下载卡片(或达批次上限)。返回 (本屏点击总数, 是否检出过卡片行)。 返回 (本屏点击总数, 是否检出过卡片行)。
【本次重构的核心】点击与滚动必须解耦:旧版每轮只点 3 个(≈1 行)却往下滚 【设计要点·串行下载版】
1200px(≈6 行),中间 5 行整片漏掉(实测昨日 100+ 段只下 53 段)。改为"当前屏 ① 点击与滚动解耦——整屏未下载卡片不全部打勾就绝不滚动,结构上零漏行。
全部下完才前进",结构上不可能漏。attempted 记录本屏已点过的格子,避免个别 ② 水位泵:维持在途下载数≤WATERLINE。App 实为串行下载(一次只真下一个),泵只为
下载慢/失败的卡片一直不打勾被反复重点(重试点击是重复下载的根源,一并去掉)。""" 填满其队列、消除"整批下完→截图滚动"的空转间隙。在途按"已点但尚未打勾的格子数"
逐格精确统计。
③ 【点完就滚 + 去重兜底】滚动的前置条件是"整屏每张卡都至少点过一次",不要求
真下完。最好的退出是整屏全打勾(重叠行都是勾,下屏天然跳过,零重复);若
max_wait 内一直没有新卡打勾(个别卡慢/失败),就把没点过的强制补点一遍后
直接滚动,未下完的靠下游 md5 去重、下次同步再补——绝不为个别慢卡把整屏乃至
整个列表卡死(这是"整屏全打勾才滚"卡在第 1 屏的教训)。
④ 本屏不滚动,故首帧锁定行中心 locked_rows,之后每轮都按同一批 y 采样打勾,
不受 detect_card_rows 帧间抖动影响。tapped 记录已点格子、点过不再点(重复点击
是重复下载的根源)。"""
nonlocal dialog_dismissed nonlocal dialog_dismissed
attempted = set()
screen_tapped = 0 screen_tapped = 0
had_rows = False had_rows = False
for b in range(8): # 一屏可见 6~7 行,8 批足够;防意外死循环 targets = None # 本屏所有"真卡片"格子 [(gx,cy)];None=尚未成功检出
locked_rows = None # 首帧锁定的行中心 y(本屏不滚动,后续都按此采样打勾)
tapped = set() # 已点过的格子(点过不再点)
prev_checked = -1 # 上轮已打勾格子数,用于识别停滞
stall = 0 # 距上次有新格子打勾的秒数
while True:
ensure_not_anr() ensure_not_anr()
# 截图前等一下,让"灰色占位"缩略图加载出真实画面——否则纯色占位 std≈0 # 截图前等缩略图从灰占位加载出真实画面,否则 std≈0 被当间隙漏检。
# 会被 detect_card_rows 当成间隙,导致整行漏检(见 THUMB_LOAD_WAIT 说明)。
time.sleep(THUMB_LOAD_WAIT) time.sleep(THUMB_LOAD_WAIT)
shot = screenshot("/tmp/_gridchk.png") shot = screenshot("/tmp/_gridchk.png")
# 首次成功检出时锁定本屏行中心与全部真卡片格子(含已打勾的)。
if targets is None:
rows = detect_card_rows(shot) rows = detect_card_rows(shot)
# 检测不到行时重试:缩略图加载慢、ANR 刚恢复、画面切换中都会临时检测不到。 # 检测不到行时重试:缩略图慢/ANR 刚恢复/画面切换中会临时检测不到。
retry = 0 retry = 0
while not rows and retry < 3: while not rows and retry < 3:
retry += 1 retry += 1
@@ -820,58 +846,74 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
if not rows: if not rows:
break break
had_rows = True had_rows = True
# 每屏动态实测卡片行中心 y(消除整点分组标签平移错位),逐格判打勾。 locked_rows = rows
chk = get_checked_cells_dynamic(shot, rows) chk = get_checked_cells_dynamic(shot, locked_rows)
badge_skipped = sum(1 for cy in rows for gx in THUMB_X if chk.get((gx, cy))) # 稀疏布局下同一行不一定 3 列都有卡片,用 cell_has_card 过滤空列,
# 稀疏布局下同一行不一定 3 列都有卡片,必须用 cell_has_card 过滤掉空列, # 否则点空→永远不打勾→白熬满超时。
# 否则点空→永远不打勾→等打勾白熬满超时。 targets = [(gx, cy) for cy in locked_rows for gx in THUMB_X
empty_skipped = sum(1 for cy in rows for gx in THUMB_X if cell_has_card(shot, gx, cy)]
if not chk.get((gx, cy)) and not cell_has_card(shot, gx, cy)) already = sum(1 for t in targets if chk.get(t))
# 收集"真卡片 且 未打勾 且 本屏还没点过"的格子(从上到下)。 print(f" 轮 {round_no}: 本屏 {len(locked_rows)} 行,卡片 {len(targets)},"
todo = [(gx, cy) for cy in rows for gx in THUMB_X f"已打勾 {already},待下载 {len(targets) - already}")
if not chk.get((gx, cy)) and cell_has_card(shot, gx, cy) if not targets:
and (gx, cy) not in attempted]
if not todo:
if b == 0:
print(f" 轮 {round_no}: 本屏 {len(rows)} 行,已打勾 {badge_skipped},空列 {empty_skipped},无待下载")
break break
batch = todo[:3] # 每批≤3:CB60 是 4G 电池机,回放下载串行且慢, else:
# 一次点太多(曾用9)会把下载队列压垮→大量 0 字节失败, chk = get_checked_cells_dynamic(shot, locked_rows)
# 还会让 App 卡死触发 uiautomator dump 挂起。实测 3 个稳妥。 # 本屏尚未打勾的目标格子。
baseline_checked = count_checked_badges(shot) remaining = [t for t in targets if not chk.get(t)]
for (gx, cy) in batch: n_checked = len(targets) - len(remaining)
# 最好的退出:整屏全部打勾(重叠行都是勾,下屏天然跳过,零重复)。
if not remaining:
print(f" 轮 {round_no}: 本屏 {len(targets)} 卡全部打勾,可滚动")
break
# 有新卡片打勾 → 下载在推进,重置停滞计时。
if n_checked > prev_checked:
prev_checked = n_checked
stall = 0
# 超时兜底:max_wait 内没有任何新卡片打勾(个别卡慢/失败),认定不必再等。
# 先把还没点过的强制补点一遍(保证每张卡至少被尝试过一次,绝不漏点),
# 然后直接 break 去滚动——未下完的靠下游 md5 去重、下次同步再补。
# 这样绝不会为个别慢卡把整屏乃至整个列表卡死。
if stall >= max_wait_per_round:
untapped_now = [t for t in remaining if t not in tapped]
for gx, cy in untapped_now:
adb("shell", "input", "tap", str(gx), str(cy)) adb("shell", "input", "tap", str(gx), str(cy))
time.sleep(0.5) time.sleep(0.5)
# 首次点击会弹"使用流量下载"确认框,点掉后补点一次该卡片。
if not dialog_dismissed and dismiss_dialog_if_any(): if not dialog_dismissed and dismiss_dialog_if_any():
dialog_dismissed = True dialog_dismissed = True
adb("shell", "input", "tap", str(gx), str(cy)) adb("shell", "input", "tap", str(gx), str(cy))
time.sleep(0.5) time.sleep(0.5)
attempted.add((gx, cy)) tapped.add((gx, cy))
screen_tapped += len(batch) screen_tapped += 1
print(f" 轮 {round_no} 批{b+1}: 检出 {len(rows)} 行,跳过已打勾 {badge_skipped},空列 {empty_skipped},点击 {len(batch)} 个") print(f" 轮 {round_no}: 等 {stall}s 无新完成,剩 {len(remaining)} 未下完"
# 等这批全部打勾(下载完成)再进入下一批/滚动。整屏打勾数达到"基线+本批数" f"已全部点过,靠去重兜底,继续")
# 即完成。不再重试点击(重试是重复下载的根源),超时未完成的靠本地/下游去重。
target = baseline_checked + len(batch)
waited = 0
cur = baseline_checked
while waited < max_wait_per_round:
time.sleep(poll_interval)
waited += poll_interval
ensure_not_anr()
cur = count_checked_badges(screenshot("/tmp/_waitchk.png"))
if cur >= target:
break break
got = cur - baseline_checked # 在途 = 已点但还没打勾;未点 = 还没点过的待下载格子。
if got < len(batch): untapped = [t for t in remaining if t not in tapped]
print(f" 轮 {round_no} 批{b+1}: 等 {waited}s 后 {got}/{len(batch)} 打勾,未完成的靠去重兜底,继续") inflight = len(remaining) - len(untapped)
else: # 水位泵:在途未满水位、且还有未点卡片,就逐个补点填满队列。
print(f" 轮 {round_no} 批{b+1}: {len(batch)} 个全部打勾({waited}s)") while inflight < WATERLINE and untapped:
gx, cy = untapped.pop(0)
adb("shell", "input", "tap", str(gx), str(cy))
time.sleep(0.5)
# 首次点击弹"使用流量下载"确认框,点掉后补点一次该卡片。
if not dialog_dismissed and dismiss_dialog_if_any():
dialog_dismissed = True
adb("shell", "input", "tap", str(gx), str(cy))
time.sleep(0.5)
tapped.add((gx, cy))
screen_tapped += 1
inflight += 1
print(f" 轮 {round_no}: 补点 ({gx},{cy}),在途 {inflight}/{WATERLINE},"
f"未点剩 {len(untapped)}")
time.sleep(poll_interval)
stall += poll_interval
return screen_tapped, had_rows return screen_tapped, had_rows
for r in range(max_rounds): for r in range(max_rounds):
# 先把当前屏所有未下载卡片全部下完(点击与滚动解耦,见 clear_current_screen)。 # 先把当前屏所有未下载卡片全部下完(点击与滚动解耦,见 clear_current_screen)。
tapped, had_rows = clear_current_screen(r + 1) tapped, had_rows = clear_current_screen(r + 1)
total_tapped += tapped
# 滚动 + 到底判定。用像素行剖面互相关精确测真实滚动位移(detect_card_rows # 滚动 + 到底判定。用像素行剖面互相关精确测真实滚动位移(detect_card_rows
# 中位数不可靠,会把"滚了几行"误判成"没动"→二次滚动→滚过头漏行,这是中段 # 中位数不可靠,会把"滚了几行"误判成"没动"→二次滚动→滚过头漏行,这是中段
@@ -889,15 +931,16 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
shift, sad = measure_scroll_shift(before_scroll, after_scroll) shift, sad = measure_scroll_shift(before_scroll, after_scroll)
if shift < MIN_SCROLL_PX: if shift < MIN_SCROLL_PX:
# 疑似到底。但本列表在下载队列堵/卡顿时单次滚动手势常失效或只滚一点点 # 疑似到底。但本列表【分页懒加载】:滚到已加载页末尾时,下一页(如 06:4x→00:00)
# (实测首次位移 8~176px 都出现过),不能凭一两次小位移就判到底——那样会把 # 还要经 3G 现拉,那一刻滑不动、位移=0,并非真到底(实测轮17就是这样把整段
# 06:00~00:00 一整段没滚到就误判结束(实测轮15二次滚了156px其实还在滚,却因 # 06:4x→00:00 切掉漏下)。也可能是队列堵/卡顿导致单次手势只滚一点点。
# 旧阈值250被当到底提前退出)。改为连续【3 次】重试滚动位移都<阈值才确认真到底; # 改法A·加耐心:连续【6 次】重试、每次滑动前多等 7 秒给下一页加载时间,
# 其间任一次滚动生效(>=阈值)就跳出重试、继续正常循环。 # 全部 <阈值 才确认真到底;其间任一次滚动生效(>=阈值)就跳出、继续正常循环。
# 真到底只多花几十秒(每次跑一次),却能救回被误切的底部整段。
confirmed_bottom = True confirmed_bottom = True
shifts = [shift] shifts = [shift]
for retry in range(3): for retry in range(6):
time.sleep(2) time.sleep(7) # 关键:给懒加载的下一页足够时间经 3G 拉出来
b2 = screenshot("/tmp/_prescroll2.png") b2 = screenshot("/tmp/_prescroll2.png")
adb("shell", "input", "swipe", "540", "1800", "540", "1150", "500") adb("shell", "input", "swipe", "540", "1800", "540", "1150", "500")
time.sleep(2.0) time.sleep(2.0)
@@ -908,13 +951,19 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
print(f" 轮 {r+1}: 滚动未生效(位移{shift}px),第{retry+1}次重试已推进({s2}px),继续") print(f" 轮 {r+1}: 滚动未生效(位移{shift}px),第{retry+1}次重试已推进({s2}px),继续")
confirmed_bottom = False confirmed_bottom = False
break break
print(f" 轮 {r+1}: 到底确认中,第{retry+1}/3次重试位移{s2}px(<{MIN_SCROLL_PX})") print(f" 轮 {r+1}: 到底确认中,第{retry+1}/6次重试位移{s2}px(<{MIN_SCROLL_PX})")
if confirmed_bottom: if confirmed_bottom:
# 到底确认后,那 3 次重试滚动虽位移<阈值,但累积的微小移动会把最底 # 到底确认后,那 6 次重试滚动虽位移<阈值,但累积的微小移动会把最底
# 一两行完整露出来(实测到底后 06:19~06:30 两行已显示却从未被点、漏下)—— # 一两行完整露出来(实测到底后 06:19~06:30 两行已显示却从未被点、漏下)——
# 这最终画面从没被 clear 过。break 前对最终屏补扫一次,确保最后几行不漏。 # 这最终画面从没被 clear 过。break 前对最终屏补扫一次,确保最后几行不漏。
print(f" 轮 {r+1}: 连续多次强力滚动均滚不动{shifts},已到底,补扫最终屏") print(f" 轮 {r+1}: 连续多次强力滚动均滚不动{shifts},已到底,补扫最终屏")
clear_current_screen(r + 1) final_tapped, final_had_rows = clear_current_screen(r + 1)
total_tapped += final_tapped
if not final_had_rows and r > 0:
# 一行都检不出来:可能整页还在"加载中"(并非真到底)。已连续 6 次滚不动
# 才走到这里,通常就是真到底/空页;仅打一条提示,不再判死。个别因加载慢
# 漏掉的片段靠 daily 重跑 + md5 去重下次补齐。
print(f" 轮 {r+1}: ⚠ 到底判定时未检出卡片行(可能列表加载慢),按到底结束")
print(f" 轮 {r+1}: 确认到底,结束") print(f" 轮 {r+1}: 确认到底,结束")
break break
continue continue
@@ -929,7 +978,63 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
print(f" 轮 {r+1}: 本屏已清空(点击 {tapped} 个),滚动 {shift}px(≈{shift/204:.1f}行),继续") print(f" 轮 {r+1}: 本屏已清空(点击 {tapped} 个),滚动 {shift}px(≈{shift/204:.1f}行),继续")
print("→ 批量下载循环结束") print(f"→ 批量下载循环结束(本趟累计点击 {total_tapped} 个未打勾卡片)")
return total_tapped
def _device_unique_count():
"""统计今日下载目录里【唯一内容】的数量(按整文件 md5 去重,与本地 _dedup 同口径)。
多趟循环用它判"这一趟有没有真的补到新录像"。用设备端 md5sum 计算,避免拉回本地。"""
today = datetime.now().strftime("%Y%m%d")
remote_dir = f"/sdcard/Android/data/{PKG}/files/{today}"
r = adb("shell", "md5sum", f"{remote_dir}/*_BH*")
digests = set()
for line in r.stdout.strip().splitlines():
line = line.strip()
if not line or "No such file" in line:
continue
digests.add(line.split()[0])
return len(digests)
def tap_all_thumbnails_multipass(baseline, max_passes=5, **kw):
"""多趟循环下载,直到"某一趟没有新增任何唯一录像"=补全完整,或达 max_passes 上限兜底。
设计依据:tap_all_thumbnails 本身就是一次完整的 top→bottom 下载——开头先滚到顶,
每屏用蓝勾检测跳过已下载卡、只点未打勾卡,滚到底才结束。它不重启 App,所以反复
调用就是"在同一 App 会话内、回顶重扫、补下漏点卡",天然满足"同会话保住蓝勾"。
完整性判据——【本趟新增唯一内容数】而非"零点击":
蓝勾检测有约 4.5% 假阴性(把已下载卡误判成未下载),会让"零点击"永远达不到、
循环白跑满上限并狂造重复。改用"设备唯一内容(md5)增量"判完整:真遗漏被补下=新增
唯一内容;假阴性重复点=只造重复、不增唯一内容;死录像=下不动、不增。三种情况
里只有"真补到新录像"才让增量>0,故"某趟增量=0"就是"再没有可补的了"=完整。
这个判据对假阴性和死录像都免疫。
退出条件:
① 成功——某趟结束后唯一内容数没有增长(gained==0),确认无更多可补,完整。
② 兜底——达 max_passes 趟仍在新增(gained>0):极少见(说明每趟都还在捞到新录像,
可能列表极长或网络反复失败),记录警告后停止,避免无限循环。
趟间必须 wait_for_downloads:本趟点的卡此刻可能还没下完,等队列消化后再统计唯一数,
才能准确反映"这一趟到底补到了多少新录像"。
"""
prev_unique = _device_unique_count()
print(f"→ 多趟循环开始,设备当前唯一录像 {prev_unique} 个")
for p in range(max_passes):
print(f"\n===== 第 {p+1}/{max_passes} 趟下载开始 =====")
tapped = tap_all_thumbnails(baseline, **kw)
# 趟间等下载队列消化,让本趟点的卡都下完,唯一数统计才准确。
wait_for_downloads(baseline)
now_unique = _device_unique_count()
gained = now_unique - prev_unique
print(f"===== 第 {p+1} 趟结束:点击 {tapped} 个未打勾卡,"
f"新增唯一录像 {gained} 个(累计 {now_unique})=====")
if gained <= 0:
print(f"✓ 第 {p+1} 趟未补到新录像,确认已完整下载,结束多趟循环")
return
prev_unique = now_unique
print(f"⚠ 已达 {max_passes} 趟上限仍在新增录像,可能列表过长或网络反复失败,停止多趟循环")
def wait_for_downloads(baseline_files, timeout=600): def wait_for_downloads(baseline_files, timeout=600):
@@ -1056,7 +1161,7 @@ def sync():
baseline = get_baseline_files() baseline = get_baseline_files()
print(f"→ 基准文件数: {len(baseline)}") print(f"→ 基准文件数: {len(baseline)}")
navigate_to_yesterday_batch() navigate_to_yesterday_batch()
tap_all_thumbnails(baseline) # 默认 max_rounds=300,跑到滚到底为止 tap_all_thumbnails(baseline)
wait_for_downloads(baseline) wait_for_downloads(baseline)
all_files = get_all_remote_files() all_files = get_all_remote_files()
@@ -1102,8 +1207,9 @@ def sync_recent_days(n_days=7):
launch_app() launch_app()
clear_device_recordings() # 下载前清空,保证有空间、且设备只剩这天的 clear_device_recordings() # 下载前清空,保证有空间、且设备只剩这天的
navigate_to_yesterday_batch(days_back=d) navigate_to_yesterday_batch(days_back=d)
tap_all_thumbnails(set()) # 多趟循环下载:反复回顶重扫补下漏点卡,直到某趟零点击=完整(或达上限兜底)。
wait_for_downloads(set()) # 内部已含趟间 wait_for_downloads,故不再单独调用。
tap_all_thumbnails_multipass(set())
day_files = get_all_remote_files() # 这一天下载的全部录像 day_files = get_all_remote_files() # 这一天下载的全部录像
if day_files: if day_files:
pull_files(list(day_files), date_str=rec_date) pull_files(list(day_files), date_str=rec_date)