Job Hunting(职位猎人) - 一款协助找工作的浏览器插件
为什么要做这个项目
当前国内使用率较高的招聘平台(排名不分先后)分别有 BOOS 直聘,前程无忧,智联招聘,猎聘网,拉勾网,其提供了各个行业的职位招聘信息的展示。但在实际使用过程中发现其展示职位信息的策略对于求职者有诸多不便,包括不仅限于:职位发布时间久远(俗称僵尸岗),不能简单识别普通职位,职位发布时间被隐藏或乱序显示,职位的公司名不是全称,没有职位公司的风险提示。
项目做了什么
为了提高使用这些招聘平台找工作的用户体验,项目会对目标平台网站页面进行增强展示;对出现过的职位进行永久存储,以便进行全网职位的个性化快速搜索,职位数据的共享;对职位数据进行多维度的分析,并以可视化的手段呈现;通过内置的讨论区,对职位进行评论,为职位打上标签等方式进行中立的职位交流;
运行截图
招聘/企业信息网站页面
搜索页(前程无忧)
推荐页(BOSS 直聘)
详情页
职位快照
爱企查
管理页面
打开管理页面
管理页面(需点击插件图标打开)
最近主要改动/新增特性
- 新增内置大模型引擎web-llm
招聘平台支持列表
| 招聘平台 | 访问地址 | 备注 |
|---|---|---|
| BOSS 直聘 | https://www.zhipin.com/web/geek/jobs | 推荐页/搜索页[账号未登录] |
| https://www.zhipin.com/web/geek/jobs | 推荐页/搜索页[账号已登录] | |
| 前程无忧 | https://we.51job.com/pc/search | 搜索页 |
| 智联招聘 | https://sou.zhaopin.com/ | 搜索页 |
| 拉钩网 | https://www.lagou.com/wn/zhaopin | 搜索页 |
| 猎聘网 | https://www.liepin.com/zhaopin | 搜索页,需点击搜索按钮才有效果 |
| 就业在线 | https://www.jobonline.cn/position | 搜索页 |
| 广东公共求职招聘服务平台 | https://ggfw.hrss.gd.gov.cn/recruitment/internet/main/#/search?type=1 | 搜索页 |
企业搜索平台支持列表
| 企业搜索平台 | 访问地址 | 备注 |
|---|---|---|
| 爱企查 | https://aiqicha.baidu.com/s |
浏览器支持
教程
快速开始
职位猎人插件
- 打开 Release 页 或 直接访问 最新发布
- 点击下载 Assets 下的 job-hunting-extension-chrome-xxx.zip
- 打开浏览器,安装插件,下面是针对不同浏览器的安装步骤
- chrome:地址栏输入 chrome://extensions/,打开开发者模式,将 zip 文件拖进页面里
- edge,地址栏输入 edge://extensions/,打开开发人员模式,将 zip 文件拖进页面里
- 打开页面
- boss 直聘: https://www.zhipin.com/web/geek/jobs
- 51Job: https://we.51job.com/pc/search
- 智联招聘: https://sou.zhaopin.com/
- 拉钩网:https://www.lagou.com/wn/zhaopin
- 猎聘网: https://www.liepin.com/zhaopin
- 就业在线: https://www.jobonline.cn/position
- 广东公共求职招聘服务平台 https://ggfw.hrss.gd.gov.cn/recruitment/internet/main/#/search?type=1
开发
浏览器插件
Important
项目根目录: apps/extension
编译
- 安装,编译
pnpm i
pnpm run build
-
打开 chrome,选择加载已解压的扩展程序,选择当前项目的 .output/chrome-mv3 目录
-
打开页面
- boss 直聘: https://www.zhipin.com/web/geek/jobs
- 51Job: https://we.51job.com/pc/search
- 智联招聘: https://sou.zhaopin.com/
- 拉钩网:https://www.lagou.com/wn/zhaopin
- 猎聘网: https://www.liepin.com/zhaopin
- 就业在线: https://www.jobonline.cn/position
- 广东公共求职招聘服务平台 https://ggfw.hrss.gd.gov.cn/recruitment/internet/main/#/search?type=1
开发
-
安装,编译
pnpm i pnpm run dev -
chrome 浏览器打开 chrome://extensions/ 页面
-
点击
加载已解压的扩展程序 -
选择项目中生成的 .output/chrome-mv3-dev 文件夹即可
-
每次保存都会重新编译,扩展程序需要**重新点一次刷新按钮**才生效
测试
职位分析组件
Important
项目根目录: libs/analysis
编译
pnpm exec nx run analysis:build
文档
pnpm exec nx run analysis:storybook
数据源
数据源仓库说明
- 仓库目录结构
├── 2025 //年份,格式YYYY
│ └── 07-01 //月日,格式MM-DD
│ └── 深圳避雷公司.zip //数据文件,格式xxx.zip(包含文件xxx.xlsx)备注:zip文件和xlsx文件的文件名必须相同
├── metadata.json //源数据,包含数据源元数据,可用的数据源列表
- metadata.json 文件格式
{
"data": {
"source": [
{
"name": "" //数据源名称
"config": {
"taskTypeList": [
{
"name": "", //名称
"type": "COMPANY_COMMENT_DATA_DOWNLOAD", //固定值,代表公司评论数据类型
"emotion": "NEGATIVE", //情感导向,负面:NEGATIVE,积极:POSITIVE,正常:其他值
"fileName": "深圳避雷公司", //数据文件名
"description": "", //描述
"retentionDay": 3650 //数据保留时间,单位:天
}
]
},
"repoType": "GITHUB", //固定值
"reponame": "", //仓库名
"username": "", //仓库用户名
"description": "" //数据源描述
}
]
},
"icon":"", //元数据图标,格式为svg,注意转移字符
"name": "", //元数据名称
"type": "GIT_METADATA", //固定值,GIT元数据类型
"config": {
"config": {
"url": "", //元数据git仓库地址,以http或https开头的地址
"filePath": "metadata.json" //元数据文件名,一般取metadata.json
}
},
"enable": true, //是否开启
"description": "", //元数据描述
"autoUpdateEnable": true //是否自动更新元数据
}
-
公司评论数据文件格式
公司 评论 xxxx yyyy 备注:
- 第一行的标题必须保留,插件会以标题来标识字段列的数据
- 公司:可以同时填写多家公司,换行隔开
- 公司或评论为空的行将被跳过
-
Q&A
-
如何更新数据? 根据仓库目录结构,新建年月日目录,将整理后的数据文件zip 文件上传到新建的年月日目录里即可
-
如何快速测试文件格式是否正确 打开插件后台页面 ->系统 ->数据管理 ->公司评论数据导入
如果能正常导入 xlsx 文件,则代表该文件正常(当前不支持导入 zip 文件)
-
如何使用?
-
新增元数据:打开插件后台页面 ->数据源 ->元数据 ->从网络导入
1.从网络导入,填入 metadata.json 的访问地址: https://github.com/用户名/仓库名/raw/refs/heads/main/metadata.json
-
添加数据源: 打开插件后台页面 ->数据源 ->列表 ->搜寻数据源 ->选中新增的元数据 ->选中并添加数据源
-
-
数据同步的逻辑是怎样的? 插件会遍历整个仓库的年月日目录,根据 metadata.json 的规则进行数据的增量同步,公司评论信息采取 散列(公司名+评论)作为唯一标识进行去重逻辑。
-
Commit Message
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
提交说明包含了下面的结构化元素,以向类库使用者表明其意图:
fix: 类型 为 fix 的提交表示在代码库中修复了一个 bug(这和语义化版本中的 PATCH 相对应)。
feat: 类型 为 feat 的提交表示在代码库中新增了一个功能(这和语义化版本中的 MINOR 相对应)。
BREAKING CHANGE: 在脚注中包含 BREAKING CHANGE: 或 <类型>(范围) 后面有一个 ! 的提交,表示引入了破坏性 API 变更(这和语义化版本中的 MAJOR 相对应)。 破坏性变更可以是任意 类型 提交的一部分。
除 fix: 和 feat: 之外,也可以使用其它提交 类型 ,例如 @commitlint/config-conventional(基于 Angular 约定)中推荐的 build:、chore:、 ci:、docs:、style:、refactor:、perf:、test:,等等。
build: 用于修改项目构建系统,例如修改依赖库、外部接口或者升级 Node 版本等;
chore: 用于对非业务性代码进行修改,例如修改构建流程或者工具配置等;
ci: 用于修改持续集成流程,例如修改 Travis、Jenkins 等工作流配置;
docs: 用于修改文档,例如修改 README 文件、API 文档等;
style: 用于修改代码的样式,例如调整缩进、空格、空行等;
refactor: 用于重构代码,例如修改代码结构、变量名、函数名等但不修改功能逻辑;
perf: 用于优化性能,例如提升代码的性能、减少内存占用等;
test: 用于修改测试用例,例如添加、删除、修改代码的测试用例等。
脚注中除了 BREAKING CHANGE: <description> ,其它条目应该采用类似 git trailer format 这样的惯例。
其它提交类型在约定式提交规范中并没有强制限制,并且在语义化版本中没有隐式影响(除非它们包含 BREAKING CHANGE)。 可以为提交类型添加一个围在圆括号内的范围,以为其提供额外的上下文信息。例如 feat(parser): adds ability to parse arrays.。
特别地:
bump: v1.1.0,代表发布版本
Version
版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
主版本号:当你做了不兼容的 API 修改,
次版本号:当你做了向下兼容的功能性新增,
修订号:当你做了向下兼容的问题修正。
先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
Reference
- https://keepachangelog.com/zh-CN/1.1.0/
- https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format
- https://www.conventionalcommits.org/zh-hans/v1.0.0/
- https://semver.org/lang/zh-CN/
auto_explain
db = new PGlite(`opfs-ahp://${JOB_DB_PATH}`, {
extensions: { auto_explain },
debug:1,
});
await db.exec(`
LOAD 'auto_explain';
SET auto_explain.log_min_duration = '0';
SET auto_explain.log_analyze = 'true';
`);
打开offscreen的console,可看到详细的分析日志
Transaction
Chrome Extension下的持久层事务问题(Transaction)
- 当前采用PGLite,其事务的调用采用异步调用下的在回调函数里进行,如
await pg.transaction(async (tx) => {
await tx.query(
'INSERT INTO test (name) VALUES ('$1');',
[ 'test' ]
);
return await tx.query('SELECT * FROM test;');
});
- Chrome Extension的调用链为
ContentScript(或SidePanel) -> Background -> OffScreen -> WebWorker
而持久层(即PGLite)是在WebWorker进行调用,而运行逻辑部份是放在Background或ContentScript(或SidePanel)里,这样会出现一个问题,怎样使得逻辑的调用链处在事务的回调函数里?
可能的解决方案
- 传递tx对象
伪代码:
let idAndTxMap = new Map();
let idAndTxPromiseResolveReject = new Map()
async function startTransaction(){
await pg.transaction(async (tx) => {
let id = genId();
idAndTxMap.set(id,tx);
postMessage(message,id);
return new Promise((resolve,reject)=>{
idAndTxPromiseResolveReject.set(id,{resolve,reject});
});
});
}
async function endTransaction(id){
idAndTxPromiseResolveReject.get(id).resolve();
}
async function update(param,{transactionId=null}={}){
let connection = idAndTxMap.get(transactionId)??await getDb();
connection.query("update....",param);
}
async main(){
let id = await startTransaction();
update(param,{transactionId:id})
await endTransaction(id);
}
- 将逻辑都放到WebWorker里
选择的解决方案
- 选择将逻辑都放到WebWorker里。
Dump
数据库备份
https://github.com/electric-sql/pglite/tree/main/packages/pglite-tools
这里有pgDump的工具,但整合后报错(pg_dump failed with exit code 1),不能正常导出
- 导出
package.json
"@electric-sql/pglite-tools": "^0.2.2",
wxt.config.ts
copyFileSync(resolve(srcDir, "node_modules", "@electric-sql", "pglite-tools", "dist", "pg_dump.wasm"), resolve(outDir, "assets", "pg_dump.wasm"));
database.js
import { PGlite } from '@electric-sql/pglite'
import { pgDump } from '@electric-sql/pglite-tools/pg_dump'
const pg = await PGlite.create()
// Create a table and insert some data
await pg.exec(`
CREATE TABLE test (
id SERIAL PRIMARY KEY,
name TEXT
);
`)
await pg.exec(`
INSERT INTO test (name) VALUES ('test');
`)
// Dump the database to a file
const dump = await pgDump({ pg })
- 导入
Data Share Plan 数据共享计划
文档版本:2024-10-10
背景
招聘网站展示给应聘者的岗位存在某些问题
- 职位并不是最新的,有可能是挂职几个月的,一页展示的只有大概三分之一是最近的
- 部分职位因为使用某种手段,使其展示的优先级往上靠
- 某些招聘网站没有提供按职位发布时间进行排序的功能
- 招聘网站提供的职位搜索条件较少,一般只通过职位关键字来进行搜索
基于上述的原因,实现数据共享计划并结合 JobHutting 内置的职位偏好功能是部分痛点的解决方案
数据字段
职位数据字段
职位自编号
发布平台
职位访问地址
职位
公司
公司是否为全称
地区
地址
经度
纬度
职位描述
学历
所需经验
最低薪资
最高薪资
首次发布时间
招聘人
招聘公司
招聘者职位
首次扫描日期
记录更新日期
公司数据字段
公司
公司描述
成立时间
经营状态
法人
统一社会信用代码
官网
社保人数
自身风险数
关联风险数
地址
经营范围
纳税人识别号
所属行业
工商注册号
经度
纬度
数据来源地址
数据来源平台
数据来源记录编号
数据来源更新时间
公司标签数据字段
公司
标签
数据流向
官方数据流(数据上传)
招聘网站 -> JobHunting Extension -> Git(个人GitHub仓库)
共享数据流(数据下载)
共享数据仓库列表 -> Git(个人GitHub仓库) -> JobHunting Extension
数据提交流程
最多每天提交一次,插件启动时开启定时检查任务
查询仓库最近一次提交时间
提交的记录的范围条件:记录更新时间 < 今天0点0分 和记录更新时间 >= 最近一次提交时间
备注:针对公司标签,现在是全量提交
提交的目录
提交时间(YYYY)
提交时间(MM-DD)
job.zip
company.zip
company_tag.zip
数据获取和同步流程
1. 查询仓库60天内的记录
2. 下载60天内缺失的数据文件
3. 根据数据文件进行数据同步
4. 针对不同类型数据进行处理
职位数据
如果是新数据
新增记录
如果是重复数据
根据创建时间来处理,并且需要处理公司名全称问题
公司数据
如果是新数据
新增记录
如果是重复数据
根据数据来源更新时间来处理
公司标签数据
如果是新数据
新增记录
如果是重复数据
合并标签
关键组件及行为
相关表
task 任务表
id 编号
type 任务类型
data_id 数据编号
status 任务状态
error_reason 异常原因
cost_time 最近一次任务执行耗时
retry_count 重试次数
create_datetime 创建时间
update_datetime 更新时间
task_data_upload 任务数据表(上传)
id 编号
type 任务类型
username 用户名
reponame 仓库名
start_datetime 数据开始时间
end_datetime 数据结束时间
data_count 数据总量
create_datetime 创建时间
update_datetime 更新时间
task_data_download 任务数据表(下载)
id 编号
type 任务类型
username 用户名
reponame 仓库名
datetime 文件日期
create_datetime 创建时间
update_datetime 更新时间
file 文件表
id 编号
name 文件名
sha 散列值
encoding 文件内容编码,
content 文件内容,
size 文件尺寸,
type 类型(当前只有file)
create_datetime 创建时间
update_datetime 更新时间
task_data_merge 任务数据表(数据合并)
id 编号
type 任务类型
username 用户名
reponame 仓库名
datetime 文件日期
data_id 文件编号
data_count 数据总量
create_datetime 创建时间
update_datetime 更新时间
data_share_partner 数据共享伙伴信息表
id 编号
username 用户名
reponame 仓库名
repo_type 仓库类型(当前只有GITHUB)
create_datetime 创建时间
update_datetime 更新时间
附表:
任务类型
职位数据上传:TASK_TYPE_JOB_DATA_UPLOAD
公司数据上传:TASK_TYPE_COMPANY_DATA_UPLOAD
公司标签数据上传:TASK_TYPE_COMPANY_TAG_DATA_UPLOAD
职位数据下载:TASK_TYPE_JOB_DATA_DOWNLOAD
公司数据下载:TASK_TYPE_COMPANY_DATA_DOWNLOAD
公司标签数据下载:TASK_TYPE_COMPANY_TAG_DATA_DOWNLOAD
职位数据合并:TASK_TYPE_JOB_DATA_MERGE
公司数据合并:TASK_TYPE_COMPANY_DATA_MERGE
公司标签数据合并:TASK_TYPE_COMPANY_TAG_DATA_MERGE
状态
准备:TASK_STATUS_READY
运行中:TASK_STATUS_RUNNING
完成:TASK_STATUS_FINISHED
异常完成:TASK_STATUS_FINISHED_BUT_ERROR
错误:TASK_STATUS_ERROR
取消:TASK_STATUS_CANCEL
数据存储目录结构
提交时间(YYYY)
提交时间(MM-DD)
job.zip
company.zip
company_tag.zip
GitHub
仓库的建立
所需权限:"Administration" repository permissions (write)
https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#create-a-repository-for-the-authenticated-user
/user/repos
仓库目录的提交&数据文件的提交
所需权限:"Contents" repository permissions (write)
https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#create-or-update-file-contents
/repos/{owner}/{repo}/contents/{path}
仓库目录的查询&仓库文件的下载
所需权限:无
特别事项:This API has an upper limit of 1,000 files for a directory. If you need to retrieve more files,
https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content
/repos/{owner}/{repo}/contents/{path}
FAQ
1. 报错 No more file handles available in the pool
如果在 Linux 下,请使用命令 ulimit -n 检查 soft file descriptor 的值,一般默认为 1024 或 2048,请设定一个较高的值如 9001
免责声明
1. 项目目的与性质
本项目(以下简称“本项目”)是作为一个技术研究与学习工具而创建的,旨在探索和学习网络数据采集技术。本项目专注于招聘平台的数据爬取与分析技术研究,旨在提供给学习者和研究者作为技术交流之用。
2. 法律合规性声明
本项目开发者(以下简称“开发者”)郑重提醒用户在下载、安装和使用本项目时,严格遵守中华人民共和国相关法律法规,包括但不限于《中华人民共和国网络安全法》、《中华人民共和国反间谍法》等所有适用的国家法律和政策。用户应自行承担一切因使用本项目而可能引起的法律责任。
3. 使用目的限制
本项目严禁用于任何非法目的或非学习、非研究的商业行为。本项目不得用于任何形式的非法侵入他人计算机系统,不得用于任何侵犯他人知识产权或其他合法权益的行为。用户应保证其使用本项目的目的纯属个人学习和技术研究,不得用于任何形式的非法活动。
4. 免责声明
开发者已尽最大努力确保本项目的正当性及安全性,但不对用户使用本项目可能引起的任何形式的直接或间接损失承担责任。包括但不限于由于使用本项目而导致的任何数据丢失、设备损坏、法律诉讼等。
5. 知识产权声明
本项目的知识产权归开发者所有。本项目受到著作权法和国际著作权条约以及其他知识产权法律和条约的保护。用户在遵守本声明及相关法律法规的前提下,可以下载和使用本项目。
6. 最终解释权
关于本项目的最终解释权归开发者所有。开发者保留随时更改或更新本免责声明的权利,恕不另行通知。

