Skip to content

Commit e1b0544

Browse files
committed
fix: update tool_cache
1 parent 46d2433 commit e1b0544

6 files changed

Lines changed: 393 additions & 73 deletions

File tree

extract_ths_tool_cache/compress.sh

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,40 @@
11
#!/bin/bash
22

3-
# 定义目标文件夹和输出文件名前缀
3+
# 1. 定义变量
44
TARGET_DIR="tool_cache"
5-
OUTPUT_PREFIX="extract_ths_tool_cache/tool_cache.tar.gz.part_"
5+
OUTPUT_DIR="extract_ths_tool_cache"
6+
OUTPUT_PREFIX="${OUTPUT_DIR}/tool_cache.tar.gz.part_"
67
SIZE_LIMIT="50m"
78

8-
echo "正在压缩并切分 $TARGET_DIR ..."
9+
# 确保输出目录存在
10+
mkdir -p "$OUTPUT_DIR"
911

10-
# tar -c: 创建
11-
# -z: gzip压缩
12-
# -f -: 将结果输出到标准输出流
13-
# split -b: 按大小切分
14-
# -: 从标准输入流读取
15-
tar -czf - "$TARGET_DIR" --exclude='.DS_Store' | split -b $SIZE_LIMIT - "$OUTPUT_PREFIX"
12+
# 2. 检查目标文件夹是否存在
13+
if [ ! -d "$TARGET_DIR" ]; then
14+
echo "错误:未发现目标文件夹 '$TARGET_DIR'"
15+
exit 1
16+
fi
1617

