{"id":"linkedin-jobs-search","name":"linkedin-jobs-search","summary":"LinkedInの求人情報を検索し、詳細な求人情報を抽出してください。仕事の種類(リモート/現地/ハイブリッド)、契約タイプ(フルタイム/パートタイム/契約/インターンシップ)、経験レベル、掲載日、所属会社によるフィルタリングをサポートします。","body":"# LinkedIn — Job Search\n\n> keywords + location + filters → paginated job list with full details\n\n## Language\n\nAll process output to user (progress updates, process notifications) follows the user's language.\n\n## Objective\n\nSearch LinkedIn job listings with full filter support, extract complete job data with full field coverage.\n\n## Prerequisites\n\n- The browser is open and the LinkedIn session is active (logged in). A LinkedIn jobs search page such as `https://www.linkedin.com/jobs/search/` must have been visited at least once so the CSRF token cookie is set.\n\n## Pre-execution Checks\n\n### 1. Tool Readiness\n\nIf browser-act has been confirmed available in the current session → skip this step.\n\nInvoke `browser-act` via Skill tool to load usage. If installation or configuration issues arise, follow its guidance to resolve then retry.\n\n### 2. Login Verification\n\nIf login status for LinkedIn has been confirmed in the current session → skip this step.\n\nOtherwise: open `https://www.linkedin.com` and observe the page:\n- User avatar or \"Me\" menu visible → logged in, continue\n- Sign in / Join button visible → not logged in, inform user that LinkedIn login is required first\n\nUser refuses or cannot log in → terminate execution.\n\n## Capability Components\n\n> This Skill's operational boundary = what the user can manually do in their browser. It accesses LinkedIn through the user's logged-in browser, only reading data already available to the user. JS code is encapsulated in Python files under the `scripts/` directory, invoked via `eval \"$(python scripts/xxx.py {params})\"`. `$(...)` is bash syntax; it is recommended to use the bash tool for execution.\n\n### API: Search LinkedIn jobs (list page)\n\n`eval \"$(python scripts/search-jobs.py '{keywords}' '{location}' --count {count} --start {start} --work-type {work_type} --job-type {job_type} --experience {experience} --time-posted {time_posted} --company-ids {company_ids})\"`\n\nParameters:\n- `keywords`: job title or search keywords (e.g., `software engineer`, `data analyst`)\n- `location`: location name (e.g., `United States`, `New York`, `San Francisco Bay Area`)\n- `--count`: results per API call, default `25`, max `100`\n- `--start`: pagination offset, default `0`. Increment by `count` for each page\n- `--work-type`: work arrangement filter — `1`=On-site, `2`=Remote, `3`=Hybrid (optional)\n- `--job-type`: contract type filter — `F`=Full-time, `P`=Part-time, `C`=Contract, `T`=Temporary, `I`=Internship, `V`=Volunteer (optional)\n- `--experience`: experience level filter — `1`=Internship, `2`=Entry, `3`=Associate, `4`=Mid-Senior, `5`=Director (optional)\n- `--time-posted`: recency filter — `r86400`=24h, `r604800`=7 days, `r2592000`=30 days (optional)\n- `--company-ids`: comma-separated LinkedIn company numeric IDs (optional, e.g., `76987811,1441`)\n\nOutput example:\n```json\n{\n  \"total\": 36015,\n  \"start\": 0,\n  \"count\": 5,\n  \"jobs\": [\n    {\n      \"id\": \"4416832078\",\n      \"title\": \"Lead Frontend Software Engineer\",\n      \"company\": \"RowsOne\",\n      \"location\": \"Boca Raton, FL\",\n      \"workType\": \"Remote\",\n      \"jobUrl\": \"https://www.linkedin.com/jobs/view/4416832078\",\n      \"companyUrl\": \"https://www.linkedin.com/company/rowsone\"\n    }\n  ]\n}\n```\n\nError handling: If `{\"error\": true}` is returned, check that the browser is still logged in to LinkedIn and navigate to `https://www.linkedin.com/jobs/search/` to refresh the session, then retry once.\n\n### API: Get full job details\n\n`eval \"$(python scripts/job-detail.py '{job_id}')\"`\n\nParameters:\n- `job_id`: numeric LinkedIn job posting ID (from `id` field in search results)\n\nOutput example:\n```json\n{\n  \"id\": \"4416832078\",\n  \"title\": \"Lead Frontend Software Engineer\",\n  \"company\": \"RowsOne\",\n  \"companyUrl\": \"https://www.linkedin.com/company/rowsone\",\n  \"location\": \"Boca Raton, FL\",\n  \"workType\": \"Remote\",\n  \"contractType\": \"Full-time\",\n  \"experienceLevel\": \"Mid-Senior level\",\n  \"listedAt\": \"2026-05-26T16:14:30.000Z\",\n  \"applicantCount\": 37,\n  \"description\": \"Lead Frontend Engineer (React / Next.js)...\",\n  \"salary\": null,\n  \"jobUrl\": \"https://www.linkedin.com/jobs/view/4416832078\"\n}\n```\n\nError handling: HTTP 404 means job has been removed or ID is invalid. If `{\"error\": true, \"message\": \"HTTP 403\"}`, the LinkedIn session may have expired — navigate back to LinkedIn and verify login, then retry.\n\n### Composite: Full job extraction (search list + detail for each job)\n\nFor complete output with all fields (description, contract type, experience level, posted date):\n\n1. Run search component to collect job IDs and basic info\n2. For each job ID, run the detail component\n3. Merge results by job ID\n\nBatch script template (bash):\n```bash\n#!/bin/bash\nSESSION=\"fb_explore\"\nKEYWORDS=\"software engineer\"\nLOCATION=\"United States\"\nTOTAL_ROWS=50\nCOUNT=25\nOUTPUT_FILE=\"output/jobs.jsonl\"\n\noffset=0\ncollected=0\nwhile [ $collected -lt $TOTAL_ROWS ]; do\n  batch_count=$((TOTAL_ROWS - collected))\n  [ $batch_count -gt $COUNT ] && batch_count=$COUNT\n\n  result=$(browser-act --session $SESSION eval \"$(python scripts/search-jobs.py \"$KEYWORDS\" \"$LOCATION\" --count $batch_count --start $offset)\")\n  echo \"$result\" | python -c \"\nimport json, sys\ndata = json.loads(sys.stdin.read())\nfor job in data.get('jobs', []):\n    print(json.dumps(job))\n\" >> output/jobs_basic.jsonl\n\n  job_ids=$(echo \"$result\" | python -c \"import json,sys; [print(j['id']) for j in json.loads(sys.stdin.read()).get('jobs',[])]\")\n  for job_id in $job_ids; do\n    detail=$(browser-act --session $SESSION eval \"$(python scripts/job-detail.py $job_id)\")\n    echo \"$detail\" >> $OUTPUT_FILE\n    sleep 1\n  done\n\n  page_count=$(echo \"$result\" | python -c \"import json,sys; print(json.loads(sys.stdin.read()).get('count',0))\")\n  [ \"$page_count\" -eq 0 ] && break\n  collected=$((collected + page_count))\n  offset=$((offset + page_count))\n  sleep 2\ndone\necho \"Done. Collected $collected jobs.\"\n```\n\nNote: Add `sleep 1` between detail calls to avoid rate limiting. For large batches (>200 jobs), use multiple browser sessions in parallel — each session counts independently toward rate limits.\n\n## Enum Parameters\n\nFilter values are hardcoded in scripts; no dynamic enumeration needed.\n\nWork type (`--work-type`): `1`=On-site, `2`=Remote, `3`=Hybrid\n\nContract type (`--job-type`): `F`=Full-time, `P`=Part-time, `C`=Contract, `T`=Temporary, `I`=Internship, `V`=Volunteer\n\nExperience level (`--experience`): `1`=Internship, `2`=Entry level, `3`=Associate, `4`=Mid-Senior level, `5`=Director\n\nTime posted (`--time-posted`): `r86400`=Past 24 hours, `r604800`=Past week, `r2592000`=Past month\n\n## Pagination\n\n**API Pagination**: parameter `--start`, type: page-offset, start value: `0`. Next page: increment by `--count` value. Termination: when `count` in response is `0`, or `start >= total`, or `start >= rows` target.\n\nLinkedIn typically returns results up to `start=1000` maximum regardless of `total`.\n\n## Success Criteria\n\n`result count >= 1` and `jobs[0].id` is non-null\n\n## Known Limitations\n\n- LinkedIn limits accessible search results to approximately the first 1000 jobs per query even when `total` shows a higher number\n- `experienceLevel` may be null for many postings — companies do not always fill in this field\n- `salary` is null for most postings; LinkedIn only shows salary when the employer explicitly provides it\n- Rate limiting: sustained rapid requests (e.g., >100 detail calls without sleep) may trigger temporary blocks. Add `sleep 1` between detail calls\n- Login required: unlike public job boards, LinkedIn's Voyager API requires an authenticated session. The CSRF token is derived from the `JSESSIONID` cookie set at login\n\n## Execution Efficiency\n\n- **Batch orchestration**: write a bash loop iterating over job IDs serially; do not parallelize within one browser. For higher throughput, use multiple stealth browsers with separate sessions\n- **Test before batch**: run with `--count 3` first to confirm the script runs correctly before scaling up\n- **Error resumption**: append results to `.jsonl` file line-by-line so the job can resume from a specific offset on failure\n- **Search only for large volumes**: for >500 jobs where full description is not needed, use the search component alone — it returns title, company, location, work type, and URLs without per-job detail calls\n\n## Experience Notes\n\nPath: `{working-directory}/browser-act-skill-forge-memories/linkedin-job-search-linkedin-jobs-search.memory.md` (working directory is determined by the Agent running the Skill, typically the project root or current working directory)\n\n**Before execution**: If the file exists, read it first — it records unexpected situations encountered during past executions (e.g., a strategy has become ineffective); adjust strategy order accordingly.\n\n**After execution**: If an unexpected situation is encountered (strategy became ineffective, page redesigned, anti-scraping upgraded, better path discovered), append a line:\n`{YYYY-MM-DD}: {what happened} → {conclusion}`\n\nNormal execution does not write to the file. Do not record what keywords were used or how many results were returned — those are task outputs, not experience.","author":"@browser-act","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/browser-act/skills/tree/main/solutions/lead-generation/linkedin-jobs-search","license":"MIT","category":"coding","lang":"en","tokens":2338,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"scripts/job-detail.py","size":2662,"sha256":"8a4e1e54791d669bc37579740d3507e2940bcc0664917a37c2d16e535e97cfdc"},{"path":"scripts/search-jobs.py","size":3974,"sha256":"d0ce47b09aa06d8a97b29241e37afea48d65402a2a0f67449dd83da08e6fa24f"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[{"code":"net.endpoints","kind":"exfiltration","excerpt":"www.linkedin.com","message":"bundled scripts reach 1 external host(s)","severity":"warn"}],"scannedAt":"2026-08-22","hasScripts":true,"networkEndpoints":["www.linkedin.com"]}}