104-sourcing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese104 HR 人才搜尋
104 HR Talent Search
概覽
Overview
本 Skill 使用 agent-browser CLI 控制瀏覽器,自動登入 104 招募管理平台並搜尋合適人選。執行流程分三個階段:需求訪談 → 登入 → 純 API 搜尋篩選。
依賴工具:本 Skill 需要CLI,安裝方式見 frontmatter 的agent-browser欄位。 指令參考:references/agent-browser.mdcompatibility
This Skill uses the agent-browser CLI to control the browser, automatically log in to the 104 Recruitment Management Platform and search for suitable candidates. The execution process is divided into three stages: Requirement Interview → Login → API-only Search & Filter.
Dependent Tool: This Skill requires theCLI. For installation instructions, refer to theagent-browserfield in the frontmatter. Command Reference: references/agent-browser.mdcompatibility
架構原則(重要)
Architecture Principles (Important)
- 登入用 browser,其餘全用 API:瀏覽器只用來建立 session cookie,搜尋/篩選/評估全部走 API,速度快 100 倍以上
- 所有 fetch 必須在主 session(104-sourcing)內呼叫:Subagent 的獨立 session 無法共用登入 cookie
- eval 只能用 ES5 語法:不能用
var,不能用 arrow function,否則 eval 會報錯const/let
- Use browser only for login, all other operations via API: The browser is only used to establish session cookies; search/filter/evaluation are all done via API, which is over 100 times faster
- All fetch requests must be called within the main session (104-sourcing): Independent sessions of Subagents cannot share login cookies
- eval only supports ES5 syntax: Use instead of
var, do not use arrow functions, otherwise eval will throw errorsconst/let
第一階段:需求訪談
Stage 1: Requirement Interview
在開啟瀏覽器前,必須先透過訪談了解招募需求,不得假設或跳過。訪談分兩層:
Before launching the browser, you must first understand the recruitment requirements through interviews, and you cannot make assumptions or skip this step. The interview is divided into two levels:
基本條件(硬性篩選用)
Basic Criteria (For Hard Filtering)
- 職位名稱:要搜尋什麼職務?(例:電銷、業務、工程師)
- 工作地點:候選人可接受的工作地點?(例:台北市、新北市板橋區)
- 硬性要求:有哪些必要條件?(例:工作年資、學歷、特定技能或證照)
- 待業狀況:是否只看特定就業狀態的求職者?(預設不限)
- 不限:
empStatus=0 - 在職中(想跳槽):
empStatus=1 - 待業中(即可上班):
empStatus=2
- 不限:
- Job Title: What position are you searching for? (e.g., Telemarketing, Sales, Engineer)
- Work Location: What work locations can candidates accept? (e.g., Taipei City, Banqiao District, New Taipei City)
- Hard Requirements: What are the necessary conditions? (e.g., work experience, education background, specific skills or certifications)
- Employment Status: Do you only want to view job seekers in specific employment statuses? (Default: No restriction)
- No restriction:
empStatus=0 - Employed (seeking job change):
empStatus=1 - Unemployed (available immediately):
empStatus=2
- No restriction:
質性條件(篩選後評估用)
Qualitative Criteria (For Post-Filter Evaluation)
- 理想候選人樣貌:這個職位最重視什麼特質或背景?有哪些過往經歷特別加分?
- 地雷:有哪些狀況要排除?(例:頻繁換工作、特定產業背景、工作空窗過長)
- 加分項目:其他 nice-to-have?(例:具備特定軟體操作經驗、語言能力)
訪談結束後,整理並回覆「搜尋策略確認」:
- 搜尋關鍵字:用於 API 的 參數
kws - 硬性篩選條件:JS 過濾邏輯的依據
- 質性評估標準:看完候選人資料後打分的依據
用戶確認後,再進入第二階段。
- Ideal Candidate Profile: What traits or backgrounds are most valued for this position? What past experiences are particularly preferred?
- Red Flags: What situations need to be excluded? (e.g., frequent job changes, specific industry backgrounds, long employment gaps)
- Bonus Items: Other nice-to-have conditions? (e.g., experience with specific software, language proficiency)
After the interview, organize and reply with a "Search Strategy Confirmation":
- Search Keywords: Used for the parameter in the API
kws - Hard Filter Criteria: Basis for JS filtering logic
- Qualitative Evaluation Standards: Basis for scoring after viewing candidate profiles
Proceed to Stage 2 only after user confirmation.
第二階段:啟動瀏覽器並確認登入狀態
Stage 2: Launch Browser and Verify Login Status
瀏覽器只需登入,不需要用 UI 操作搜尋或篩選條件。
The browser only needs to log in; there is no need to use the UI to perform search or set filter criteria.
步驟
Steps
-
直接開啟招募管理平台(帶現有 session,看是否已登入)bash
agent-browser --session-name 104-sourcing open https://vip.104.com.tw/rms/index -
取得目前狀態bash
agent-browser --session-name 104-sourcing snapshot -i解讀 snapshot 輸出,判斷目前頁面狀態: -
若已在 vip.104.com.tw(招募管理平台)→ 直接進入第三階段,不需要登入
-
若被導向登入頁 → 向用戶索取帳號密碼,注意兩種情況:
- 完整登入(snapshot 有 email + password 兩個輸入欄):輸入帳號與密碼
- 密碼重新驗證(snapshot 只有 password 輸入欄,無 email 欄):104 在 session 過期時有時只要求再輸入密碼,不需重新輸入 email
bashagent-browser --session-name 104-sourcing fill @eX "{用戶提供的 email}" # 僅完整登入時 agent-browser --session-name 104-sourcing fill @eX "{用戶提供的密碼}" agent-browser --session-name 104-sourcing click @eX # 登入按鈕 agent-browser --session-name 104-sourcing snapshot -i -
若出現 MFA(snapshot 中有 OTP 輸入)
- 向用戶索取 6 位數 Email 驗證碼
agent-browser --session-name 104-sourcing fill @eX "驗證碼"agent-browser --session-name 104-sourcing press Enter
-
確認已進入 vip.104.com.tw
- 若出現重複登入對話框:snapshot 找「將目前帳號登出」→ click
- 若出現廣告彈窗(screenshot 中看到「加強曝光」等促銷文字):snapshot 找關閉按鈕 → click
-
Directly open the Recruitment Management Platform (with existing session to check if already logged in)bash
agent-browser --session-name 104-sourcing open https://vip.104.com.tw/rms/index -
Get current statusbash
agent-browser --session-name 104-sourcing snapshot -iInterpret the snapshot output to determine the current page status: -
If already on vip.104.com.tw (Recruitment Management Platform) → directly proceed to Stage 3, no need to log in
-
If redirected to login page → request account and password from the user, note two scenarios:
- Full Login (snapshot shows both email and password input fields): Enter account and password
- Password Re-verification (snapshot shows only password input field, no email field): 104 sometimes only requires re-entering the password when the session expires, no need to re-enter the email
bashagent-browser --session-name 104-sourcing fill @eX "{user-provided email}" # Only for full login agent-browser --session-name 104-sourcing fill @eX "{user-provided password}" agent-browser --session-name 104-sourcing click @eX # Login button agent-browser --session-name 104-sourcing snapshot -i -
If MFA appears (snapshot shows OTP input field)
- Request 6-digit email verification code from the user
agent-browser --session-name 104-sourcing fill @eX "verification code"agent-browser --session-name 104-sourcing press Enter
-
Confirm access to vip.104.com.tw
- If duplicate login dialog appears: Find "Log out current account" in snapshot → click
- If ad popup appears (promotional text like "Enhance Exposure" in screenshot): Find close button in snapshot → click
第三階段:純 API 搜尋與篩選
Stage 3: API-only Search & Filter
登入完成後,全程用 eval + fetch API,不再操作瀏覽器 UI。
After login is completed, use eval + fetch API for all operations, no longer operate the browser UI.
已知 API 端點
Known API Endpoints
| 用途 | 端點 |
|---|---|
| 搜尋候選人列表(含工作經歷) | |
| 取得單一候選人完整履歷 | |
| 取得儲存資料夾列表(含 folderNo) | |
| 儲存候選人到資料夾(支援批次) | |
| Purpose | Endpoint |
|---|---|
| Search candidate list (including work experience) | |
| Get full resume of a single candidate | |
| Get saved folder list (including folderNo) | |
| Save candidates to folder (supports batch) | |
就業狀態代碼(empStatus 參數)
Employment Status Codes (empStatus Parameter)
| 值 | 說明 |
|---|---|
| 不限 |
| 在職中(想跳槽; |
| 待業中(已離職,即可上班; |
| Value | Description |
|---|---|
| No restriction |
| Employed (seeking job change; |
| Unemployed (already resigned, available immediately; |
城市代碼
City Codes
完整代碼清單見 references/area.json,支援台灣、大陸、海外地區。縣市層代碼末三位為 ,行政區層末三位為流水號。
000常用縣市快查:
| 地點 | city 參數值 |
|---|---|
| 台北市 | |
| 新北市 | |
| 基隆市 | |
| 桃園市 | |
| 新竹縣市 | |
| 台中市 | |
| 台南市 | |
| 高雄市 | |
Complete code list can be found in references/area.json, supporting Taiwan, mainland China, and overseas regions. County/city level codes end with three s, district level codes end with serial numbers.
0Common County/City Quick Reference:
| Location | city Parameter Value |
|---|---|
| Taipei City | |
| New Taipei City | |
| Keelung City | |
| Taoyuan City | |
| Hsinchu County/City | |
| Taichung City | |
| Tainan City | |
| Kaohsiung City | |
步驟 1:呼叫搜尋 API 第 1 頁,取得總頁數與 fixedUpdateDate
Step 1: Call Search API for Page 1, get total pages and fixedUpdateDate
bash
agent-browser --session-name 104-sourcing eval "
fetch('https://auth.vip.104.com.tw/api/search/searchResult?contactPrivacy=0&kws=%E9%9B%BB%E9%8A%B7%E4%BA%BA%E6%89%8D&city=6001001000&workExpTimeType=all&sex=2&empStatus={0|1|2}&updateDateType=1&sortType=RANK&readStatus=all&plastActionDateType=1&page=1&ec=105', {credentials:'include'})
.then(function(r){return r.json()})
.then(function(d){
window._fixedDate = d.result.fixedUpdateDate;
window._totalPages = d.result.pageInfo.total_page;
window._allCandidates = d.result.data;
window._p1done = true;
});
'fetching page 1...'
"等待後確認:
bash
sleep 3 && agent-browser --session-name 104-sourcing eval "JSON.stringify({done:window._p1done, fixedDate:window._fixedDate, totalPages:window._totalPages, count:window._allCandidates&&window._allCandidates.length})"重要:必須從第 1 頁回應取得,後續所有分頁都要帶這個值,確保結果一致性。fixedUpdateDate
bash
agent-browser --session-name 104-sourcing eval "
fetch('https://auth.vip.104.com.tw/api/search/searchResult?contactPrivacy=0&kws=%E9%9B%BB%E9%8A%B7%E4%BA%BA%E6%89%8D&city=6001001000&workExpTimeType=all&sex=2&empStatus={0|1|2}&updateDateType=1&sortType=RANK&readStatus=all&plastActionDateType=1&page=1&ec=105', {credentials:'include'})
.then(function(r){return r.json()})
.then(function(d){
window._fixedDate = d.result.fixedUpdateDate;
window._totalPages = d.result.pageInfo.total_page;
window._allCandidates = d.result.data;
window._p1done = true;
});
'fetching page 1...'
"Wait and confirm:
bash
sleep 3 && agent-browser --session-name 104-sourcing eval "JSON.stringify({done:window._p1done, fixedDate:window._fixedDate, totalPages:window._totalPages, count:window._allCandidates&&window._allCandidates.length})"Important:must be obtained from the response of Page 1, and this value must be included in all subsequent page requests to ensure result consistency.fixedUpdateDate
步驟 2:並發抓取所有剩餘頁
Step 2: Concurrent Fetch of All Remaining Pages
bash
agent-browser --session-name 104-sourcing eval "
var baseUrl = 'https://auth.vip.104.com.tw/api/search/searchResult?contactPrivacy=0&kws=%E9%9B%BB%E9%8A%B7%E4%BA%BA%E6%89%8D&city=6001001000&workExpTimeType=all&sex=2&empStatus={0|1|2}&updateDateType=1&sortType=RANK&readStatus=all&plastActionDateType=1&ec=105&fixed_update_date=';
var pages = [];
for(var i=2; i<=window._totalPages; i++) pages.push(i);
Promise.all(pages.map(function(p){
return fetch(baseUrl+window._fixedDate+'&page='+p, {credentials:'include'})
.then(function(r){return r.json()})
.then(function(d){ window._allCandidates = window._allCandidates.concat(d.result.data); });
})).then(function(){ window._allDone = true; });
'fetching remaining pages...'
"等待後確認:
bash
sleep 8 && agent-browser --session-name 104-sourcing eval "JSON.stringify({done:window._allDone, total:window._allCandidates.length})"bash
agent-browser --session-name 104-sourcing eval "
var baseUrl = 'https://auth.vip.104.com.tw/api/search/searchResult?contactPrivacy=0&kws=%E9%9B%BB%E9%8A%B7%E4%BA%BA%E6%89%8D&city=6001001000&workExpTimeType=all&sex=2&empStatus={0|1|2}&updateDateType=1&sortType=RANK&readStatus=all&plastActionDateType=1&ec=105&fixed_update_date=';
var pages = [];
for(var i=2; i<=window._totalPages; i++) pages.push(i);
Promise.all(pages.map(function(p){
return fetch(baseUrl+window._fixedDate+'&page='+p, {credentials:'include'})
.then(function(r){return r.json()})
.then(function(d){ window._allCandidates = window._allCandidates.concat(d.result.data); });
})).then(function(){ window._allDone = true; });
'fetching remaining pages...'
"Wait and confirm:
bash
sleep 8 && agent-browser --session-name 104-sourcing eval "JSON.stringify({done:window._allDone, total:window._allCandidates.length})"步驟 3:在 JS 中直接篩選,不需要呼叫個別履歷 API
Step 3: Direct Filtering in JS, No Need to Call Individual Resume APIs
搜尋結果的每筆資料已包含完整 ,可直接在記憶體中篩選:
expJobArrbash
agent-browser --session-name 104-sourcing eval "Each entry in the search results already contains the complete , which can be directly filtered in memory:
expJobArrbash
agent-browser --session-name 104-sourcing eval "範例:篩選職稱含「電銷」且年資 >= 1 年的候選人
Example: Filter candidates with job title containing 'Telemarketing' and work experience >= 1 year
var qualified = window._allCandidates.filter(function(c){
return c.expJobArr && c.expJobArr.some(function(e){
if((e.expTitle||'').indexOf('電銷') === -1) return false;
var desc = e.expEndDesc || '';
if(desc.indexOf('仍在職') > -1) return true;
var yr = desc.match(/(\d+)年/);
return yr && parseInt(yr[1]) >= 1;
});
});
window._qualified = qualified;
JSON.stringify({
totalSearched: window._allCandidates.length,
qualified: qualified.length,
list: qualified.map(function(c){
return {
idNo: c.idNo,
name: c.userName,
age: c.age,
edu: c.eduDesc.split(' ')[0],
city: c.wcityNoDesc,
totalExp: c.expPeriodDesc,
teleSalesJobs: c.expJobArr.filter(function(e){
return (e.expTitle||'').indexOf('電銷')>-1;
}).map(function(e){ return e.expTitle+'@'+e.expFirm+'('+e.expEndDesc+')'; })
};
})
})"
undefinedvar qualified = window._allCandidates.filter(function(c){
return c.expJobArr && c.expJobArr.some(function(e){
if((e.expTitle||'').indexOf('電銷') === -1) return false;
var desc = e.expEndDesc || '';
if(desc.indexOf('仍在職') > -1) return true;
var yr = desc.match(/(\d+)年/);
return yr && parseInt(yr[1]) >= 1;
});
});
window._qualified = qualified;
JSON.stringify({
totalSearched: window._allCandidates.length,
qualified: qualified.length,
list: qualified.map(function(c){
return {
idNo: c.idNo,
name: c.userName,
age: c.age,
edu: c.eduDesc.split(' ')[0],
city: c.wcityNoDesc,
totalExp: c.expPeriodDesc,
teleSalesJobs: c.expJobArr.filter(function(e){
return (e.expTitle||'').indexOf('電銷')>-1;
}).map(function(e){ return e.expTitle+'@'+e.expFirm+'('+e.expEndDesc+')'; })
};
})
})"
undefined搜尋 API 回傳的候選人欄位參考
Reference for Candidate Fields Returned by Search API
| 欄位 | 說明 |
|---|---|
| 候選人 ID(用於呼叫個別履歷 API) |
| 姓名 |
| 年齡 |
| 性別 |
| 學歷(含學校名,可用 |
| 希望工作地點(多個以「、」分隔) |
| 總工作年資描述 |
| 希望職稱類別 |
| 工作經歷陣列(含 |
| Field | Description |
|---|---|
| Candidate ID (used to call individual resume API) |
| Name |
| Age |
| Gender |
| Education background (including school name; use |
| Desired work location (multiple locations separated by 「、」) |
| Total work experience description |
| Desired job title category |
| Work experience array (including |
步驟 4:批次儲存合格候選人到資料夾
Step 4: Batch Save Qualified Candidates to Folder
篩選完成後,一次 API 呼叫即可批次儲存所有合格候選人,不需要 UI 操作。
After filtering is completed, you can batch save all qualified candidates with a single API call, no need for UI operations.
先取得資料夾列表,讓用戶選擇
First Get Folder List and Let User Select
bash
agent-browser --session-name 104-sourcing eval "fetch('https://auth.vip.104.com.tw/api/resumeTools/getFolderList?source=search&ec=105',{credentials:'include'}).then(function(r){return r.json()}).then(function(d){window._folders=JSON.stringify(d.result.folderList.map(function(f){return {name:f.name,folderNo:f.folderNo}}))});'fetching'"
sleep 2 && agent-browser --session-name 104-sourcing eval "window._folders"取得結果後,以表格呈現給用戶:
| # | 資料夾名稱 | folderNo |
|---|---|---|
| 1 | (依 API 回應填入) | ... |
詢問用戶:「請問要將 N 位候選人存入哪個資料夾?」,等用戶選擇後,以選定的 執行下一步。
folderNobash
agent-browser --session-name 104-sourcing eval "fetch('https://auth.vip.104.com.tw/api/resumeTools/getFolderList?source=search&ec=105',{credentials:'include'}).then(function(r){return r.json()}).then(function(d){window._folders=JSON.stringify(d.result.folderList.map(function(f){return {name:f.name,folderNo:f.folderNo}}))});'fetching'"
sleep 2 && agent-browser --session-name 104-sourcing eval "window._folders"Present the results to the user in a table:
| # | Folder Name | folderNo |
|---|---|---|
| 1 | (Fill in according to API response) | ... |
Ask the user: "Which folder would you like to save the N candidates to?" After the user selects, use the selected to proceed to the next step.
folderNo批次儲存所有合格候選人
Batch Save All Qualified Candidates
重要:每次 API 呼叫最多成功儲存 50 筆,超過的會靜默失敗(在中)。 必須以 50 筆為單位分批送出,並使用同步 XHR 取得即時結果(async fetch + window 變數在頁面導航後會消失)。params.fail
bash
undefinedImportant: Each API call can successfully save a maximum of 50 entries; entries exceeding this limit will fail silently (listed in). You must send requests in batches of 50 entries, and use synchronous XHR to get real-time results (async fetch + window variables will be lost after page navigation).params.fail
bash
undefined以每批 50 筆為單位,用同步 XHR 儲存,直接取得結果
Save in batches of 50 entries, use synchronous XHR to get direct results
agent-browser --session-name 104-sourcing eval "
var folderNo = '{用戶選擇的 folderNo}';
var batch = window._qualified.slice(0, 50).map(function(c){return c.idNo});
var body = 'rc=11012313&docNo='+folderNo+'&pageSource=search&isDuplicate=0&contentInfo%5Bsnapshot%5D=&contentInfo%5BsearchEngine%5D='+batch.join('%2C');
var xhr = new XMLHttpRequest();
xhr.open('POST','https://auth.vip.104.com.tw/api/resumeTools/saveResume',false);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.withCredentials = true;
xhr.send(body);
xhr.responseText;
"
將 `slice(0, 50)` 改為 `slice(50, 100)`、`slice(100, 150)`... 依此類推完成所有批次。
**回應格式說明:**
- `code: 0` → 全部儲存成功
- `code: 6, type: dataDuplicated` → **使用 isDuplicate=-1 時才會出現**;`params.id` 是成功儲存的 ID(非重複),其餘全被帳號層級的重複檢查跳過
- `code: 7, type: partialFail` → 部分失敗;`params.success.searchEngine[]` 是成功的,`params.fail.searchEngine[]` 是失敗的
- `code: 4, type: overStorage` → 資料夾已滿(上限 300 筆)
- `code: 5, type: overView` → 單次請求候選人數量超過平台上限
**saveResume POST 參數說明:**
| 參數 | 值 | 說明 |
|------|-----|------|
| `rc` | `11012313` | 操作類型碼(搜尋頁儲存,固定值) |
| `docNo` | `{folderNo}` | 目標資料夾 ID |
| `pageSource` | `search` | 來源頁面(固定值) |
| `isDuplicate` | `0` | **務必用 0**;`-1` 會跳過帳號內任何資料夾已存過的候選人,導致大量漏存 |
| `contentInfo[searchEngine]` | `{idNo1},{idNo2},...` | 候選人 idNo,逗號分隔,**每批最多 50 筆** |
| `contentInfo[snapshot]` | 空 | 快照 ID(搜尋頁固定留空) |agent-browser --session-name 104-sourcing eval "
var folderNo = '{folderNo selected by user}';
var batch = window._qualified.slice(0, 50).map(function(c){return c.idNo});
var body = 'rc=11012313&docNo='+folderNo+'&pageSource=search&isDuplicate=0&contentInfo%5Bsnapshot%5D=&contentInfo%5BsearchEngine%5D='+batch.join('%2C');
var xhr = new XMLHttpRequest();
xhr.open('POST','https://auth.vip.104.com.tw/api/resumeTools/saveResume',false);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.withCredentials = true;
xhr.send(body);
xhr.responseText;
"
Change `slice(0, 50)` to `slice(50, 100)`, `slice(100, 150)`... and so on to complete all batches.
**Response Format Description:**
- `code: 0` → All saved successfully
- `code: 6, type: dataDuplicated` → **Only appears when isDuplicate=-1**; `params.id` is the ID saved successfully (not duplicate), others are skipped by account-level duplicate check
- `code: 7, type: partialFail` → Partial failure; `params.success.searchEngine[]` are successful entries, `params.fail.searchEngine[]` are failed entries
- `code: 4, type: overStorage` → Folder is full (limit 300 entries)
- `code: 5, type: overView` → Number of candidates in single request exceeds platform limit
**Description of saveResume POST Parameters:**
| Parameter | Value | Description |
|------|-----|------|
| `rc` | `11012313` | Operation type code (save from search page, fixed value) |
| `docNo` | `{folderNo}` | Target folder ID |
| `pageSource` | `search` | Source page (fixed value) |
| `isDuplicate` | `0` | **Must use 0**; `-1` will skip any candidates that have been saved in any folder under the account, resulting in a large number of missed candidates; `0` will force save to the target folder |
| `contentInfo[searchEngine]` | `{idNo1},{idNo2},...` | Candidate idNo, separated by commas, **max 50 entries per batch** |
| `contentInfo[snapshot]` | Empty | Snapshot ID (fixed empty for search page) |候選人評估報告格式(基本版)
Candidate Evaluation Report Format (Basic Version)
undefinedundefined篩選結果(共 N 位符合條件)
Filter Results (Total N Qualified Candidates)
| # | 姓名 | 年齡 | 學歷 | 希望地點 | 工作年資 | 相關經歷 |
|---|---|---|---|---|---|---|
| 1 | 王小明 | 35 | 大學 | 台北市 | 8~9年 | 電銷主管@XX公司(3年) |
| ... |
建議聯絡:#1 王小明、#3 ...
建議略過:#2 ...(原因:地點不符)
undefined| # | Name | Age | Education | Desired Location | Total Work Experience | Relevant Experience |
|---|---|---|---|---|---|---|
| 1 | Wang Xiaoming | 35 | University | Taipei City | 8~9 years | Telemarketing Supervisor@XX Company (3 years) |
| ... |
Recommended for Contact: #1 Wang Xiaoming, #3 ...
Recommended to Skip: #2 ... (Reason: Location mismatch)
undefined步驟 3.5(選用):深度履歷分析
Step 3.5 (Optional): In-Depth Resume Analysis
顯示基本篩選結果後,詢問用戶是否進行深度分析:
「目前根據搜尋列表資料篩出 N 位候選人。是否進行深度履歷分析?(額外讀取自我介紹、希望薪資、產業年資,耗時較長但評估更精準)」
用戶選擇「是」後,依序執行:
After presenting the basic filter results, ask the user if they want to perform in-depth analysis:
"Currently, N candidates have been filtered based on search list data. Would you like to perform in-depth resume analysis? (This will additionally read self-introduction, desired salary, and industry experience; it takes longer but provides more accurate evaluation)"
If the user selects "Yes", proceed in order:
分批 fetch 個別履歷(每批 50 筆)
Batch Fetch Individual Resumes (50 Entries per Batch)
以 50 筆為一批,批次間 sleep 5 秒,避免 rate limit。主 session 收集每批 JSON:
bash
undefinedFetch in batches of 50 entries, sleep 5 seconds between batches to avoid rate limits. The main session collects JSON data for each batch:
bash
undefined第 1 批(idNo 0~49)
Batch 1 (idNo 0~49)
agent-browser --session-name 104-sourcing eval "
var ids = window._qualified.slice(0, 50).map(function(c){return c.idNo});
var results = {};
Promise.all(ids.map(function(id){
return fetch('https://auth.vip.104.com.tw/vipapi/resume/search/'+id+'?path_for_log=list_search',{credentials:'include'})
.then(function(r){return r.json()})
.then(function(d){
var res = d.data ? d.data.resume : null;
if(!res) return;
results[id] = {
intro: res.intro ? res.intro.replace(/<[^>]+>/g,'') : '',
hopeSalary: res.hopeSalaryDesc || '',
expCats: res.expCatTimeDesc ? res.expCatTimeDesc.map(function(e){return e.expCatDesc+':'+e.expTimeDesc}).join(', ') : ''
};
});
})).then(function(){ window._resumeBatch = JSON.stringify(results); });
'fetching batch 1...'
"
sleep 10 && agent-browser --session-name 104-sourcing eval "window._resumeBatch"
每批取回 JSON 後,累積到主 session 的物件中(`let allDetails = {...allDetails, ...JSON.parse(batchJson)}`)。
將 `slice(0,50)` 改為 `slice(50,100)`, `slice(100,150)` ... 完成所有批次。
> 批次數量 = `Math.ceil(qualified.length / 50)`,每批 sleep 10 秒。agent-browser --session-name 104-sourcing eval "
var ids = window._qualified.slice(0, 50).map(function(c){return c.idNo});
var results = {};
Promise.all(ids.map(function(id){
return fetch('https://auth.vip.104.com.tw/vipapi/resume/search/'+id+'?path_for_log=list_search',{credentials:'include'})
.then(function(r){return r.json()})
.then(function(d){
var res = d.data ? d.data.resume : null;
if(!res) return;
results[id] = {
intro: res.intro ? res.intro.replace(/<[^>]+>/g,'') : '',
hopeSalary: res.hopeSalaryDesc || '',
expCats: res.expCatTimeDesc ? res.expCatTimeDesc.map(function(e){return e.expCatDesc+':'+e.expTimeDesc}).join(', ') : ''
};
});
})).then(function(){ window._resumeBatch = JSON.stringify(results); });
'fetching batch 1...'
"
sleep 10 && agent-browser --session-name 104-sourcing eval "window._resumeBatch"
Change `slice(0,50)` to `slice(50,100)`, `slice(100,150)` ... to complete all batches.
> Number of batches = `Math.ceil(qualified.length / 50)`, sleep 10 seconds between batches.合併資料並寫入暫存檔
Merge Data and Write to Temporary File
所有批次完成後,將 (基本資料)與累積的個別履歷合併,寫入暫存檔:
window._qualifiedbash
undefinedAfter all batches are completed, merge (basic data) with the collected individual resumes, and write to .
window._qualified/tmp/104_resumes.jsonjson
[
{
"idNo": "...",
"name": "...",
"age": 28,
"expJobArr": [...],
"intro": "...",
"hopeSalary": "35,000~45,000",
"expCats": "教育業:2年, 金融業:1年"
},
...
]主 session 中取出 _qualified 基本資料
Launch Sub-agents for Concurrent Analysis
agent-browser --session-name 104-sourcing eval "JSON.stringify(window._qualified)"
將兩份資料合併為:
```json
[
{
"idNo": "...",
"name": "...",
"age": 28,
"expJobArr": [...],
"intro": "...",
"hopeSalary": "35,000~45,000",
"expCats": "教育業:2年, 金融業:1年"
},
...
]寫入 。
/tmp/104_resumes.jsonGroup candidates into groups of 50 entries, use the Task tool to simultaneously launch multiple sub-agents (subagent_type: ):
general-purposeSample Task prompt (for each sub-agent):
Please read /tmp/104_resumes.json and analyze candidates from index {start} to {end} (0-indexed).
Recruitment Criteria:
- Position: {Job Title}
- Hard Requirements: {Criteria}
- Ideal Candidate: {Description}
- Red Flags: {Exclusion Criteria}
- Bonus Items: {nice-to-have}
For each candidate:
1. Score (1-5)
2. Recommendation Reason (one sentence)
3. Concerns (if any)
Return Format (JSON array):
[{"idNo":"...","score":4,"reason":"...","concern":"..."}]After all sub-agents return results, the main session aggregates all scoring results, sorts them by score, and presents the in-depth evaluation report:
undefined並發啟動 Sub-agents 分析
In-Depth Filter Results (Total N Candidates, Sorted by Recommendation Score)
將候選人分組,每組 50 筆,用 Task tool 同時啟動多個 sub-agents(subagent_type: ):
general-purposeTask prompt 範例(每個 sub-agent):
請讀取 /tmp/104_resumes.json,分析第 {start} 到第 {end} 筆候選人(0-indexed)。
招募條件:
- 職位:{職位}
- 硬性要求:{條件}
- 理想候選人:{描述}
- 地雷:{排除條件}
- 加分:{nice-to-have}
請針對每位候選人:
1. 評分(1-5)
2. 推薦原因(一句話)
3. 疑慮(若有)
回傳格式(JSON array):
[{"idNo":"...","score":4,"reason":"...","concern":"..."}]主 session 等所有 sub-agents 回傳後,彙整所有評分結果,以分數排序後呈現深度版評估報告:
undefined| # | Name | Score | Age | Desired Salary | Industry Background | Recommendation Reason | Concerns |
|---|---|---|---|---|---|---|---|
| 1 | Wang Xiaoming | ★★★★★ | 28 | 40,000~50,000 | 2 years in education industry | Meets ideal background | None |
| ... |
---深度篩選結果(共 N 位,按推薦分數排序)
Notes
| # | 姓名 | 評分 | 年齡 | 希望薪資 | 產業背景 | 推薦原因 | 疑慮 |
|---|---|---|---|---|---|---|---|
| 1 | 王小明 | ★★★★★ | 28 | 4~5萬 | 教育業2年 | 符合理想背景 | 無 |
| ... |
---- eval only supports ES5: Do not use , arrow functions, or template literals, otherwise SyntaxError will be thrown
const/let - API fetch requests must be called within the main session (104-sourcing): Independent sessions of Subagents do not have login cookies
- contains HTML tags; use
expJobNoteto clean it before analysis.replace(/<[^>]+>/g, '') - must be obtained from the response of Page 1 and used for all subsequent page requests
fixedUpdateDate - If the session expires (fetch returns "Not logged in"), re-execute the login process in Stage 2
- Do not add "人才" (talent) to search keywords: The 104 search mechanism is not precise enough; adding "人才" will easily result in HR positions like "Talent Specialist" or "Talent Consultant", which interfere with results. Use only the job title instead (e.g., Telemarketing, Sales, Engineer)
- Must use isDuplicate=0 for saveResume: is account-level duplicate check (candidates saved in any folder are considered duplicates), which will cause a large number of candidates to be skipped silently;
-1will force save to the target folder0 - Max 50 entries per batch: The actual maximum number of successfully saved entries per saveResume request is 50; entries exceeding this limit will be listed in without error prompts, so be sure to batch in units of 50
params.fail - Use synchronous XHR instead of async fetch for saveResume: Async fetch requires additional sleep + reading window variables, which will be lost once page navigation occurs; synchronous XHR returns results directly and is more reliable
- Page navigation will clear all window variables: As long as is executed to switch pages,
open,window._qualified, etc. will all be lost, and you need to re-fetch and filterwindow._allCandidates
注意事項
—
- eval 只能用 ES5:不可用 、arrow function、template literal,否則報 SyntaxError
const/let - API fetch 必須在主 session(104-sourcing)內呼叫:Subagent 的獨立 session 沒有登入 cookie
- 含 HTML 標籤,分析前用
expJobNote清除.replace(/<[^>]+>/g, '') - 必須從第 1 頁回應取得後,用於所有後續分頁請求
fixedUpdateDate - 若 session 失效(fetch 回傳「尚未登入」),重新執行第二階段登入流程
- 搜尋關鍵字不要加「人才」兩字:104 搜尋機制不夠精準,加了「人才」後容易搜到「人才專員」「人才顧問」等人資職缺,反而干擾結果。直接用職位名稱即可(例如:電銷、業務、工程師)
- saveResume 必須用 isDuplicate=0:是帳號層級的重複檢查(只要曾存過任何資料夾就算重複),會導致大量候選人被靜默跳過;
-1才會強制儲存到目標資料夾0 - 每批最多 50 筆:saveResume 每次請求實際成功儲存上限為 50 筆,超過的會列在 但不報錯,務必以 50 為單位分批
params.fail - saveResume 用同步 XHR,不用 async fetch:async fetch 需要額外的 sleep + 讀取 window 變數,一旦中間導航頁面就會遺失;同步 XHR 直接回傳結果,更可靠
- 頁面導航會清空所有 window 變數:只要執行 切換頁面,
open、window._qualified等全部消失,需重新抓取篩選window._allCandidates
—