1. 2scraper
  2. Jobs & hiring
  3. Just Join IT
Open source · MITRuns with no key, proxy or account

Just Join IT Scraper

Polish IT job offers with real salaries, seniority, skill levels and locations — as JSON or CSV, no key needed.

  • Playwright
  • Selenium
  • Puppeteer
  • Scraping Browser API
  • JSON
  • CSV
justjoin-scraper
# Job listings
$ python3 playwright_scraper.py --city Warszawa --experience senior --with-salary --pages 5 --format csv --out warsaw_senior
✓ 9 sample rows · 50 columns in sample_output.json
3modes: listings, offer, facets
50columns per offer
19,381offers on the board, Sep 18 2026
0keys, proxies or accounts needed
Available Just Join IT scrapers

What the Just Join IT scraper collects

Scrapes justjoin.it job offers through the endpoint its own front end calls: paginated, filtered listings, full single offers with the description and per-skill levels, and the board's uncapped facet counts. Output is JSON or CSV with a sidecar recording the site's 10,000-result cap.

Job listings

Input: Filters: --category, --city, --experience, --with-salary and more

One row per offer: company, category, seniority, workplace type, salary in the employer's unit plus a monthly figure, required skills, languages and locations.

Single offers

Input: A justjoin.it job-offer URL, or a listings run's JSON via --slugs-file

One offer in full: the description body, per-skill levels, company website and size, and the country.

Board facets

Input: No input — the whole board

The board's own taxonomy with its own offer counts, which are not capped, so you can plan category runs that cover everything.

Quick start

From zero to Just Join IT data in 3 steps

  1. Install

    Clone the repository and install one browser engine. Python 3.9+.

    $ git clone https://github.com/2scraper/justjoin-scraper.git && cd justjoin-scraper
    $ pip install -r requirements.txt -r requirements-playwright.txt
    $ python3 -m playwright install chromium
    $ python3 playwright_scraper.py --category python --pages 5 --format csv
  2. Run a scraper

    Pick what you need and run it. Every flag is documented in the README.

    $ python3 playwright_scraper.py --city Warszawa --experience senior --with-salary --pages 5 --format csv --out warsaw_senior
  3. Get structured data

    Rows land in JSON or CSV (--format), with a .meta.json sidecar recording status, pages fetched and why the run stopped.

Output

Just Join IT data fields

50 columns per row in a predictable schema. The most useful ones:

FieldWhat it holds
skuThe offer's guid, a UUID — the join key across modes
titleJob title
company_nameEmployer name
categoryBoard category, e.g. python
experience_levelSeniority, e.g. senior
workplace_typeremote, hybrid or office
salary_minLower bound in the employer's quoted unit; null when undisclosed
salary_maxUpper bound in the employer's quoted unit
salary_currencyThe employer's original currency, not a site conversion
salary_unitUnit the salary was quoted in, e.g. hour or month
salary_monthly_minThe site's monthly normalisation of the lower bound
salary_contract_typeContract the salary applies to, e.g. b2b
required_skillsRequired skills list
required_skill_levelsLevel for each required skill
locationsEvery city the offer lists
apply_methodexternal or form (applied to on justjoin.it)
[
  {
    "source": "justjoin.it",
    "scraped_at": "2026-09-18T11:56:56Z",
    "url": "https://justjoin.it/job-offer/port-blue-sky-sp-z-o-o-sp-k--senior-software-engineer---p…",
    "sku": "3be7c75b-f182-4dd5-a956-16b505f93fa1",
    "title": "Senior Software Engineer – Python / React / Data",
    "slug": "port-blue-sky-sp-z-o-o-sp-k--senior-software-engineer---python-react-data-warszawa-python",
    "company_name": "PORT BLUE SKY Sp. z o.o. sp.k.",
    "company_logo_url": "https://imgproxy.justjoinit.tech/OeuSuuCp2rJICtPt10dWVrEYrgRPo_mYWIkJ7BElWFU/h:200/w:20…",
    "company_url": null,
    "company_size": null,
    "category": "python",
    "experience_level": "senior",
    "workplace_type": "remote",
    "working_time": "b2b_contract",
    "salary_min": 200.0,
    "salary_max": 240.0,
    "salary_currency": "PLN",
    "salary_unit": "hour",
    "salary_is_gross": false,
    "salary_contract_type": "b2b",
    "salary_monthly_min": 33600.0,
    "salary_monthly_max": 40320.0,
    "salary_options": null,
    "city": "Warszawa",
    "street": "Rondo ONZ 1",
    "country_code": null,
    "latitude": 52.23,
    "longitude": 21.0,
    "locations": [
      "Warszawa"
    ],
    "location_count": 1,
    "required_skills": [
      "Python",
      "React",
      "Docker",
      "… 6 more"
    ],
    "required_skill_levels": [
      5,
      5,
      5,
      "… 6 more"
    ],
    "nice_to_have_skills": null,
    "languages": null,
    "language_levels": null,
    "office_days": null,
    "remote_days": null,
    "remote_interview": false,
    "open_to_hire_ukrainians": false,
    "is_super_offer": false
  },
  {
    "source": "justjoin.it",
    "scraped_at": "2026-09-18T11:56:56Z",
    "url": "https://justjoin.it/job-offer/jit-team-mlops-data-engineer-warszawa-python",
    "sku": "a8fdc280-9a73-4177-b03e-f070c1f54c06",
    "title": "MLOps / Data Engineer",
    "slug": "jit-team-mlops-data-engineer-warszawa-python",
    "company_name": "Jit Team",
    "company_logo_url": "https://imgproxy.justjoinit.tech/wWSYQ3PfIRATt5r6FpNQ1tx2EsfW1STfzPhAXi3RVQg/h:200/w:20…",
    "company_url": null,
    "company_size": null,
    "category": "python",
    "experience_level": "mid",
    "workplace_type": "hybrid",
    "working_time": "freelance",
    "salary_min": 25200.0,
    "salary_max": 28560.0,
    "salary_currency": "PLN",
    "salary_unit": "month",
    "salary_is_gross": false,
    "salary_contract_type": "b2b",
    "salary_monthly_min": 25200.0,
    "salary_monthly_max": 28560.0,
    "salary_options": null,
    "city": "Warszawa",
    "street": "-",
    "country_code": null,
    "latitude": 52.23,
    "longitude": 21.01,
    "locations": [
      "Warszawa"
    ],
    "location_count": 1,
    "required_skills": [
      "Machine Learning",
      "Azure",
      "DevOps",
      "… 4 more"
    ],
    "required_skill_levels": [
      3,
      3,
      3,
      "… 4 more"
    ],
    "nice_to_have_skills": null,
    "languages": null,
    "language_levels": null,
    "office_days": null,
    "remote_days": null,
    "remote_interview": false,
    "open_to_hire_ukrainians": false,
    "is_super_offer": false
  }
]