17-
echo "压缩完成!生成的切分文件如下:"
18-
ls -lh ${OUTPUT_PREFIX}*
18+
echo "正在压缩并切分 $TARGET_DIR (已过滤 macOS 冗余文件) ..."
19+
20+
# 3. 清理旧的分卷文件,防止新旧文件混淆
21+
rm -f ${OUTPUT_PREFIX}*
22+
23+
# 4. 执行核心压缩与切分
24+
# COPYFILE_DISABLE=1: 禁止 macOS tar 产生新的 AppleDouble (._) 文件
25+
# --exclude='._*': 显式排除磁盘上已有的 ._ 开头的元数据文件
26+
# --exclude='.DS_Store': 排除文件夹索引文件
27+
COPYFILE_DISABLE=1 tar -czf - \
28+
--exclude='._*' \
29+
--exclude='.DS_Store' \
30+
--exclude='__MACOSX' \
31+
"$TARGET_DIR" | split -b $SIZE_LIMIT - "$OUTPUT_PREFIX"
32+
33+
# 5. 验证结果
34+
if [ $? -eq 0 ]; then
35+
echo "压缩完成!生成的切分文件如下:"
36+
ls -lh ${OUTPUT_PREFIX}*
37+
else
38+
echo "错误:压缩过程中出现问题。"
39+
exit 1
40+
fi
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
#!/usr/bin/env python3
2+
"""
3+
同步 progress 文件和缓存文件的 completed_codes。
4+
5+
功能:
6+
1. 遍历 progress 目录中的所有进度文件
7+
2. 对于每个进度文件,找到对应的缓存文件(可能有多个,如 _01.json, _02.json)
8+
3. 加载所有缓存文件,提取所有的 code
9+
4. 对比 progress 中的 completed_codes 和缓存文件中的 codes
10+
5. 如果不一致,打印出来并更新 progress 文件
11+
6. 删除 time_records
12+
7. 统计每个工具的爬取进度
13+
"""
14+
15+
import csv
16+
import json
17+
import os
18+
import glob
19+
import re
20+
from pathlib import Path
21+
22+
23+
def get_tool_name_from_progress(progress_file: str) -> str:
24+
"""从 progress 文件名提取工具名称。
25+
26+
例如: crawl_ths_bonus_progress.json -> crawl_ths_bonus
27+
"""
28+
filename = os.path.basename(progress_file)
29+
# 去掉 _progress.json 后缀
30+
return filename.replace("_progress.json", "")
31+
32+
33+
def find_cache_files(tool_name: str, cache_dir: str) -> list[str]:
34+
"""找到工具对应的所有缓存文件。
35+
36+
例如: crawl_ths_bonus -> [crawl_ths_bonus_01.json, crawl_ths_bonus_02.json, ...]
37+
"""
38+
pattern = os.path.join(cache_dir, f"{tool_name}_*.json")
39+
cache_files = glob.glob(pattern)
40+
# 排序确保顺序一致
41+
return sorted(cache_files)
42+
43+
44+
def load_cache_codes(cache_files: list[str]) -> set[str]:
45+
"""从所有缓存文件中加载 codes。"""
46+
codes = set()
47+
for cache_file in cache_files:
48+
try:
49+
with open(cache_file, "r", encoding="utf-8") as f:
50+
data = json.load(f)
51+
for item in data:
52+
if "tool_args" in item and "code" in item["tool_args"]:
53+
codes.add(item["tool_args"]["code"])
54+
except Exception as e:
55+
print(f" [错误] 加载缓存文件失败 {cache_file}: {e}")
56+
return codes
57+
58+
59+
def load_progress(progress_file: str) -> dict:
60+
"""加载 progress 文件。"""
61+
with open(progress_file, "r", encoding="utf-8") as f:
62+
return json.load(f)
63+
64+
65+
def save_progress(progress_file: str, data: dict):
66+
"""保存 progress 文件。"""
67+
with open(progress_file, "w", encoding="utf-8") as f:
68+
json.dump(data, f, ensure_ascii=False, indent=2)
69+
70+
71+
def get_tool_name_from_cache(cache_file: str) -> str | None:
72+
"""从缓存文件名提取工具名称。
73+
74+
例如: crawl_ths_bonus_01.json -> crawl_ths_bonus
75+
"""
76+
filename = os.path.basename(cache_file)
77+
# 去掉 _01.json, _02.json 等后缀
78+
match = re.match(r"(.+)_\d+\.json$", filename)
79+
if match:
80+
return match.group(1)
81+
return None
82+
83+
84+
def find_all_tool_names(cache_dir: str) -> set[str]:
85+
"""找到所有工具名称(从缓存文件中提取)。"""
86+
cache_files = glob.glob(os.path.join(cache_dir, "crawl_ths_*_*.json"))
87+
tool_names = set()
88+
for cache_file in cache_files:
89+
tool_name = get_tool_name_from_cache(cache_file)
90+
if tool_name:
91+
tool_names.add(tool_name)
92+
return tool_names
93+
94+
95+
def load_all_stock_codes(csv_file: str) -> set[str]:
96+
"""从 CSV 文件加载所有股票代码。"""
97+
codes = set()
98+
with open(csv_file, "r", encoding="utf-8") as f:
99+
reader = csv.DictReader(f)
100+
for row in reader:
101+
codes.add(row["symbol"])
102+
return codes
103+
104+
105+
def sync_progress_with_cache():
106+
"""主函数:同步 progress 和 cache。"""
107+
# 配置路径
108+
base_dir = Path(__file__).parent.parent
109+
cache_dir = base_dir / "tool_cache"
110+
progress_dir = cache_dir / "progress"
111+
stock_csv = base_dir / "tushare_stock_basic_20251226104714.csv"
112+
113+
print(f"缓存目录: {cache_dir}")
114+
print(f"进度目录: {progress_dir}")
115+
print(f"股票代码文件: {stock_csv}")
116+
print("=" * 80)
117+
118+
# 加载所有股票代码
119+
all_stock_codes = load_all_stock_codes(str(stock_csv))
120+
total_stocks = len(all_stock_codes)
121+
print(f"总股票代码数量: {total_stocks}")
122+
print("=" * 80)
123+
124+
# 从缓存文件中找到所有工具名称
125+
all_tool_names = find_all_tool_names(str(cache_dir))
126+
print(f"发现 {len(all_tool_names)} 个工具: {sorted(all_tool_names)}")
127+
128+
total_updated = 0
129+
total_created = 0
130+
131+
# 统计信息
132+
stats = []
133+
134+
for tool_name in sorted(all_tool_names):
135+
print(f"\n处理工具: {tool_name}")
136+
137+
# 找到对应的缓存文件
138+
cache_files = find_cache_files(tool_name, str(cache_dir))
139+
140+
if not cache_files:
141+
print(f" [警告] 没有找到对应的缓存文件,跳过")
142+
stats.append({
143+
"tool_name": tool_name,
144+
"completed": 0,
145+
"remaining": total_stocks,
146+
"progress": 0.0
147+
})
148+
continue
149+
150+
print(f" 缓存文件: {[os.path.basename(f) for f in cache_files]}")
151+
152+
# 加载缓存中的 codes
153+
cache_codes = load_cache_codes(cache_files)
154+
print(f" 缓存中的 codes 数量: {len(cache_codes)}")
155+
156+
# 检查进度文件是否存在
157+
progress_file = str(progress_dir / f"{tool_name}_progress.json")
158+
progress_exists = os.path.exists(progress_file)
159+
160+
if progress_exists:
161+
print(f" 进度文件: {os.path.basename(progress_file)}")
162+
# 加载 progress
163+
progress_data = load_progress(progress_file)
164+
progress_codes = set(progress_data.get("completed_codes", []))
165+
print(f" 进度中的 completed_codes 数量: {len(progress_codes)}")
166+
167+
# 检查是否有 time_records
168+
has_time_records = "time_records" in progress_data
169+
if has_time_records:
170+
print(f" 进度中存在 time_records,将被删除")
171+
else:
172+
print(f" [新建] 进度文件不存在,将创建: {os.path.basename(progress_file)}")
173+
progress_codes = set()
174+
has_time_records = False
175+
176+
# 对比差异
177+
only_in_progress = progress_codes - cache_codes
178+
only_in_cache = cache_codes - progress_codes
179+
180+
if only_in_progress:
181+
print(f" [不一致] 进度中有但缓存中没有的 codes ({len(only_in_progress)}个):")
182+
# 打印前10个
183+
sample = sorted(only_in_progress)[:10]
184+
print(f" 示例: {sample}")
185+
if len(only_in_progress) > 10:
186+
print(f" ... 还有 {len(only_in_progress) - 10} 个")
187+
188+
if only_in_cache:
189+
print(f" [不一致] 缓存中有但进度中没有的 codes ({len(only_in_cache)}个):")
190+
# 打印前10个
191+
sample = sorted(only_in_cache)[:10]
192+
print(f" 示例: {sample}")
193+
if len(only_in_cache) > 10:
194+
print(f" ... 还有 {len(only_in_cache) - 10} 个")
195+
196+
# 判断是否需要更新
197+
needs_update = (only_in_progress or only_in_cache or has_time_records or not progress_exists)
198+
199+
if needs_update:
200+
# 更新/创建 progress
201+
new_progress_data = {
202+
"completed_codes": sorted(list(cache_codes))
203+
}
204+
save_progress(progress_file, new_progress_data)
205+
if progress_exists:
206+
print(f" [更新] 已更新进度文件,新的 completed_codes 数量: {len(cache_codes)}")
207+
total_updated += 1
208+
else:
209+
print(f" [创建] 已创建进度文件,completed_codes 数量: {len(cache_codes)}")
210+
total_created += 1
211+
else:
212+
print(f" [一致] 无需更新")
213+
214+
# 统计爬取进度
215+
completed = len(cache_codes)
216+
remaining = total_stocks - completed
217+
progress_pct = (completed / total_stocks) * 100 if total_stocks > 0 else 0
218+
stats.append({
219+
"tool_name": tool_name,
220+
"completed": completed,
221+
"remaining": remaining,
222+
"progress": progress_pct
223+
})
224+
225+
# 打印统计汇总
226+
print("\n" + "=" * 80)
227+
print(f"总计更新了 {total_updated} 个进度文件,创建了 {total_created} 个新进度文件")
228+
229+
print("\n" + "=" * 80)
230+
print("爬取进度统计(总股票数: {})".format(total_stocks))
231+
print("=" * 80)
232+
print(f"{'\u5de5\u5177\u540d\u79f0':<25} {'\u5df2\u5b8c\u6210':>10} {'\u5269\u4f59':>10} {'\u8fdb\u5ea6':>10}")
233+
print("-" * 60)
234+
235+
incomplete_tools = []
236+
for stat in stats:
237+
status = "✓" if stat["remaining"] == 0 else ""
238+
print(f"{stat['tool_name']:<25} {stat['completed']:>10} {stat['remaining']:>10} {stat['progress']:>9.1f}% {status}")
239+
if stat["remaining"] > 0:
240+
incomplete_tools.append(stat)
241+
242+
print("-" * 60)
243+
244+
# 汇总未完成的工具
245+
if incomplete_tools:
246+
print(f"\n未完成的工具数量: {len(incomplete_tools)}/{len(stats)}")
247+
total_remaining = sum(t["remaining"] for t in incomplete_tools)
248+
print(f"总剩余爬取记录数: {total_remaining}")
249+
print("\n未完成工具详情:")
250+
for stat in sorted(incomplete_tools, key=lambda x: x["remaining"], reverse=True):
251+
print(f" - {stat['tool_name']}: 剩余 {stat['remaining']} 条,已完成 {stat['progress']:.1f}%")
252+
else:
253+
print("\n所有工具均已爬取完成!")
254+
255+
256+
if __name__ == "__main__":
257+
sync_progress_with_cache()
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
7.46 MB
Binary file not shown.

0 commit comments

Comments
 (0)