Skip to content

Latest commit

 

History

History
319 lines (220 loc) · 12.1 KB

File metadata and controls

319 lines (220 loc) · 12.1 KB

GeekHunter — Scrape vagas and apply automatically

This script lists job openings from GeekHunter vagas, filters them by keywords and remote work, and optionally applies to each job (either by clicking “Tenho interesse nessa vaga” or by filling and submitting the “Candidatar para a vaga” form).


Table of contents


What it does

  1. List jobs
    Opens the vagas listing in a headless browser (with your cookies), fetches one or more pages, and parses each job card. For each job it extracts:

    • URL (job detail page)
    • Title (when present in the card)
    • Remote (true if the card contains “Remoto”)
  2. Filter
    Keeps only jobs that:

    • Match at least one keyword in title or description (e.g. Full Stack, Backend, TypeScript, Node, React)
    • Are remote when “remote only” is enabled
    • Respects a maximum number of jobs per run (e.g. 10)
  3. Dry-run or apply

    • Dry-run: Prints the filtered job URLs. Does not open job pages or click anything. Does not use the “applied” state file.
    • Apply: For each filtered job (excluding already-applied URLs):
      • Opens the job page
      • If “Tenho interesse nessa vaga” is visible → clicks it
      • Else if “Candidatar para a vaga” and a form are visible → fills the form (name, LinkedIn, CV file, terms) and submits
      • Else → skips and logs the reason
      • Waits 2 seconds between jobs and records each applied URL in the state file

Prerequisites

  • Node.js (v18+ recommended)
  • npm (to install dependencies and run scripts)
  • Playwright (installed as a project dependency; browsers are downloaded on first use)

From the repository root:

npm install

No separate install step is needed for the GeekHunter script; it uses the project’s existing Playwright dependency.


Cookies (authentication)

The script needs your GeekHunter session cookies so the site sees you as logged in. Without them, the listing may show a login page and apply will fail.

Cookie file format

A JSON file containing an array of cookie objects. Each object should have at least:

  • name (string)
  • value (string)
  • domain (e.g. ".geekhunter.com.br" or "www.geekhunter.com.br")
  • path (e.g. "/")

Optional but useful: expires, httpOnly, secure, sameSite.

Example (minimal):

[
  {
    "name": "remember_candidate_token",
    "value": "YOUR_TOKEN_VALUE",
    "domain": "www.geekhunter.com.br",
    "path": "/"
  },
  {
    "name": "_geekhunter_session",
    "value": "YOUR_SESSION_VALUE",
    "domain": "www.geekhunter.com.br",
    "path": "/"
  }
]

In practice you need at least the session-related cookies (e.g. remember_candidate_token, _geekhunter_session, auth_token, XSRF-TOKEN, user_type) for the logged-in listing and apply to work.

Where to put the file

  • Default: In the repository root, name the file geekhunter-cookies.json.
  • Custom path: Set the env var GEEKHUNTER_COOKIES to the path (relative to the current working directory or absolute). Example: GEEKHUNTER_COOKIES=./my-cookies.json.

How to get the cookies

  1. Log in to GeekHunter in your normal browser.
  2. Open DevTools (F12) → Application (Chrome) or Storage (Firefox) → Cookies → https://www.geekhunter.com.br.
  3. Copy the relevant cookies (name, value, domain, path) into a JSON array and save as geekhunter-cookies.json in the repo root (or the path you set in GEEKHUNTER_COOKIES).

Important: This file is in .gitignore. Do not commit it; it contains session data.


How to run

All commands are run from the repository root (JamDev0).

Dry-run (recommended first)

Only lists and filters; prints URLs. Does not open job pages or apply. Cookies are optional (listing may show login if missing).

npm run geekhunter -- --dry-run

Apply (real run)

Lists, filters, then applies to each job (interest button or form). Requires cookies. Skips URLs already in the state file.

npm run geekhunter

Direct Node (same behavior)

node scripts/geekhunter/run.mjs --dry-run
node scripts/geekhunter/run.mjs

Environment variables

Variable Description Default
GEEKHUNTER_COOKIES Path to the JSON cookie file geekhunter-cookies.json (in cwd)
GEEKHUNTER_APPLIED_FILE Path to the “applied URLs” state file geekhunter-applied.json (in cwd)
GEEKHUNTER_MAX_PAGES Max listing pages to fetch (1–77) 77 (all pages)

Examples:

GEEKHUNTER_MAX_PAGES=10 npm run geekhunter -- --dry-run
GEEKHUNTER_COOKIES=./cookies/gh.json npm run geekhunter
GEEKHUNTER_APPLIED_FILE=./data/applied.json npm run geekhunter

Filter configuration

The filter is defined in scripts/geekhunter/run.mjs in DEFAULT_FILTER_CONFIG:

  • keywords — Array of strings; job title/description must contain at least one (case-insensitive).
  • remoteOnly — If true, only jobs with remote === true (card contains “Remoto”) are kept.
  • maxJobs — Maximum number of jobs to consider after keyword and remote filter (e.g. 10).

To change behavior, edit that object in run.mjs (or later refactor to a config.json / env and load it there).


Apply flows

GeekHunter job pages can show two different flows:

  1. “Tenho interesse nessa vaga”
    One click to signal interest. No form. The script clicks the button and records the URL as applied.

  2. “Candidatar para a vaga”
    A form with Nome completo, Email (often pre-filled), LinkedIn, file upload (CV), and terms checkbox. The script fills what it can from formConfig, checks the terms box, and clicks the button.

