{ "name": "HN Who is Hiring Scrape", "tags": [], "nodes": [ { "id": "f7cdb3ee-9bb0-4006-829a-d4ce797191d5", "name": "When clicking ‘Test workflow’", "type": "n8n-nodes-base.manualTrigger", "position": [ -20, -220 ], "parameters": {}, "typeVersion": 1 }, { "id": "0475e25d-9bf4-450d-abd3-a04608a438a4", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [ 60, -620 ], "parameters": { "width": 460, "height": 340, "content": "## Find Who is hiring threads\n\nUse the public Hacker News Algolia date-sorted search API with the exact source phrase and story filter. No shared-index key or browser cURL is needed." }, "typeVersion": 1 }, { "id": "a686852b-ff84-430b-92bb-ce02a6808e19", "name": "Split Out", "type": "n8n-nodes-base.splitOut", "position": [ 400, -220 ], "parameters": { "options": {}, "fieldToSplitOut": "hits" }, "typeVersion": 1 }, { "id": "cdaaa738-d561-4fa0-b2c7-8ea9e6778eb1", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [ 1260, -620 ], "parameters": { "width": 500, "height": 340, "content": "## Go to HN API \nhttps://github.com/HackerNews/API\n\nWe'll need following endpoints: \n- For example, a story: https://hacker-news.firebaseio.com/v0/item/8863.json?print=pretty\n- comment: https://hacker-news.firebaseio.com/v0/item/2921983.json?print=pretty\n\n" }, "typeVersion": 1 }, { "id": "4f353598-9e32-4be4-9e7b-c89cc05305fd", "name": "OpenAI Chat Model", "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", "position": [ 2680, -20 ], "parameters": { "model": { "__rl": true, "mode": "id", "value": "YOUR_OPENAI_MODEL_ID" }, "options": { "maxRetries": 2 } }, "typeVersion": 1.3 }, { "id": "5bd0d7cc-497a-497c-aa4c-589d9ceeca14", "name": "Structured Output Parser", "type": "@n8n/n8n-nodes-langchain.outputParserStructured", "position": [ 2840, -20 ], "parameters": { "schemaType": "manual", "inputSchema": "{\"type\":\"object\",\"properties\":{\"company\":{\"type\":[\"string\",\"null\"],\"description\":\"Name of the hiring company\"},\"title\":{\"type\":[\"string\",\"null\"],\"description\":\"Job title/role being advertised\"},\"location\":{\"type\":[\"string\",\"null\"],\"description\":\"Work location including remote/hybrid status\"},\"type\":{\"type\":[\"string\",\"null\"],\"description\":\"Employment type (Full-time, Contract, etc)\",\"enum\":[\"FULL_TIME\",\"PART_TIME\",\"CONTRACT\",\"INTERNSHIP\",\"FREELANCE\",null]},\"work_location\":{\"type\":[\"string\",\"null\"],\"description\":\"Work arrangement type\",\"enum\":[\"REMOTE\",\"HYBRID\",\"ON_SITE\",null]},\"salary\":{\"type\":[\"string\",\"null\"],\"description\":\"Compensation details if provided\"},\"description\":{\"type\":[\"string\",\"null\"],\"description\":\"Main job description text including requirements and team info\"},\"apply_url\":{\"type\":[\"string\",\"null\"],\"description\":\"Direct application/job posting URL\"},\"company_url\":{\"type\":[\"string\",\"null\"],\"description\":\"Company website or careers page\"}},\"additionalProperties\":false}" }, "typeVersion": 1.2 }, { "id": "b84ca004-6f3b-4577-8910-61b8584b161d", "name": "Search for Who is hiring posts", "type": "n8n-nodes-base.httpRequest", "position": [ 200, -220 ], "parameters": { "method": "GET", "url": "https://hn.algolia.com/api/v1/search_by_date", "sendQuery": true, "queryParameters": { "parameters": [ { "name": "query", "value": "\"Ask HN: Who is hiring\"" }, { "name": "tags", "value": "story" }, { "name": "page", "value": "0" }, { "name": "hitsPerPage", "value": "30" }, { "name": "advancedSyntax", "value": "true" }, { "name": "restrictSearchableAttributes", "value": "title,comment_text,url,story_text,author" } ] }, "options": { "timeout": 30000, "response": { "response": { "responseFormat": "json" } } } }, "typeVersion": 4.2 }, { "id": "205e66f6-cd6b-4cfd-a6ec-2226c35ddaac", "name": "Get relevant data", "type": "n8n-nodes-base.set", "position": [ 700, -220 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "73dd2325-faa7-4650-bd78-5fc97cc202de", "name": "title", "type": "string", "value": "={{ $json.title }}" }, { "id": "44918eac-4510-440e-9ac0-bf14d2b2f3af", "name": "createdAt", "type": "string", "value": "={{ $json.created_at }}" }, { "id": "00eb6f09-2c22-411c-949c-886b2d95b6eb", "name": "updatedAt", "type": "string", "value": "={{ $json.updated_at }}" }, { "id": "2b4f9da6-f60e-46e0-ba9d-3242fa955a55", "name": "storyId", "type": "string", "value": "={{ $json.objectID }}" } ] } }, "typeVersion": 3.4 }, { "id": "16bc5628-8a29-4eac-8be9-b4e9da802e1e", "name": "Get latest post", "type": "n8n-nodes-base.filter", "position": [ 900, -220 ], "parameters": { "options": {}, "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "and", "conditions": [ { "id": "d7dd7175-2a50-45aa-bd3e-4c248c9193c4", "operator": { "type": "dateTime", "operation": "after" }, "leftValue": "={{ $json.createdAt }}", "rightValue": "={{$now.minus({days: 30})}} " } ] } }, "typeVersion": 2.2 }, { "id": "92e1ef74-5ae1-4195-840b-115184db464f", "name": "Split out children (jobs)", "type": "n8n-nodes-base.splitOut", "position": [ 1460, -220 ], "parameters": { "options": {}, "fieldToSplitOut": "kids" }, "typeVersion": 1 }, { "id": "d0836aae-b98a-497f-a6f7-0ad563c262a0", "name": "Trun into structured data", "type": "@n8n/n8n-nodes-langchain.chainLlm", "position": [ 2600, -220 ], "parameters": { "text": "={{ $json.cleaned_text }}", "messages": { "messageValues": [ { "type": "SystemMessagePromptTemplate", "message": "Extract the JSON data" } ] }, "promptType": "define", "hasOutputParser": true }, "typeVersion": 1.7 }, { "id": "fd818a93-627c-435d-91ba-5d759d5a9004", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [ 2600, -620 ], "parameters": { "width": 840, "height": 340, "content": "## Data Structure\n\nWe use Openai GPT-4o-mini to transform the raw data in a unified data structure. Feel free to change this.\n\n```json\n{\n \"company\": \"Name of the hiring company\",\n \"title\": \"Job title/role being advertised\",\n \"location\": \"Work location including remote/hybrid status\",\n \"type\": \"Employment type (Full-time, Contract, etc)\",\n \"salary\": \"Compensation details if provided\",\n \"description\": \"Main job description text including requirements and team info\",\n \"apply_url\": \"Direct application/job posting URL\",\n \"company_url\": \"Company website or careers page\"\n}\n```" }, "typeVersion": 1 }, { "id": "b70c5578-5b81-467a-8ac2-65374e4e52f3", "name": "Extract text", "type": "n8n-nodes-base.set", "position": [ 1860, -220 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "6affa370-56ce-4ad8-8534-8f753fdf07fc", "name": "text", "type": "string", "value": "={{ $json.text }}" } ] } }, "typeVersion": 3.4 }, { "id": "acb68d88-9417-42e9-9bcc-7c2fa95c4afd", "name": "Clean text", "type": "n8n-nodes-base.code", "position": [ 2060, -220 ], "parameters": { "jsCode": "function cleanHiringText(text){\n if(typeof text!=='string'||!text.trim())throw new Error('Hiring comment has no text.');\n return text.replace(///g,'/').replace(/'/g,\"'\").replace(/&\\w+;/g,' ').replace(/<[^>]*>/g,'').replace(/\\|\\s*/g,'| ').replace(/\\s+/g,' ').replace(/\\s*(https?:\\/\\/[^\\s]+)\\s*/g,'\\n$1\\n').replace(/\\n{3,}/g,'\\n\\n').trim();\n}\n\nreturn $input.all().map((item,index)=>({json:{cleaned_text:cleanHiringText(item.json.text)},pairedItem:{item:index}}));" }, "typeVersion": 2 }, { "id": "a0727b55-565d-47c0-9ab5-0f001f4b9941", "name": "Limit for testing (optional)", "type": "n8n-nodes-base.limit", "position": [ 2280, -220 ], "parameters": { "maxItems": 5 }, "typeVersion": 1 }, { "id": "650baf5e-c2ac-443d-8a2b-6df89717186f", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [ 580, -620 ], "parameters": { "width": 540, "height": 340, "content": "## Clean the result \n\n```json\n{\n\"title\": \"Ask HN: Who is hiring? (February 2025)\",\n\"createdAt\": \"2025-02-03T16:00:43Z\",\n\"updatedAt\": \"2025-02-17T08:35:44Z\",\n\"storyId\": \"42919502\"\n},\n{\n\"title\": \"Ask HN: Who is hiring? (January 2025)\",\n\"createdAt\": \"2025-01-02T16:00:09Z\",\n\"updatedAt\": \"2025-02-13T00:03:24Z\",\n\"storyId\": \"42575537\"\n},\n```" }, "typeVersion": 1 }, { "id": "1ca5c39f-f21d-455a-b63a-702e7e3ba02b", "name": "Write results to airtable", "type": "n8n-nodes-base.airtable", "position": [ 3040, -220 ], "parameters": { "base": { "__rl": true, "mode": "id", "value": "YOUR_AIRTABLE_BASE_ID" }, "table": { "__rl": true, "mode": "id", "value": "YOUR_AIRTABLE_TABLE_ID" }, "columns": { "value": { "type": "={{ $json.output.type }}", "title": "={{ $json.output.title }}", "salary": "={{ $json.output.salary }}", "company": "={{ $json.output.company }}", "location": "={{ $json.output.location }}", "apply_url": "={{ $json.output.apply_url }}", "company_url": "={{ $json.output.company_url }}", "description": "={{ $json.output.description }}" }, "schema": [ { "id": "title", "type": "string", "display": true, "removed": false, "readOnly": false, "required": false, "displayName": "title", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "company", "type": "string", "display": true, "removed": false, "readOnly": false, "required": false, "displayName": "company", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "location", "type": "string", "display": true, "removed": false, "readOnly": false, "required": false, "displayName": "location", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "type", "type": "string", "display": true, "removed": false, "readOnly": false, "required": false, "displayName": "type", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "salary", "type": "string", "display": true, "removed": false, "readOnly": false, "required": false, "displayName": "salary", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "description", "type": "string", "display": true, "removed": false, "readOnly": false, "required": false, "displayName": "description", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "apply_url", "type": "string", "display": true, "removed": false, "readOnly": false, "required": false, "displayName": "apply_url", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "company_url", "type": "string", "display": true, "removed": false, "readOnly": false, "required": false, "displayName": "company_url", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "posted_date", "type": "string", "display": true, "removed": true, "readOnly": false, "required": false, "displayName": "posted_date", "defaultMatch": false, "canBeUsedToMatch": true } ], "mappingMode": "defineBelow", "matchingColumns": [], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {}, "operation": "create" }, "typeVersion": 2.1 }, { "id": "d71fa024-86a0-4f74-b033-1f755574080c", "name": "Sticky Note4", "type": "n8n-nodes-base.stickyNote", "position": [ -520, -300 ], "parameters": { "width": 380, "height": 500, "content": "## Hacker News - Who is Hiring Scrape\n\nIn this template we setup a scraper for the monthly HN Who is Hiring post. This way we can scrape the data and transform it to a common data strcutre.\n\nFirst we use the [Algolia Search](https://hn.algolia.com/) provided by hackernews to drill down the results.\n\nWe can use the official [Hacker News API](https://github.com/HackerNews/API\n) to get the post data and also all the replies!\n\nThis will obviously work for any kind of post on hacker news! Get creative 😃\n\nAll you need is an Openai Account to structure the text data and an Airtable Account (or similar) to write the results to a list." }, "typeVersion": 1 }, { "id": "7466fb0c-9f0c-4adf-a6de-b2cf09032719", "name": "HI API: Get the individual job post", "type": "n8n-nodes-base.httpRequest", "position": [ 1660, -220 ], "parameters": { "url": "=https://hacker-news.firebaseio.com/v0/item/{{ $json.kids }}.json?print=pretty", "options": { "response": { "response": { "responseFormat": "json" } } } }, "typeVersion": 4.2 }, { "id": "184abccf-5838-49bf-9922-e0300c6b145e", "name": "HN API: Get Main Post", "type": "n8n-nodes-base.httpRequest", "position": [ 1260, -220 ], "parameters": { "url": "=https://hacker-news.firebaseio.com/v0/item/{{ $json.storyId }}.json?print=pretty", "options": { "response": { "response": { "responseFormat": "json" } } } }, "typeVersion": 4.2 } ], "active": false, "pinData": {}, "settings": { "executionOrder": "v1" }, "connections": { "Split Out": { "main": [ [ { "node": "Get relevant data", "type": "main", "index": 0 } ] ] }, "Clean text": { "main": [ [ { "node": "Limit for testing (optional)", "type": "main", "index": 0 } ] ] }, "Extract text": { "main": [ [ { "node": "Clean text", "type": "main", "index": 0 } ] ] }, "Get latest post": { "main": [ [ { "node": "HN API: Get Main Post", "type": "main", "index": 0 } ] ] }, "Get relevant data": { "main": [ [ { "node": "Get latest post", "type": "main", "index": 0 } ] ] }, "OpenAI Chat Model": { "ai_languageModel": [ [ { "node": "Trun into structured data", "type": "ai_languageModel", "index": 0 } ] ] }, "HN API: Get Main Post": { "main": [ [ { "node": "Split out children (jobs)", "type": "main", "index": 0 } ] ] }, "Structured Output Parser": { "ai_outputParser": [ [ { "node": "Trun into structured data", "type": "ai_outputParser", "index": 0 } ] ] }, "Split out children (jobs)": { "main": [ [ { "node": "HI API: Get the individual job post", "type": "main", "index": 0 } ] ] }, "Trun into structured data": { "main": [ [ { "node": "Write results to airtable", "type": "main", "index": 0 } ] ] }, "Limit for testing (optional)": { "main": [ [ { "node": "Trun into structured data", "type": "main", "index": 0 } ] ] }, "Search for Who is hiring posts": { "main": [ [ { "node": "Split Out", "type": "main", "index": 0 } ] ] }, "When clicking ‘Test workflow’": { "main": [ [ { "node": "Search for Who is hiring posts", "type": "main", "index": 0 } ] ] }, "HI API: Get the individual job post": { "main": [ [ { "node": "Extract text", "type": "main", "index": 0 } ] ] } } }