Files
py-chan/main.py
2026-08-11 16:18:44 +08:00

127 lines
4.3 KiB
Python
Raw 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.
"""缠论《教你炒股票》108 课抓取入口。
用法
----
python main.py # 抓取全部并生成数据(使用本地缓存)
python main.py --refresh # 忽略缓存重新抓取
python main.py --limit 5 # 仅抓前 5 篇(联调测试)
python main.py --no-images # 不下载图片,正文保留远程链接
产物
----
data/raw/p001.html ... 原始 HTML 归档
data/markdown/ke-001.md ... 每篇 Markdown 归档
data/articles.json 全量结构化数据
D:\\code\\chan\\src\\data\\chanlun.ts Vue 数据源(覆盖示例)
D:\\code\\chan\\public\\chanlun-img\\... 本地化图片
"""
import argparse
import json
import shutil
from datetime import datetime
from pathlib import Path
from config import (
CHAN_DATA_FILE,
INDEX_URL,
JSON_PATH,
MARKDOWN_DIR,
RAW_DIR,
article_url,
)
from fetcher import fetch_text
from parser import parse_article, parse_index
from writer import make_image_resolver, remote_image_resolver, write_ts_file
GEN_MARKER = "自动生成"
def cached_html(p_num: int, refresh: bool) -> str:
"""读取/抓取单篇 HTML落盘缓存以便重跑。"""
path = RAW_DIR / f"p{p_num:03d}.html"
if not refresh and path.exists():
return path.read_text(encoding="utf-8")
html = fetch_text(article_url(p_num))
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(html, encoding="utf-8")
return html
def main() -> None:
ap = argparse.ArgumentParser(description="抓取缠论《教你炒股票》系列")
ap.add_argument("--refresh", action="store_true", help="忽略缓存重新抓取")
ap.add_argument("--limit", type=int, default=0, help="仅抓取前 N 篇(测试)")
ap.add_argument("--no-images", action="store_true", help="不下载图片,保留远程链接")
args = ap.parse_args()
print("① 抓取目录页 …")
index_html = fetch_text(INDEX_URL)
pages = parse_index(index_html)
if args.limit:
pages = pages[: args.limit]
if not pages:
raise SystemExit("目录页未解析到任何文章链接")
print(f" 发现 {len(pages)} 篇文章p={pages[0]}{pages[-1]}")
RAW_DIR.mkdir(parents=True, exist_ok=True)
MARKDOWN_DIR.mkdir(parents=True, exist_ok=True)
articles: list[dict] = []
for i, p in enumerate(pages, 1):
print(f"② [{i}/{len(pages)}] 解析 p={p}")
html = cached_html(p, args.refresh)
resolver = remote_image_resolver if args.no_images else make_image_resolver(p, args.refresh)
art = parse_article(html, p, resolver)
(MARKDOWN_DIR / f"{art['slug']}.md").write_text(
f"# {art['title']}\n\n{art['content']}\n", encoding="utf-8"
)
articles.append(art)
# 源站少数篇目日期残缺/缺失(如 h5 仅写"2006"),按课次顺序用上一篇日期兜底
last_date = "2006-06-07"
fixed = 0
for a in articles:
if not a["date"]:
a["date"] = last_date
fixed += 1
else:
last_date = a["date"]
if fixed:
print(f" {fixed} 篇源站日期缺失,已用相邻课次日期兜底")
print("③ 写入归档 JSON …")
JSON_PATH.parent.mkdir(parents=True, exist_ok=True)
JSON_PATH.write_text(
json.dumps(articles, ensure_ascii=False, indent=2), encoding="utf-8"
)
print("④ 生成 Vue 数据文件 …")
_backup_sample_once(CHAN_DATA_FILE)
stamp = datetime.now().strftime("%Y-%m-%d %H:%M")
write_ts_file(articles, CHAN_DATA_FILE, stamp)
print(f"{CHAN_DATA_FILE}")
print("✅ 完成。")
print(f" 文章数:{len(articles)}")
print(" 归档目录data/")
print(" Vue 路由http://localhost:5173/chanlun")
def _backup_sample_once(path: Path) -> None:
"""首次覆盖前,把原始示例数据备份为 chanlun.sample.ts。"""
if not path.exists():
return
head = "\n".join(path.read_text(encoding="utf-8").splitlines()[:6])
if GEN_MARKER in head:
return # 已是生成文件,无需备份
backup = path.with_name("chanlun.sample.ts")
if backup.exists():
return
shutil.copy2(path, backup)
print(f" 已备份示例数据 → {backup.name}")
if __name__ == "__main__":
main()