First 2 of 9 rows from a real run — sample_output.json. Long values shortened here.

Built for Just Join IT

Handles what breaks naive Just Join IT scrapers

The employer's salary, not a conversion

employmentTypes mixes the employer's figure with currency conversions, and the original is not first on 40% of detail records. The scraper selects on currencySource, never on position.

Quoted rate and monthly figure kept apart

Reading the adjacent keys gives "18,480 PLN per hour" for a 110-an-hour job. salary_min/max carry the quoted rate beside salary_unit; the monthly figure has its own salary_monthly_* columns.

The 10,000 cap is in the sidecar

Every query stops at 10,000 results. The sidecar records total_results, site_total, reachable_max and capped_by_site, and the planner stops at the ceiling instead of hitting the site's HTTP 500.

Only filters that actually filter

The endpoint silently ignores several parameter names. Only parameters measured to change the result count are exposed as flags, so a filter typo cannot pass for a filtered run.

Everything built in

Scraping infrastructure included

The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.

CAPTCHA handling

Detects the challenge a page really serves and, with a 2Captcha key, solves it only when it blocks the data — a capped number of solves per page.

Proxy rotation

Single proxy or a pool from a file, rotated on blocks with a fresh browser per exit. Credentials stay out of argv and logs.

Fingerprints

Optional complete device identity from the 2Captcha Fingerprint API — user agent, client hints, locale and timezone that agree with each other.

Scraping Browser API

Point --cdp-endpoint at a managed Chromium with its own exit country — no browser infrastructure to run yourself.

Honest run metadata

Every run writes a .meta.json sidecar and distinct exit codes: complete, blocked, empty and partial are never confused, and a failed run never overwrites good data.

Diff two runs

diff_runs.py compares two complete Just Join IT snapshots by id — new, removed and changed rows for monitoring.

Use cases

What teams do with Just Join IT data

IT salary benchmarking

Compare quoted and monthly salaries by category, seniority, city and contract type across the Polish market.

Skills demand analysis

Count required skills and their levels across thousands of offers to see which technologies employers ask for.

Recruitment lead lists

Track which companies are hiring, in which cities and for which roles, and diff daily runs for new postings.

Job board market sizing

Use facets mode's uncapped counts to size categories and plan full-board coverage.

Access & pricing

Free to run. Pay only for what the site requires.

Runs with no key, proxy or account

Every route this scraper reads was served in full to a bare datacentre address with no credentials, measured Sep 18 2026, and the daily canary runs from a GitHub runner with no secrets. A proxy buys volume and politeness for full-board runs; the Scraping Browser API buys a chosen exit country and no local Chromium, not access. No captcha appears on any route read, so a 2Captcha key buys nothing today.

Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.

Good to know

  • Any single query reaches at most 10,000 offers; split by category to cover the board.
  • --route ssr rows have a poorer schema: no category, languages or skill levels, and one page only.
  • Salaries are reproduced as employers type them, typos included.

2scraper vs. hosted scraper APIs

2scraperTypical hosted API
CodeOpen source, MIT — read and patch itClosed
CostFree; optional services billed only when usedSubscription or per request / per result
Runs onYour machine, server or CIThe vendor's infrastructure
OutputJSON/CSV files + run metadataAPI responses
When the site changesFix it yourself or open an issue / PRWait for the vendor
FAQ

Just Join IT scraper FAQ

Do I need a proxy or API key?

No. Listings, offers and facets were served to a datacentre address with no headers at all. Only a Python-urllib User-Agent is refused, which the scraper never sends.

Why do I never get more than 10,000 offers?

The site caps every query at 10,000 results while the board held 19,381 on 2026-09-18. Run --mode facets and split by category; 25 category runs covered the board then.

Why is salary null on many rows?

461 of 1,000 measured offers disclose no salary. The scraper writes null, never zero, and --with-salary filters them out at the site.

Does it respect robots.txt?

By default it reads the /api/ endpoint the site's own front end calls, which robots.txt disallows — the README says so plainly. --route ssr reads the rendered listing page instead, within robots.txt, at one page of 100 rows.

Does the sort order matter?

Yes: because queries are capped, the sort decides which offers you get. diff_runs.py refuses to compare runs with different sorts, orderings or filters.

Is it legal?

It reads public job offers and never submits an application. Follow the site's terms and the law where you operate.