Hacker-job API 参考手册:数据结构和接口调用完全指南
Hacker-job API 参考手册:数据结构和接口调用完全指南
【免费下载链接】hacker-jobPlay with hackernews' "who is hiring"项目地址: https://gitcode.com/gh_mirrors/ha/hacker-job
Hacker-job 是一个强大的招聘数据分析平台,专门用于解析和结构化 Hacker News "Who is Hiring?" 帖子的数据。本指南将详细介绍 Hacker-job 的数据结构和 API 接口,帮助开发者和数据分析师充分利用这个宝贵的技术招聘数据集。无论你是想构建自己的招聘分析工具,还是需要访问结构化的工作数据,这个完整的 API 参考手册都将为你提供所需的一切信息。
📊 项目概述与数据架构
Hacker-job 项目通过 AI 解析 Hacker News 的招聘帖子,将自由文本转换为结构化数据。整个系统采用无数据库架构,数据直接存储在 JSON 文件中,前端应用通过简单的 HTTP 请求访问这些数据。
核心数据文件结构
项目的数据存储在data/目录中,采用以下组织方式:
data/ ├── jobs/ # 按月存储的工作数据 │ ├── 2026-07.json # 2026年7月的招聘数据 │ ├── 2026-06.json # 2026年6月的招聘数据 │ └── index.json # 所有可用月份的清单 ├── trends.json # 薪资和关键词趋势数据 └── hackers.json # GitHub 赞助者信息每个月的 JSON 文件采用 JSONL 格式(每行一个 JSON 对象),这种格式既便于流式处理,又保持了良好的可读性。
🔧 API 接口调用指南
基础数据访问接口
Hacker-job 的前端通过简单的 HTTP GET 请求访问数据,所有接口都返回 JSON 格式的响应。
1. 获取月份清单 API
// 前端实现 export function getManifest(): Promise<JobsManifest> { return fetch(base + 'data/jobs/index.json').then((r) => r.json()) }接口地址:/data/jobs/index.json
响应示例:
{ "months": ["2026-07", "2026-06", "2026-05"], "count": 87183, "generated": "2026-07-03T12:34:56Z" }字段说明:
months:可用的月份列表(按从新到旧排序)count:工作职位总数generated:数据生成时间戳
2. 获取单月工作数据 API
// 前端实现 export async function getMonth(month: string): Promise<Job[]> { const text = await fetch(base + 'data/jobs/' + month + '.json').then((r) => r.text()) return text.split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l) as Job) }接口地址:/data/jobs/{month}.json(例如:/data/jobs/2026-07.json)
数据格式:JSONL(每行一个完整的 JSON 对象)
3. 获取趋势数据 API
// 前端实现 export function getTrends(): Promise<Trends> { return fetch(base + 'data/trends.json').then((r) => r.json()) }接口地址:/data/trends.json
4. 获取赞助者数据 API
// 前端实现 export function getHackers(): Promise<Hacker[]> { return fetch(base + 'data/hackers.json').then((r) => r.json()) }接口地址:/data/hackers.json
📋 完整数据结构详解
Job 工作职位数据结构
工作职位数据结构是 Hacker-job 的核心,包含了从 Hacker News 帖子中提取的所有关键信息:
interface Job { id: number; // Hacker News 帖子 ID author: string | null; // 发帖者用户名 ts: number; // 创建时间戳(Unix 秒) company: string; // 公司名称 roles: string[]; // 职位角色数组 location: string | null; // 工作地点 remote_type: string | null; // 远程类型:onsite/remote/hybrid remote_regions: string[]; // 远程工作允许的地区 salary_min: number | null; // 最低薪资 salary_max: number | null; // 最高薪资 salary_currency: string | null; // 薪资货币(USD/EUR/GBP 等) tech_stack: string[]; // 技术栈标签数组 job_type: string | null; // 工作类型:full-time/contract/intern visa: number | null; // 签证支持:1=支持,0=不支持,null=未提及 text: string; // 原始帖子文本 }数据示例分析
让我们看一个实际的数据示例:
{ "id": 48775598, "author": "maddie-confido", "ts": 1783089373, "company": "Confido", "roles": ["Forward Deployed Engineer"], "location": "NYC, Chelsea", "remote_type": "onsite", "remote_regions": [], "salary_min": 200000, "salary_max": 270000, "salary_currency": "USD", "tech_stack": ["Rails", "Python", "TypeScript", "Postgres", "AWS"], "job_type": "full-time", "visa": null, "text": "Confido | Forward Deployed Engineer | $200-270k BASE | ON-SITE (NYC, Chelsea) | Full-time\n\nWe manage over $3B for CPG brands like OLIPOP, Tropicana, and Dr. Squatch..." }关键字段解析:
id: 48775598 - 对应的 Hacker News 帖子 IDts: 1783089373 - 对应 2026年7月的发布时间salary_min/salary_max: 200000-270000 USD - 年薪范围tech_stack: 包含 Rails、Python 等技术标签remote_type: "onsite" - 现场办公职位
Trends 趋势数据结构
趋势数据提供了宏观的市场洞察:
interface Trends { meta: { jobs: number; // 总工作职位数 months: number; // 月份总数 from: string; // 起始月份(YYYY-MM) to: string; // 结束月份(YYYY-MM) }; volume: TrendPoint[]; // 每月职位数量趋势 salary: TrendPoint[]; // 平均薪资趋势 keywords: KeywordSeries[]; // 关键词趋势系列 } interface TrendPoint { x: string; // 月份(YYYY-MM) y: number; // 数值 } interface KeywordSeries { key: string; // 关键词标识 label: string; // 显示标签 default: boolean; // 是否默认显示 data: TrendPoint[]; // 趋势数据点 }Hacker 赞助者数据结构
赞助者信息来自 GitHub 赞助者:
interface Hacker { login: string; // GitHub 用户名 name?: string; // 显示名称 avatar?: string; // 头像 URL url?: string; // GitHub 主页 bio?: string; // 个人简介 location?: string; // 地理位置 blog?: string; // 个人博客 twitter?: string; // Twitter 账号 readme?: string; // 详细的个人介绍 }🚀 实战使用指南
1. 快速获取最新招聘数据
要获取最新的招聘数据,你可以按照以下步骤:
// 获取可用月份列表 const manifest = await fetch('/data/jobs/index.json').then(r => r.json()); const latestMonth = manifest.months[0]; // 最新的月份 // 获取该月所有工作职位 const jobsText = await fetch(`/data/jobs/${latestMonth}.json`).then(r => r.text()); const jobs = jobsText.split('\n') .filter(line => line.trim()) .map(line => JSON.parse(line)); console.log(`获取到 ${jobs.length} 个工作职位`);2. 构建自定义筛选器
利用 Hacker-job 的结构化数据,你可以轻松构建各种筛选功能:
// 筛选远程工作 const remoteJobs = jobs.filter(job => job.remote_type === 'remote'); // 筛选特定技术栈 const pythonJobs = jobs.filter(job => job.tech_stack.some(tech => tech.toLowerCase().includes('python') ) ); // 筛选高薪职位 const highSalaryJobs = jobs.filter(job => job.salary_min && job.salary_min >= 150000 ); // 筛选支持签证的职位 const visaJobs = jobs.filter(job => job.visa === 1);3. 分析技术趋势
通过分析tech_stack字段,你可以了解技术栈的流行趋势:
// 统计技术栈出现频率 const techFrequency = {}; jobs.forEach(job => { job.tech_stack.forEach(tech => { techFrequency[tech] = (techFrequency[tech] || 0) + 1; }); }); // 获取最热门的技术栈 const popularTechs = Object.entries(techFrequency) .sort((a, b) => b[1] - a[1]) .slice(0, 10); console.log('最热门技术栈:', popularTechs);4. 薪资数据分析
薪资数据提供了重要的市场参考:
// 计算平均薪资 const jobsWithSalary = jobs.filter(job => job.salary_min && job.salary_max); const avgMinSalary = jobsWithSalary.reduce((sum, job) => sum + job.salary_min, 0) / jobsWithSalary.length; const avgMaxSalary = jobsWithSalary.reduce((sum, job) => sum + job.salary_max, 0) / jobsWithSalary.length; // 按货币分类统计 const salaryByCurrency = {}; jobsWithSalary.forEach(job => { const currency = job.salary_currency || 'USD'; if (!salaryByCurrency[currency]) { salaryByCurrency[currency] = []; } salaryByCurrency[currency].push({ min: job.salary_min, max: job.salary_max, company: job.company }); });🔍 高级查询技巧
组合筛选查询
// 高级组合筛选:远程、高薪、特定技术栈 const advancedFilter = (jobs, options) => { return jobs.filter(job => { // 远程类型筛选 if (options.remoteType && job.remote_type !== options.remoteType) return false; // 薪资范围筛选 if (options.minSalary && (!job.salary_max || job.salary_max < options.minSalary)) return false; // 技术栈筛选 if (options.techStack && options.techStack.length > 0) { const hasTech = options.techStack.some(tech => job.tech_stack.some(jobTech => jobTech.toLowerCase().includes(tech.toLowerCase()) ) ); if (!hasTech) return false; } // 工作类型筛选 if (options.jobType && job.job_type !== options.jobType) return false; // 地点筛选 if (options.location && (!job.location || !job.location.toLowerCase().includes(options.location.toLowerCase()))) { return false; } return true; }); }; // 使用示例 const filteredJobs = advancedFilter(jobs, { remoteType: 'remote', minSalary: 100000, techStack: ['react', 'typescript'], jobType: 'full-time', location: 'europe' });时间序列分析
// 按月分析职位数量变化 const jobsByMonth = {}; jobs.forEach(job => { const date = new Date(job.ts * 1000); const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; if (!jobsByMonth[monthKey]) { jobsByMonth[monthKey] = []; } jobsByMonth[monthKey].push(job); }); // 计算每月统计 const monthlyStats = Object.entries(jobsByMonth).map(([month, monthJobs]) => { const salaries = monthJobs.filter(j => j.salary_min && j.salary_max); const avgSalary = salaries.length > 0 ? salaries.reduce((sum, j) => sum + (j.salary_min + j.salary_max) / 2, 0) / salaries.length : null; return { month, count: monthJobs.length, remoteCount: monthJobs.filter(j => j.remote_type === 'remote').length, avgSalary, topTechs: getTopTechnologies(monthJobs) }; });🛠️ 数据质量与验证
数据验证规则
Hacker-job 的数据经过 AI 解析,但可能存在解析错误。以下是重要的数据验证点:
- 薪资数据验证:检查
salary_min和salary_max的逻辑关系 - 远程类型验证:确保
remote_type为有效值(onsite/remote/hybrid) - 时间戳验证:确认
ts在合理的时间范围内 - 必填字段验证:
company和roles不应为空
数据修正机制
项目提供了数据问题报告机制:
function createIssueUrl(job) { const title = `Data issue: ${job.company} (job #${job.id})`; const body = `The extracted info for this job looks wrong (fields are AI-extracted).\n\n` + `Job: ${job.company}\nHacker News: https://news.ycombinator.com/item?id=${job.id}\n\n` + 'What is incorrect:\n- '; return `https://github.com/hacker-job/hacker-job/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`; }📈 趋势数据应用
使用趋势数据 API
// 获取趋势数据 const trends = await fetch('/data/trends.json').then(r => r.json()); // 分析职位数量趋势 console.log(`数据范围:${trends.meta.from} 到 ${trends.meta.to}`); console.log(`总职位数:${trends.meta.jobs}`); console.log(`覆盖月份:${trends.meta.months}`); // 绘制趋势图表 const volumeData = trends.volume; // 职位数量趋势 const salaryData = trends.salary; // 平均薪资趋势 const keywordSeries = trends.keywords; // 关键词趋势系列 // 找出最热门的关键词 const popularKeywords = trends.keywords .filter(series => series.default) .map(series => ({ keyword: series.label, latestValue: series.data[series.data.length - 1]?.y || 0 })) .sort((a, b) => b.latestValue - a.latestValue);自定义趋势分析
// 自定义技术趋势分析 function analyzeTechTrends(jobs, techKeywords) { const monthlyTechCount = {}; jobs.forEach(job => { const date = new Date(job.ts * 1000); const month = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; if (!monthlyTechCount[month]) { monthlyTechCount[month] = {}; techKeywords.forEach(keyword => { monthlyTechCount[month][keyword] = 0; }); } // 检查每个关键词 techKeywords.forEach(keyword => { if (job.tech_stack.some(tech => tech.toLowerCase().includes(keyword.toLowerCase()) )) { monthlyTechCount[month][keyword]++; } }); }); // 转换为趋势数据格式 return techKeywords.map(keyword => ({ key: keyword, label: keyword.charAt(0).toUpperCase() + keyword.slice(1), data: Object.entries(monthlyTechCount) .map(([month, counts]) => ({ x: month, y: counts[keyword] })) .sort((a, b) => a.x.localeCompare(b.x)) })); }🔗 集成与扩展
构建自定义 API 服务
你可以基于 Hacker-job 的数据构建自己的 API 服务:
// 简单的 Express.js API 服务示例 const express = require('express'); const app = express(); // 获取所有工作职位 app.get('/api/jobs', async (req, res) => { const { month, company, tech, remote, minSalary } = req.query; // 这里可以添加缓存逻辑 const jobs = await getAllJobs(); // 从 Hacker-job 数据源获取 let filteredJobs = jobs; // 应用筛选条件 if (month) { filteredJobs = filteredJobs.filter(job => new Date(job.ts * 1000).toISOString().startsWith(month) ); } if (company) { filteredJobs = filteredJobs.filter(job => job.company.toLowerCase().includes(company.toLowerCase()) ); } // 更多筛选逻辑... res.json({ count: filteredJobs.length, jobs: filteredJobs.slice(0, 100) // 分页 }); }); // 获取统计数据 app.get('/api/stats', async (req, res) => { const jobs = await getAllJobs(); const stats = { totalJobs: jobs.length, remoteJobs: jobs.filter(j => j.remote_type === 'remote').length, hybridJobs: jobs.filter(j => j.remote_type === 'hybrid').length, onsiteJobs: jobs.filter(j => j.remote_type === 'onsite').length, avgSalary: calculateAverageSalary(jobs), topCompanies: getTopCompanies(jobs), popularTechs: getPopularTechnologies(jobs) }; res.json(stats); });数据导出与备份
// 导出数据到 CSV function exportJobsToCSV(jobs) { const headers = [ 'ID', 'Company', 'Roles', 'Location', 'Remote Type', 'Salary Min', 'Salary Max', 'Currency', 'Tech Stack', 'Job Type', 'Visa Support', 'Date', 'HN Link' ]; const rows = jobs.map(job => [ job.id, `"${job.company}"`, `"${job.roles.join(', ')}"`, job.location ? `"${job.location}"` : '', job.remote_type || '', job.salary_min || '', job.salary_max || '', job.salary_currency || '', `"${job.tech_stack.join(', ')}"`, job.job_type || '', job.visa === 1 ? 'Yes' : job.visa === 0 ? 'No' : 'Not Mentioned', new Date(job.ts * 1000).toISOString().split('T')[0], `https://news.ycombinator.com/item?id=${job.id}` ]); return [headers, ...rows].map(row => row.join(',')).join('\n'); }💡 最佳实践建议
1. 数据缓存策略
由于数据文件相对稳定,建议实现适当的缓存机制:
// 简单的浏览器缓存实现 class JobDataCache { constructor() { this.cache = new Map(); this.cacheDuration = 24 * 60 * 60 * 1000; // 24小时 } async getMonthData(month) { const cacheKey = `month-${month}`; const cached = this.cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.cacheDuration) { return cached.data; } // 从服务器获取数据 const data = await fetch(`/data/jobs/${month}.json`).then(r => r.text()); const jobs = data.split('\n').filter(l => l.trim()).map(l => JSON.parse(l)); // 更新缓存 this.cache.set(cacheKey, { timestamp: Date.now(), data: jobs }); return jobs; } }2. 错误处理与重试
async function fetchWithRetry(url, retries = 3) { for (let i = 0; i < retries; i++) { try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.text(); } catch (error) { if (i === retries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))); // 指数退避 } } }3. 性能优化技巧
// 使用 Web Worker 处理大量数据 function processJobsInWorker(jobs) { return new Promise((resolve, reject) => { const worker = new Worker('job-processor.js'); worker.postMessage({ jobs }); worker.onmessage = (event) => { resolve(event.data); worker.terminate(); }; worker.onerror = (error) => { reject(error); worker.terminate(); }; }); } // 分批加载数据 async function loadJobsInBatches(months, batchSize = 3) { const results = []; for (let i = 0; i < months.length; i += batchSize) { const batch = months.slice(i, i + batchSize); const batchPromises = batch.map(month => getMonth(month)); const batchResults = await Promise.all(batchPromises); results.push(...batchResults.flat()); // 可选:更新进度或触发渲染 updateProgress(i + batch.length, months.length); } return results; }🎯 总结与下一步
Hacker-job 提供了丰富而结构化的技术招聘数据,通过简单的 HTTP 接口即可访问。无论是构建招聘分析工具、研究技术趋势,还是开发个性化的职位搜索平台,这个数据集都是宝贵的资源。
关键要点:
- 📁 数据存储在
data/目录的 JSON 文件中 - 🔄 采用 JSONL 格式,便于流式处理
- 🏗️ 完整的数据结构定义在
scripts/types.ts - 🌐 通过简单的 HTTP GET 请求访问数据
- 🔍 支持多种筛选和查询方式
- 📈 包含趋势分析和统计数据
下一步建议:
- 从获取月份清单开始(
/data/jobs/index.json) - 根据需要加载特定月份的数据
- 利用结构化字段进行精确筛选
- 结合趋势数据进行市场分析
- 构建自定义的可视化或分析工具
通过本指南,你应该能够充分利用 Hacker-job 的数据接口,构建强大的招聘数据分析应用。记得查看项目中的frontend/src/data.ts文件获取最新的接口实现,并根据实际需求调整数据加载和处理策略。
Happy coding! 🚀
【免费下载链接】hacker-jobPlay with hackernews' "who is hiring"项目地址: https://gitcode.com/gh_mirrors/ha/hacker-job
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考