If neither is present (e.g. already applied or different page layout), the job is skipped and the reason is logged.


Form config (for “Candidatar” jobs)

When the script submits the “Candidatar para a vaga” form, it can fill:

  • Nome completo — from formConfig.fullName
  • LinkedIn — from formConfig.linkedInUrl
  • CV file — from formConfig.cvPath (absolute or relative path to a file)

Currently run.mjs passes an empty object {} to applyToJob, so those fields are left blank unless you change the code. To personalize:

  1. Open scripts/geekhunter/run.mjs.
  2. Find the call: applyToJob(page, job.url, {}).
  3. Replace with something like:
    applyToJob(page, job.url, {
      fullName: 'Your Full Name',
      linkedInUrl: 'https://linkedin.com/in/your-profile',
      cvPath: resolve(process.cwd(), 'resumes', 'your-cv.pdf'),
    });
    You can also read these from env (e.g. process.env.GEEKHUNTER_FULL_NAME) if you prefer.

State file (avoid re-applying)

When you run apply (without --dry-run), the script keeps a list of job URLs it has already applied to. Before applying, it loads this list and skips those URLs. After each successful apply, it appends the new URL and writes the file.

  • Default path: geekhunter-applied.json in the current working directory (repo root when you run npm run geekhunter).
  • Override: Set GEEKHUNTER_APPLIED_FILE to another path.

Format:

{
  "applied": [
    "https://www.geekhunter.com.br/company-a/jobs/slug-one",
    "https://www.geekhunter.com.br/company-b/jobs/slug-two"
  ]
}
  • Dry-run does not read or write this file.
  • The file is in .gitignore; do not commit it if it contains your data.

File layout

scripts/geekhunter/
├── README.md                 # This file
├── run.mjs                   # CLI entry: list → filter → apply
├── list-vagas.mjs            # Fetches listing pages, parses with parse-listing
├── parse-listing.mjs         # Pure: HTML → job list (url, title, remote)
├── filter.mjs                # Pure: filter jobs by keywords, remote, maxJobs
├── apply-decision.mjs        # Pure: page snapshot → action (click_interest | submit_form | skip)
├── apply.mjs                 # Per-job: navigate, decide, click or submit form
└── __tests__/
    ├── filter.test.mjs
    ├── parse-listing.test.mjs
    └── apply-decision.test.mjs
  • Pure modules (filter, parse-listing, apply-decision): no browser; easy to unit test.
  • Browser modules (list-vagas, apply): take a Playwright page; used by run.mjs, which launches the browser and loads cookies.

Tests

Unit tests cover the pure logic: filter, parse-listing, and apply-decision.

From the repository root:

npm run test -- --run

Or to watch:

npm run test

Tests are under scripts/geekhunter/__tests__/. No browser is started; no live requests to GeekHunter.


How to verify that applications were really sent

  1. On GeekHunter (source of truth)
    Log in at geekhunter.com.br, open your profile/dashboard, and check for any section that lists vagas you’ve shown interest in or candidaturas. That list is the only definitive proof that the platform registered your action.

  2. Spot-check a few job pages
    Open 2–3 URLs from geekhunter-applied.json in your browser while logged in. If the apply really went through, the page will usually show something like “Você já demonstrou interesse” or the “Tenho interesse” / “Candidatar” button will be gone or disabled. If you still see the normal apply button, that one may not have been recorded.

  3. What the script does
    After each click (“Tenho interesse” or “Candidatar”), the script waits ~3 seconds and checks whether the apply button disappeared or success-style text appeared. When that happens it logs Applied: <url> (verified on page). If you see (verified on page) for a run, that application was confirmed by the page state. If you don’t see it, the click was performed but the success check didn’t match (e.g. different wording on the site); you can still confirm manually with steps 1–2.


Troubleshooting

  • “Cookies required for apply”
    You ran without --dry-run and the script could not load cookies. Create geekhunter-cookies.json (or set GEEKHUNTER_COOKIES) with a valid cookie array.

  • Dry-run prints “(none matched filter)”
    The listing was fetched but no job passed the filter (keywords and/or remote). Check DEFAULT_FILTER_CONFIG in run.mjs. If the site’s HTML changed, title/remote might not be parsed; check parse-listing.mjs and the structure of the listing page.

  • Apply always skips with “no interest or candidatar form on page”
    The job page may have a different layout, or your profile may not be approved (some jobs only show “Tenho interesse” for approved profiles). Try opening the same URL in your browser and see which buttons/forms appear.

  • Playwright “browser not found”
    Run npx playwright install chromium from the repo root (or install the browser(s) you use).

  • Rate limiting / blocking
    The script waits 2 seconds between applies. If you still get blocked, increase the delay in run.mjs (DELAY_BETWEEN_JOBS_MS) or reduce maxJobs / GEEKHUNTER_MAX_PAGES.


Security and ignored files

The following are in .gitignore and should not be committed:

  • geekhunter-cookies.json — Session cookies; sensitive.
  • geekhunter-applied.json — List of URLs you applied to; optional privacy.
  • playwright-state.json — Playwright storage state if you add it later.

Keep cookie and applied files only on your machine or in a secure, private store.