学术科研
需要在 BLS 职业数据上做抓取、解析、自定义 LLM…
用于探索 BLS Occupational Outlook Handbook 中 342 个职业的就业规模、薪资、学历、增长前景和自定义 LLM 评分,并以可切换颜色层的树状图展示。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
需要在 BLS 职业数据上做抓取、解析、特定
解决思路
- 克隆仓库并进入目录:`git clone https://github.com/karpathy/jobs`,`cd jobs`。
- 安装依赖:`uv sync`。
- 安装 Playwright 的 Chromium:`uv run playwright install chromium`。
- 创建 `.env` 文件并写入 `OPENROUTER_API_KEY=your_openrouter_key_here`;该 key 仅在 LLM 评分时需要。
- 完整新鲜构建第 1 步:抓取 BLS 页面,运行 `uv run python scrape.py`;`scrape.py` 使用非 headless Playwright,结果缓存到 `html/`,通常只需运行一次。
- 完整新鲜构建第 2 步:把原始 HTML 转成 `pages/` 中的干净 Markdown,运行 `uv run python process.py`。
- 完整新鲜构建第 3 步:提取结构化字段到 `occupations.csv`,运行 `uv run python make_csv.py`。
- 完整新鲜构建第 4 步:用 LLM 给 AI exposure 打分,运行 `uv run python score.py`,使用 OpenRouter API,结果保存到 `scores.json`。
- 完整新鲜构建第 5 步:合并 CSV 与分数生成前端数据,运行 `uv run python build_site_data.py`,输出 `site/data.json`。
- 完整新鲜构建第 6 步:本地服务可视化,运行 `cd site && python -m http.server 8000`,打开 `http://localhost:8000`。
- 关键文件 `occupations.json`:342 个职业主列表,包含 title、URL、category、slug。
- 关键文件 `occupations.csv`:汇总 pay、education、job_count、growth projections 等统计。
- 关键文件 `scores.json`:保存所有 342 个职业的 AI exposure 分数(0–10)与 rationales。
- 关键文件 `prompt.md`:把所有数据打包成一个约 45K-token 文件,供粘贴进 LLM。
- 关键文件 `html/`:BLS 原始 HTML 页面(约 40MB,source of truth)。
- 关键文件 `pages/`:每个职业页面的干净 Markdown 版本。
- 关键文件 `site/index.html`:树状图可视化,单个 HTML 文件。
- 关键文件 `site/data.json`:前端消费的紧凑合并数据。
- 关键文件 `score.py`:LLM 评分管线,fork 它来自定义 prompt。
- 写自定义 LLM 评分层时,编辑 `score.py` 中的 `SYSTEM_PROMPT`,定义评分标准与 0–10 分含义,并要求只返回 JSON:`{"score": <int 0-10>, "rationale": "<1-2 sentences>"}`。
- 评分管线会读取每个职业在 `pages/` 中的 Markdown,发送给 LLM,并把结果写入 `scores.json`。
- 自定义评分后重建站点数据:运行 `uv run python build_site_data.py`,再运行 `cd site && python -m http.server 8000`。
- `scores.json` 的结构:以 slug 为键,值为 `{"score": ..., "rationale": ...}`,共 342 个职业。
- `occupations.json` 的 entry 结构:包含 `title`、`url`、`category`、`slug`。
- `occupations.csv` 的列:`slug, title, category, median_pay, education, job_count, growth_percent, growth_outlook`。
- `site/data.json` 的 entry 结构:包含 `slug`、`title`、`category`、`median_pay`、`education`、`job_count`、`growth_percent`、`growth_outlook`、`ai_score`、`ai_rationale`。
- 前端树状图用 D3.js 在单个自包含 `site/index.html` 中实现;UI 可切换颜色层:BLS Outlook、Median Pay、Education、Digital AI Exposure。
- 给前端添加新颜色层:在 `site/index.html` 的图层切换按钮处添加按钮,例如 `<button onclick="setLayer('robotics_score')">Humanoid Robotics</button>`。
- 在 `site/index.html` 的 `getColor(d, layer)` 或 colorScale 函数中为新字段添加 case,例如 `if (layer === 'robotics_score') { return d3.interpolateRdYlBu(1 - d.robotics_score / 10); }`。
- 更新 `build_site_data.py`,把新评分字段加入 `site/data.json`。
- 生成 LLM-ready prompt 文件:运行 `uv run python make_prompt.py`,产生 `prompt.md`(约 45K tokens),可粘贴到 Claude、GPT-4、Gemini 等进行数据对话。
- 抓取被 BLS 阻止或限流时,使用仓库中已缓存的 `html/` 页面,跳过抓取并从 `process.py` 开始。
- 重新抓取 BLS 时,在请求之间加延迟以避免封锁。
- 只重新评分缺失职业:加载 `scores.json` 和 `occupations.json`,找出 `slug` 不在 existing 中的职业,打印缺失数量,然后用 `score.py` 按缺失 slug 过滤运行。
- 手动解析单个职业页:`from parse_detail import parse_occupation_page`、`from pathlib import Path`,读取 `html/software-developers.html`,调用 `parse_occupation_page(html)`,打印如 `median_pay`、`job_count`、`growth_outlook`。
- 用 pandas 加载并查询 `occupations.csv`:`df = pd.read_csv('occupations.csv')`;用 `df.nlargest(10, 'median_pay')[['title', 'median_pay', 'growth_outlook']]` 找最高薪职业。
- 用 pandas 过滤 `occupations.csv`:筛选 `growth_percent > 10` 且 `median_pay > 80000`,按 `median_pay` 降序排序。
- 合并 CSV 与 AI 分数分析:读取 `occupations.csv` 和 `scores.json`,用 slug 映射 `ai_score` 与 `ai_rationale`,过滤 `ai_score >= 8` 且 `median_pay > 100000`,输出 `title`、`median_pay`、`ai_score`、`growth_outlook`。
- 排查 `playwright install` 失败:运行 `uv run playwright install --with-deps chromium`。
- 排查 BLS 抓取被阻止或返回空页:确保 `scrape.py` 中 `headless=False`,添加手动延迟,不要在 CI 运行,或直接使用缓存 `html/`。
- 排查 `score.py` 的 OpenRouter 错误:确认 `.env` 中设置了 `OPENROUTER_API_KEY`,检查 OpenRouter 账户有额度,默认模型是 Gemini Flash,可在 `score.py` 中修改 `model`。
- 排查 `site/data.json` 重新评分后不更新:总是运行 `uv run python build_site_data.py` 重建。
- 排查树状图空白或无数据:确认 `site/data.json` 存在且是有效 JSON;用 `python -m http.server` 服务,不要用 `file://`(CORS 阻止本地 JSON fetch);检查浏览器 console 的 fetch 错误。
适用边界 · 注意事项
- 不适用于把 AI 暴露分数当作严谨经济预测或岗位消失结论:分数是 Gemini Flash via OpenRouter 的粗略 LLM 估计,且不考虑需求弹性、潜在需求、监管壁垒或社会对人工的偏好。
- 如果没有 OpenRouter API key 或额度,不要运行需要 LLM 评分的 `score.py`;只能使用已有 `scores.json` 或跳过评分层。
- 不适用于在 CI/无头环境或高强度频繁抓取 BLS:BLS 阻止 bot,`scrape.py` 需要 `headless=False` 并在请求间加延迟;可直接使用仓库缓存 `html/`。