#!/usr/bin/env bash # ============================================================ # 每日增量数据抓取(「缺几天就抓几天」) # # 用法: # ./deploy/daily-sync.sh # 抓全部目标 # ./deploy/daily-sync.sh --dry-run # 只看待抓清单,不调用接口 # ./deploy/daily-sync.sh --only price # 只补行情 # # 由 launchd 在每天 17:00 调用(见 deploy/install-sync-schedule.sh), # 也可以手工执行排障。退出码:0 = 全部成功;1 = 至少一个目标失败。 # # 为什么锁 + 日志轮转是必需的: # - 重叠运行:Tushare 限频是按接口算的,两个实例互相抢额度会让两边都变慢, # 还会把同一天的数据写两遍(幂等,但纯属浪费)。用 pid 锁直接跳过; # - 日志只增不减:每天一条,一年就是几百 KB 到数 MB,超过 5 MB 自动转 .1。 # ============================================================ set -euo pipefail PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PYTHON="${PROJECT_ROOT}/.venv/bin/python" LOG_DIR="${PROJECT_ROOT}/logs" LOG="${LOG_DIR}/daily-sync.log" LOCK_DIR="${LOG_DIR}/.daily-sync.lock" MAX_LOG_BYTES=$((5 * 1024 * 1024)) mkdir -p "${LOG_DIR}" # ---- 单实例锁(pid 存活性判定,避免残留锁永久阻塞)---- if [[ -d "${LOCK_DIR}" ]]; then holder="$(cat "${LOCK_DIR}/pid" 2>/dev/null || true)" if [[ -n "${holder}" ]] && kill -0 "${holder}" 2>/dev/null; then echo "$(date '+%F %T') [跳过] 上一次同步(pid=${holder})仍在运行" >> "${LOG}" exit 0 fi rm -rf "${LOCK_DIR}" fi mkdir -p "${LOCK_DIR}" echo $$ > "${LOCK_DIR}/pid" trap 'rm -rf "${LOCK_DIR}"' EXIT # ---- 日志轮转 ---- if [[ -f "${LOG}" ]] && [[ "$(wc -c < "${LOG}")" -gt ${MAX_LOG_BYTES} ]]; then mv -f "${LOG}" "${LOG}.1" fi if [[ ! -x "${PYTHON}" ]]; then echo "$(date '+%F %T') [错误] 虚拟环境不可用:${PYTHON}" >> "${LOG}" exit 1 fi rc=0 { echo "============================================================" echo "$(date '+%F %T') 每日增量同步开始 ${*:-(默认全部目标)}" cd "${PROJECT_ROOT}" export PYTHONPATH="${PROJECT_ROOT}/src" export LANG="${LANG:-zh_CN.UTF-8}" # 日志里只要进度与错误:httpx 每个请求一行 INFO 会把日志淹掉 # (同步进度本身走 print,不受日志级别影响)。需要排障时 HDIV_LOG_LEVEL=INFO。 LOG_LEVEL="${HDIV_LOG_LEVEL:-WARNING}" # 某个目标失败不影响其余目标(run 内部逐个捕获),这里只汇总退出码。 "${PYTHON}" -m hdiv --log-level "${LOG_LEVEL}" sync daily "$@" || rc=$? echo "$(date '+%F %T') 每日增量同步结束,exit=${rc}" } >> "${LOG}" 2>&1 exit "${rc}"