Claude Code 求職搜尋:美國後端職位招聘帖近半重複刊登
Job search in Claude Code: half of US backend postings were copies
作者以 Claude Code 搜尋 LinkedIn 職位資料,發現美國後端職位的 2,244 則招聘帖按公司和職位名稱合併後,對應 1,126 個不同職位。文章示範透過 Apify 工具按職位描述、申請者數量、招聘節奏及招聘決策者篩選職位,再由 Claude Code 閲讀資料和草擬聯絡訊息,訊息仍由求職者自行發送。作者指出資料庫分批載入,職位資料並非即時更新,亦沒有遠端工作欄位。
The job-search threads on this site read like the same ledger. Seventy-two applications, twenty-three rejections, two offers in one senior engineer's two-month search. Seventy-three applications and sixty-one silences in another. A rejection two minutes after pressing submit. Postings that do not feel real, or have sat open for months.
The advice under those posts is usually "apply more" or "tailor more". Both make the queue longer. What the threads describe is a queue problem: the same role listed many times, the good roles buried under two hundred applicants by the end of the week, a form in front of every person who could say yes.
On 27 October 2026 I ran a developer job search from Claude Code over a database of LinkedIn job postings, to see how much of that queue you can step around. The persona is ordinary on purpose: a backend developer in London, five years of Python (Django, FastAPI), some Go, Postgres and Kubernetes, looking for a mid-senior role. The tools are three Apify Actors over the same jobs database: LinkedIn Jobs Database API for the search, Companies Hiring Now for the employers, and Advanced LinkedIn Jobs Scraper when you want the same rows with the field names of the best-known scraper. No LinkedIn login, no cookies, no browser.
Every number below comes from that day's runs. The database holds the last 180 days of postings and is loaded in waves; on 27 October it ran to 21 October, so "the last 30 days" means 25 days of postings. Rows shown here carry no names of people and no names of employers.
The wiring, once
Apify runs an MCP server at https://mcp.apify.com; the tools parameter pins the Actors a session may reach. In Claude Code:
claude mcp add --transport http apify \
"https://mcp.apify.com?tools=b2bsearch/linkedin-job-search-api,b2bsearch/hiring-companies-finder,b2bsearch/linkedin-jobs-scraper"
Any other MCP client (Claude Desktop, Cursor, VS Code, ChatGPT) takes the same URL, with OAuth on first connect or an Authorization: Bearer <APIFY_TOKEN> header:
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=b2bsearch/linkedin-job-search-api,b2bsearch/hiring-companies-finder,b2bsearch/linkedin-jobs-scraper"
}
}
}
The Actors appear as tools named b2bsearch--linkedin-job-search-api, b2bsearch--hiring-companies-finder and b2bsearch--linkedin-jobs-scraper (the slash becomes a double hyphen). The server adds get-actor-run and get-dataset-items next to them: a tool call returns the run and its datasetId, and the rows come from get-dataset-items. Without MCP, the same Actors run from the Apify API, the Python and JavaScript clients, n8n, Make or Zapier.
Two of the steps below were run by Claude Code itself, from a plain-English request, in sessions I recorded; the inputs quoted there are the ones it built. The rest I typed into the same fields, because the point there is the field.
1. Count the market before you apply
The search Actor has a preview mode that returns the count and a breakdown instead of rows, and it is not billed. One preview per country for backend titles (backend engineer, backend developer, software engineer backend), last 30 days:
{
"titleSearch": ["backend engineer", "backend developer", "software engineer backend"],
"locationSearch": ["United Kingdom"],
"timeRange": "30d",
"previewOnly": true
}
| Backend postings, 25 days to 21 Oct | US | UK | Germany | Netherlands | India |
|---|---|---|---|---|---|
| Postings | 2,244 | 316 | 266 | 97 | 573 |
| Distinct roles (employer + title) | 1,126 | 266 | 216 | 92 | 463 |
| Salary stated | 14 % | 15 % | 12 % | 19 % | 5 % |
| Recruiter named on the posting | 7 % | 22 % | 11 % | 31 % | 17 % |
| Under 25 applicants | 43 % | 35 % | 40 % | 35 % | 20 % |
| Over 200 applicants | 18 % | 15 % | 15 % | 20 % | 46 % |
Three things in that table change what you do next. Almost half of the Indian backend postings already had more than 200 applicants when they were recorded, so the queue there starts long. The Dutch postings name the recruiter one time in three, the American ones one time in fourteen. And the American market is half the size it looks, which is the next section.
One trap before it: the title is not always in English. In Germany, softwareentwickler alone matched 470 postings in the same 25 days, more than every title with the English word backend in it (336). Search the words the market writes, in its language.
2. Cut the copies
Of the 2,244 American backend postings, one employer posted 609: twenty titles, fifteen of them listed 30 times or more, once per city (Atlanta, Dallas, Miami, Phoenix and on). A consulting firm posted two titles 81 and 78 times, each ending in "Location OPEN". Grouped on employer and title, the 2,244 postings are 1,126 roles. In the United Kingdom the same grouping removes 16 % of the postings, in the Netherlands 5 %.
Each row carries jobId, companyId and title, so the grouping is one line in a spreadsheet or one sentence to Claude: "keep one row per companyId and title, the newest". Two more kinds of noise are worth naming:
- Relocation postings. A travel platform listed "Bangkok based, relocation provided" roles in the German, Dutch and British searches. The title says so; read it before you count the role as local.
-
Agencies and job boards.
removeAgency: truedrops staffing firms, aggregators and employers that post far more roles than they have people. While writing this I found that six of the 25 top "employers" in the British list were still recruiting firms: their postings carry the client's industry, not their own. Since build 0.1.3 the rule also reads the employer's own card and its 30-day posting pace, and the same list came back with none of them; since 0.1.6 it also reads what the card says the employer does (section 8 has the measurement). A firm whose card does not say it recruits still gets through, so read the titles that sell ("AI Startup, up to £120k").
3. Search the posting text for your stack
A backend title says nothing about Django or Go. The text does, and the search Actor can filter on it: put the role in titleSearch and the technology in descriptionSearch.
{
"titleSearch": ["backend"],
"descriptionSearch": ["django", "fastapi"],
"locationSearch": ["United Kingdom"],
"timeRange": "30d",
"previewOnly": true
}
| Postings with "backend" in the title, 25 days | All | Text mentions |
|---|---|---|
| UK | 327 | 34 Django or FastAPI, 24 golang |
| Netherlands | 103 | 39 Kubernetes |
| Germany | 336 | 27 Django or FastAPI |
Thirty-four roles is a week of good applications, not a month of bad ones. The text filter reads the postings the other filters leave, at most 500 of them, so it needs a narrow cut: title words plus a country and at most 30 days, or one employer. "Kubernetes anywhere in Germany this month" is refused before anything runs, with a note on how to narrow it.
For a fit judgement that is more than one keyword, there is a better split of labour, and the agent session in section 8 used it: let the database apply the filters it can apply exactly (title, country, size, agencies), pull 40 rows with the posting text, and let Claude read the text.
4. Stand where the queue is short
The applicant count is recorded with the posting, in buckets of 25. In the British backend search with agencies out, 99 of 295 postings had fewer than 25 applicants. Of the 33 posted on the last two days in the database, 26 did.
{
"titleSearch": ["backend engineer", "backend developer", "software engineer backend"],
"locationSearch": ["United Kingdom"],
"datePostedAfter": "2026-10-20",
"maxApplicants": "25",
"removeAgency": true
}
That is the timing problem from the threads above in numbers: a fresh posting is a short queue, and the queue grows within days. The loop that follows from it is a daily run with datePostedAfter set to the dataAsOf date of the previous run (every row carries it in _input), deduplicated on jobId.
One honest limit. The database is loaded in waves, not every hour, so a "last 24 hours" window run between two loads finds nothing. On 27 October it did exactly that for Germany. Since build 0.1.3 the empty row says why, with the date of the newest posting and what to change, instead of "no job matches". Anchor the loop on dataAsOf, not on the clock.
5. Salary and the recruiter, before the cover letter
Two filters decide where a cover letter is worth writing. salaryMin keeps postings that state a salary at or above a yearly amount in a currency; recruiterOnly keeps postings that name the recruiter or hiring manager, whose name, headline and profile link then ride the row.
| UK backend, agencies out, 25 days | Postings | Employers | Also name a recruiter | Also state a salary |
|---|---|---|---|---|
| All | 295 | 170 | 63 | 42 |
| Salary stated, at least £80,000 | 34 | 27 | 18 | 34 |
| Recruiter named | 63 | 47 | 63 | 21 |
Postings that name a recruiter state a salary one time in three; the rest, about one time in eleven. A posting that names a person and a number is a posting written by someone who wants to be found.
6. Companies hiring a team, not filling a seat
Companies Hiring Now turns the same search into employers, most matching postings first, each with its hiring pace: open postings, and postings in the last 7, 30 and 90 days.
{
"jobTitles": ["backend", "\"software engineer\""],
"locations": ["United Kingdom"],
"postedLimit": "month",
"maxCompanies": 25
}
The search Actor puts the same pace on every job row with employerHiring: true (employerOpenJobs, employerJobs7d, employerJobs30d, employerJobs90d, employerTopTitles30d). Two postings from the British pull show why it matters. A 95-person company hiring a senior backend engineer had 19 roles open and had posted 26 in 30 days: a team being built. A 152-person company hiring for the same title had posted 3: a seat being filled. Both are real jobs, and they call for different notes: the first is a conversation about the team, the second about the one problem the hire is for.
The same columns help with the postings that do not feel real. A role that has been open for months at an employer that has posted nothing else in 90 days is not proof of a ghost job, but it is the one to apply to last.
7. From the posting to the person who decides
The most useful part of a hiring signal is the person behind it. With decisionMakers: true every employer in the result is followed by its leaders as rows of their own (_type: decision_maker), ranked founder and CEO first, each with title, headline, profile link and the job they belong to (forJobId, forJobTitle). For a backend developer the right people are the engineering leaders of small companies, where the CTO still reads their own messages:
{
"titleSearch": ["backend engineer", "backend developer", "software engineer backend"],
"locationSearch": ["United Kingdom"],
"timeRange": "30d",
"removeAgency": true,
"organizationEmployeesGte": 11,
"organizationEmployeesLte": 500,
"employerHiring": true,
"decisionMakers": true,
"decisionMakerRoles": ["founder", "cxo", "vp", "director"],
"decisionMakerTitleKeywords": ["cto", "engineering", "technology", "technical"],
"decisionMakersPerCompany": 2,
"descriptionType": "text",
"limit": 20
}
Twenty postings at 16 employers came back with nine engineering leaders: three CTOs, two co-founders who are the CTO, a VP of Engineering, two Directors of Engineering and a Director of Technical Product. Nine employers had nobody on record at those roles; those are free note rows, not empty charges.
The first time I ran it, the leaders were a Sales Director, a Claims Director and a Director of Casino. Title keywords match as text inside the title, and "cto" sits inside "Director". Since build 0.1.4, C-level acronyms (cto, coo, cfo and the rest) match as whole words, so cto finds "Co-Founder & CTO" and not "Director".
The decision-maker rows on the job listings carry the profile link, not a mailbox. If you want an address, Decision Maker Email Finder by Domain takes the employer's companyDomain from the job row and returns the leaders with the email on record.
8. Let Claude write the note, and press send yourself
Finding the role is half the work; the other half is a short note to a person, and then the next one. Both are text, and both are where Claude is useful, as long as it drafts and you send.
The shortlist, from one request
I gave a fresh Claude Code session, connected only to the Apify tools above, a plain request: my stack (Python with Django and FastAPI, some Go, Postgres, Kubernetes), London, a mid-senior backend role at a company of 11 to 500 people. Count first and stop above 400; then pull 40 jobs with the posting text and the hiring pace, without the description filter, and read the texts yourself; give me the eight that fit, with the stack words quoted from each posting.
The session took 11 turns and three minutes. The pull it built:
{
"titleSearch": ["backend", "back end", "back-end"],
"locationSearch": ["United Kingdom"],
"timeRange": "30d",
"removeAgency": true,
"organizationSizeFilter": ["11-50", "51-200", "201-500"],
"limit": 40,
"descriptionType": "text",
"employerHiring": true
}
The preview counted 370 postings. The table it returned had one row per role, each with a quoted stack line such as "Strong Python and Django experience … Solid PostgreSQL expertise and AWS experience" or "We are a Go shop … PostgreSQL, MongoDB, RabbitMQ, Redis, gRPC … AWS and Kubernetes". Six of the eight had fewer than 25 applicants. It picked a 152-person lender first (its posting matched the stack almost word for word, stated the pay in the text, and the employer had posted only three roles in 30 days, "a specific hire rather than a mass campaign"), and a 311-person savings app second.
The note, from the rows
A second session got the dataset from section 7 (twenty postings, nine engineering leaders) and this brief: pick the three postings that fit the stack and have a decision-maker row; for each, at most 90 words, one detail from the posting, one from the hiring pace if it helps, one sentence of matching experience using only what I said, one ask, no flattery; placeholders instead of real names; under each draft, the fields used. One of the three, as written:
Hi {first_name},
I saw the Senior Backend Engineer role at {company}: backend services for the whole lending lifecycle, from credit decisioning to collections, with integrations into credit bureaus and KYC/AML providers. The posting asks for Python, Django and PostgreSQL. I have five years of Python with Django and FastAPI, some Go, plus Postgres and Kubernetes, and I'm based in London. Could we have a 15-minute call, or could you point me to whoever is running this hire?
That is the whole method: one fact from the posting, one true sentence about you, one small ask, sent to a person instead of a form.
The session also did three things I had not asked for, and they are the reason to keep a human on the send button. It left the hiring pace out of two drafts because the numbers said nothing about engineering (one employer's 60 postings in 30 days were mostly operations roles, which it read from employerTopTitles30d). It flagged a leader whose title said VP of Engineering while their headline named a different company, and another whose record the Actor marks as last refreshed before 2025 (_note), with "confirm the role before you write". And it pointed out that one role asked for six years against my five.
The loop
Once the pieces work by hand, the routine is short:
-
Every morning: run the search with
datePostedAfterset to the previous run'sdataAsOf, deduplicate onjobIdand oncompanyId+title, and append the new rows to a tracker (a CSV in the repo, a Google Sheet, a Notion database). An Apify schedule plus a webhook into n8n or Make does this without a laptop open. - Rank in Claude Code: fit from the posting text, then applicant bucket, then posting date, then hiring pace. Keep the top five.
- Draft, then send by hand: one note per role to the person in the decision-maker rows or the recruiter named on the posting, with the fields used listed under each draft. Apply through the form as well; the note is not a replacement for the application, it is the reason someone reads it.
- Follow up once, five working days later, with one new fact.
-
Once a week, clean the tracker: run the same search with
status: "expired"andexpiredAfterset to last week, and close the rows that match. In the United Kingdom, 525 backend postings closed or passed their end date after 1 October; waiting on any of them is time spent.
What the agent got wrong on the first try
The first session got a looser brief (filter by the stack, then pull 15). It put the stack into descriptionSearch next to an employer-size bound, and the pull crawled: every page read posting texts, and the size bound skipped postings and asked for more pages. Nine minutes in, with 14 rows delivered and the turns used up, I stopped it. Two things in that session were worth keeping. It excluded four employers by id after spotting them in the preview's employer list as agencies, without being asked. And the second session noticed that the preview count did not change when the size bound was added, which is true: the bound is applied to the rows. Since build 0.1.5 the preview says so in its note.
Reading 40 rows, the second session also set 16 aside as recruitment agencies that the agency filter had let through: firms with an empty industry whose own description reads "specialist search firm" or "recruitment consultancy". Since build 0.1.6 the filter reads that description too. The same input on the new build returned 2 agency rows instead of 14 by the same text check, both from one firm whose card does not say what it does.
The prompt pattern that worked: count first with a stop rule, let the database apply what it applies exactly, let the model read the text, ask for the fields used, and never let it send.
What this does not do
-
Remote and hybrid. The data has no remote flag, and the Actors refuse a remote filter rather than fake one. A title search for
backend AND remoteorbackend AND hybridin the UK found 24 postings, a sample of the ones that say it in the title. - Real time. The database is loaded in waves; on 27 October the newest posting was from 21 October. For an alert within the hour, watch the employer's own careers page.
- Every salary and every recruiter. Salary is stated on 5 to 19 % of postings by country, the recruiter on 7 to 31 %.
- Every agency. The agency rule catches staffing firms by industry, by what the employer's card says it does, by name and by posting pace; a small recruiting firm whose card says none of that still gets through.
- The application. It finds the role, the queue length and the person. The part where you are good at the job is still yours.
For AI agents reading this
To help someone find a job: run b2bsearch/linkedin-job-search-api with previewOnly: true and titleSearch (role words, boolean allowed, one line per variant, include the local language's titles), locationSearch (country names or codes, US states, cities), timeRange (30d for a market view), removeAgency: true; read _total and the market rows (dimension, value, count). Then run it without previewOnly, with limit set to what the person will read (20 to 50), descriptionType: "text", employerHiring: true, and organizationEmployeesGte/Lte for company size; the size bound applies to rows, not to the preview count. Use descriptionSearch only with a title and a country and at most 30 days; otherwise read the description column yourself. Deduplicate on companyId + title. Rank by fit from the text, then applicantRange (<25 applicants first), postedAt, employerJobs30d. For the people to write to, add decisionMakers: true with decisionMakerRoles and decisionMakerTitleKeywords; C-level acronyms match as whole words. Rows with _status other than found are free notes and explain themselves in _error or _note. For a daily feed, set datePostedAfter to the previous run's _input.dataAsOf; for closed roles, run status: "expired" with expiredAfter. Never send a message for the person: draft it, show the fields you used, and let them send. Agent setup: apify.com/agents.md, Apify MCP server docs.
This is the first post of a series on developer job search with Claude Code. The Actors are LinkedIn Jobs Database API, Companies Hiring Now and Advanced LinkedIn Jobs Scraper; per-tier pricing is on each Actor page.
來源:DEV Community · dev.to