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:
+182
-76
@@ -72,6 +72,7 @@ THUMB_LOAD_WAIT = 1.0 # 每轮截图前等待缩略图从"灰占位"加
|
||||
# 空白列纯色 std≈0。
|
||||
CELL_STD_WIN = (200, 120) # 判断格子有无卡片的采样窗口(取卡片主体,避开左下角蓝勾角标)
|
||||
CELL_STD_LOW = 12 # 窗口灰度 std >= 判为有卡片,< 判为空列(无卡片,跳过不点)
|
||||
CELL_BRIGHT_HIGH = 220 # 窗口平均亮度 < 此值也判为有卡:灰色未加载占位卡(实测≈143)比纯白空隙(255)暗
|
||||
|
||||
# 滚动真实位移测量参数(用像素行剖面互相关,替代不可靠的 detect_card_rows 中位数)。
|
||||
# detect_card_rows 每次检出的行集合、缩略图加载状态都在变,中位数乱跳,会把"滚了几行"
|
||||
@@ -83,6 +84,11 @@ MIN_SCROLL_PX = 80 # 单次滚动真实位移 < 此值才视为"没滚
|
||||
# 提前退出→底部06:00~00:00一大段没下。80>底部占位回弹抖动(实测<40px)
|
||||
MAX_SCROLL_PX = 1100 # 单次滚动真实位移 > 此值视为滚过头,向上回滚补偿(保险,实测罕见)
|
||||
|
||||
WATERLINE = 4 # 水位泵:任意时刻最多几个在途下载。CB60 是 4G 电池机、回放下载
|
||||
# 串行且慢,4 个相对稳健——比旧版"每批死等3个"略高一档以填满流水线
|
||||
# 提速,又不至于压垮下载队列产生 0 字节失败。在途按"已点但未打勾
|
||||
# 的格子数"逐格精确统计(见 clear_current_screen)。
|
||||
|
||||
|
||||
def adb(*args, timeout=60):
|
||||
"""执行 adb 命令。带超时防止个别命令(尤其 uiautomator dump 在 App 卡死时)
|
||||
@@ -400,8 +406,12 @@ def measure_scroll_shift(before_png, after_png):
|
||||
|
||||
def cell_has_card(png_path, 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
|
||||
x0, y0 = gx - w // 2, cy - h // 2
|
||||
try:
|
||||
@@ -412,7 +422,8 @@ def cell_has_card(png_path, gx, cy):
|
||||
vals = list(small.getdata())
|
||||
mean = sum(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:
|
||||
return True
|
||||
|
||||
@@ -790,88 +801,119 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
|
||||
|
||||
print(f"→ 开始批量下载循环(最多 {max_rounds} 轮)")
|
||||
dialog_dismissed = False
|
||||
total_tapped = 0 # 本趟(一次完整 top→bottom)累计点击的未打勾卡片数,供多趟循环判完整
|
||||
|
||||
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
|
||||
attempted = set()
|
||||
screen_tapped = 0
|
||||
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()
|
||||
# 截图前等一下,让"灰色占位"缩略图加载出真实画面——否则纯色占位 std≈0
|
||||
# 会被 detect_card_rows 当成间隙,导致整行漏检(见 THUMB_LOAD_WAIT 说明)。
|
||||
# 截图前等缩略图从灰占位加载出真实画面,否则 std≈0 被当间隙漏检。
|
||||
time.sleep(THUMB_LOAD_WAIT)
|
||||
shot = screenshot("/tmp/_gridchk.png")
|
||||
rows = detect_card_rows(shot)
|
||||
# 检测不到行时重试:缩略图加载慢、ANR 刚恢复、画面切换中都会临时检测不到。
|
||||
retry = 0
|
||||
while not rows and retry < 3:
|
||||
retry += 1
|
||||
time.sleep(2.0 + retry)
|
||||
shot = screenshot("/tmp/_gridchk.png")
|
||||
# 首次成功检出时锁定本屏行中心与全部真卡片格子(含已打勾的)。
|
||||
if targets is None:
|
||||
rows = detect_card_rows(shot)
|
||||
if not rows:
|
||||
# 检测不到行时重试:缩略图慢/ANR 刚恢复/画面切换中会临时检测不到。
|
||||
retry = 0
|
||||
while not rows and retry < 3:
|
||||
retry += 1
|
||||
time.sleep(2.0 + retry)
|
||||
shot = screenshot("/tmp/_gridchk.png")
|
||||
rows = detect_card_rows(shot)
|
||||
if not rows:
|
||||
break
|
||||
had_rows = True
|
||||
locked_rows = rows
|
||||
chk = get_checked_cells_dynamic(shot, locked_rows)
|
||||
# 稀疏布局下同一行不一定 3 列都有卡片,用 cell_has_card 过滤空列,
|
||||
# 否则点空→永远不打勾→白熬满超时。
|
||||
targets = [(gx, cy) for cy in locked_rows for gx in THUMB_X
|
||||
if 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)},"
|
||||
f"已打勾 {already},待下载 {len(targets) - already}")
|
||||
if not targets:
|
||||
break
|
||||
else:
|
||||
chk = get_checked_cells_dynamic(shot, locked_rows)
|
||||
# 本屏尚未打勾的目标格子。
|
||||
remaining = [t for t in targets if not chk.get(t)]
|
||||
n_checked = len(targets) - len(remaining)
|
||||
# 最好的退出:整屏全部打勾(重叠行都是勾,下屏天然跳过,零重复)。
|
||||
if not remaining:
|
||||
print(f" 轮 {round_no}: 本屏 {len(targets)} 卡全部打勾,可滚动")
|
||||
break
|
||||
had_rows = True
|
||||
# 每屏动态实测卡片行中心 y(消除整点分组标签平移错位),逐格判打勾。
|
||||
chk = get_checked_cells_dynamic(shot, rows)
|
||||
badge_skipped = sum(1 for cy in rows for gx in THUMB_X if chk.get((gx, cy)))
|
||||
# 稀疏布局下同一行不一定 3 列都有卡片,必须用 cell_has_card 过滤掉空列,
|
||||
# 否则点空→永远不打勾→等打勾白熬满超时。
|
||||
empty_skipped = sum(1 for cy in rows for gx in THUMB_X
|
||||
if not chk.get((gx, cy)) and not cell_has_card(shot, gx, cy))
|
||||
# 收集"真卡片 且 未打勾 且 本屏还没点过"的格子(从上到下)。
|
||||
todo = [(gx, cy) for cy in rows for gx in THUMB_X
|
||||
if not chk.get((gx, cy)) and cell_has_card(shot, gx, cy)
|
||||
and (gx, cy) not in attempted]
|
||||
if not todo:
|
||||
if b == 0:
|
||||
print(f" 轮 {round_no}: 本屏 {len(rows)} 行,已打勾 {badge_skipped},空列 {empty_skipped},无待下载")
|
||||
# 有新卡片打勾 → 下载在推进,重置停滞计时。
|
||||
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))
|
||||
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
|
||||
print(f" 轮 {round_no}: 等 {stall}s 无新完成,剩 {len(remaining)} 未下完"
|
||||
f"已全部点过,靠去重兜底,继续")
|
||||
break
|
||||
batch = todo[:3] # 每批≤3:CB60 是 4G 电池机,回放下载串行且慢,
|
||||
# 一次点太多(曾用9)会把下载队列压垮→大量 0 字节失败,
|
||||
# 还会让 App 卡死触发 uiautomator dump 挂起。实测 3 个稳妥。
|
||||
baseline_checked = count_checked_badges(shot)
|
||||
for (gx, cy) in batch:
|
||||
# 在途 = 已点但还没打勾;未点 = 还没点过的待下载格子。
|
||||
untapped = [t for t in remaining if t not in tapped]
|
||||
inflight = len(remaining) - len(untapped)
|
||||
# 水位泵:在途未满水位、且还有未点卡片,就逐个补点填满队列。
|
||||
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)
|
||||
attempted.add((gx, cy))
|
||||
screen_tapped += len(batch)
|
||||
print(f" 轮 {round_no} 批{b+1}: 检出 {len(rows)} 行,跳过已打勾 {badge_skipped},空列 {empty_skipped},点击 {len(batch)} 个")
|
||||
# 等这批全部打勾(下载完成)再进入下一批/滚动。整屏打勾数达到"基线+本批数"
|
||||
# 即完成。不再重试点击(重试是重复下载的根源),超时未完成的靠本地/下游去重。
|
||||
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
|
||||
got = cur - baseline_checked
|
||||
if got < len(batch):
|
||||
print(f" 轮 {round_no} 批{b+1}: 等 {waited}s 后 {got}/{len(batch)} 打勾,未完成的靠去重兜底,继续")
|
||||
else:
|
||||
print(f" 轮 {round_no} 批{b+1}: {len(batch)} 个全部打勾({waited}s)")
|
||||
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
|
||||
|
||||
for r in range(max_rounds):
|
||||
# 先把当前屏所有未下载卡片全部下完(点击与滚动解耦,见 clear_current_screen)。
|
||||
tapped, had_rows = clear_current_screen(r + 1)
|
||||
total_tapped += tapped
|
||||
|
||||
# 滚动 + 到底判定。用像素行剖面互相关精确测真实滚动位移(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)
|
||||
|
||||
if shift < MIN_SCROLL_PX:
|
||||
# 疑似到底。但本列表在下载队列堵/卡顿时单次滚动手势常失效或只滚一点点
|
||||
# (实测首次位移 8~176px 都出现过),不能凭一两次小位移就判到底——那样会把
|
||||
# 06:00~00:00 一整段没滚到就误判结束(实测轮15二次滚了156px其实还在滚,却因
|
||||
# 旧阈值250被当到底提前退出)。改为连续【3 次】重试滚动位移都<阈值才确认真到底;
|
||||
# 其间任一次滚动生效(>=阈值)就跳出重试、继续正常循环。
|
||||
# 疑似到底。但本列表【分页懒加载】:滚到已加载页末尾时,下一页(如 06:4x→00:00)
|
||||
# 还要经 3G 现拉,那一刻滑不动、位移=0,并非真到底(实测轮17就是这样把整段
|
||||
# 06:4x→00:00 切掉漏下)。也可能是队列堵/卡顿导致单次手势只滚一点点。
|
||||
# 改法A·加耐心:连续【6 次】重试、每次滑动前多等 7 秒给下一页加载时间,
|
||||
# 全部 <阈值 才确认真到底;其间任一次滚动生效(>=阈值)就跳出、继续正常循环。
|
||||
# 真到底只多花几十秒(每次跑一次),却能救回被误切的底部整段。
|
||||
confirmed_bottom = True
|
||||
shifts = [shift]
|
||||
for retry in range(3):
|
||||
time.sleep(2)
|
||||
for retry in range(6):
|
||||
time.sleep(7) # 关键:给懒加载的下一页足够时间经 3G 拉出来
|
||||
b2 = screenshot("/tmp/_prescroll2.png")
|
||||
adb("shell", "input", "swipe", "540", "1800", "540", "1150", "500")
|
||||
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),继续")
|
||||
confirmed_bottom = False
|
||||
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:
|
||||
# 到底确认后,那 3 次重试滚动虽位移<阈值,但累积的微小移动会把最底
|
||||
# 到底确认后,那 6 次重试滚动虽位移<阈值,但累积的微小移动会把最底
|
||||
# 一两行完整露出来(实测到底后 06:19~06:30 两行已显示却从未被点、漏下)——
|
||||
# 这最终画面从没被 clear 过。break 前对最终屏补扫一次,确保最后几行不漏。
|
||||
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}: 确认到底,结束")
|
||||
break
|
||||
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("→ 批量下载循环结束")
|
||||
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):
|
||||
@@ -1056,7 +1161,7 @@ def sync():
|
||||
baseline = get_baseline_files()
|
||||
print(f"→ 基准文件数: {len(baseline)}")
|
||||
navigate_to_yesterday_batch()
|
||||
tap_all_thumbnails(baseline) # 默认 max_rounds=300,跑到滚到底为止
|
||||
tap_all_thumbnails(baseline)
|
||||
wait_for_downloads(baseline)
|
||||
|
||||
all_files = get_all_remote_files()
|
||||
@@ -1102,8 +1207,9 @@ def sync_recent_days(n_days=7):
|
||||
launch_app()
|
||||
clear_device_recordings() # 下载前清空,保证有空间、且设备只剩这天的
|
||||
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() # 这一天下载的全部录像
|
||||
if day_files:
|
||||
pull_files(list(day_files), date_str=rec_date)
|
||||
|
||||
Reference in New Issue
Block a user