I’m looking to create a scraper that scrapes my gmail account once a day getting multiple listings from a single email. I’ve managed to set up a workflow that extracts the listings as a JSON array like this (this is an example of a single email):
[\n {\n \“job_title\”: \“SWM-2026-01 - Development of Municipal Wayfinding and Community Signage Strategy\”,\n \“organization\”: \“County of Middlesex\”,\n \“deadline\”: \“02-27-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“05-PRC-2026 - Washroom Renovation - Victoria Park\”,\n \“organization\”: \“Town of Cobourg\”,\n \“deadline\”: \“03-10-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“26-T15 - Engineering Services for Heat Pump Replacement at Various Housing Services Division Properties\”,\n \“organization\”: \“City of Cornwall\”,\n \“deadline\”: \“03-11-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“RFPQ-2026-051 - Elgin Terminal Reservoir Expansion\”,\n \“organization\”: \“City of London\”,\n \“deadline\”: \“03-12-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“2026-004 - Arboriculture Services Preferred Supplier Agreement\”,\n \“organization\”: \“Sturgeon County\”,\n \“deadline\”: \“03-12-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“FIN2026-076Q - 2026 Pavement Management Contract Administration and Contract Inspection Services\”,\n \“organization\”: \“City of Barrie\”,\n \“deadline\”: \“03-17-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“P26-004 - Insurance Broker of Record\”,\n \“organization\”: \“City of Prince George\”,\n \“deadline\”: \“03-19-2026\”,\n \“link\”: null\n }\n]"
However, I’m having some difficulty splitting up each array into individual listings that populate into a google sheet. I’m pretty new to all this so any help or advice is greatly appreciated!
Here are my workflow and code for the Parse Job Listings node:
// 1) Grab the AI output from the incoming item
const raw = $input.first().json?.[0] ?? $input.first().json ?? $input.first().json?.text ?? $input.first().json?.output;
// 2) Normalize to string
let text = (typeof raw === ‘string’) ? raw : JSON.stringify(raw ?? ‘’);
// 3) Strip markdown code fences (json ... )
text = text
.replace(/json\s*/gi, '') .replace(//g, ‘’)
.trim();
// 4) Extract the most likely JSON chunk (array preferred)
function extractJsonCandidate(s) {
const a0 = s.indexOf(‘[’);
const a1 = s.lastIndexOf(‘]’);
if (a0 !== -1 && a1 !== -1 && a1 > a0) return s.slice(a0, a1 + 1);
const o0 = s.indexOf(‘{’);
const o1 = s.lastIndexOf(‘}’);
if (o0 !== -1 && o1 !== -1 && o1 > o0) return s.slice(o0, o1 + 1);
return s;
}
const candidate = extractJsonCandidate(text);
// 5) Parse with a small fix for trailing commas
function parseLooseJson(s) {
try {
return JSON.parse(s);
} catch (e) {
// remove trailing commas before } or ]
const fixed = s.replace(/,\s*([}]])/g, ‘$1’);
return JSON.parse(fixed);
}
}
let parsed;
try {
parsed = parseLooseJson(candidate);
} catch (error) {
// If parsing fails, return one item with error info (won’t break the workflow)
return [{
json: {
error: ‘Failed to parse AI output as JSON’,
raw_output: text,
message: error.message
}
}];
}
// 6) Unwrap common wrapper shapes: { output: … } or { “0”: … }
function unwrap(v) {
let x = v;
// sometimes output itself is a JSON string
if (typeof x === ‘string’) x = parseLooseJson(extractJsonCandidate(x));
// unwrap a few layers if needed
for (let i = 0; i < 3; i++) {
if (x && typeof x === ‘object’ && !Array.isArray(x)) {
if (x.output) { x = x.output; continue; }
if (x.jobs) { x = x.jobs; continue; }
if (x.data) { x = x.data; continue; }
if (x[“0”]) { x = x[“0”]; continue; }
}
break;
}
return x;
}
let jobs = unwrap(parsed);
// 7) Ensure array
if (!Array.isArray(jobs)) jobs = [jobs];
// 8) Map to ONLY the 3 fields required by Google Sheets node
// Support multiple possible key names from your AI: job_title, job title, title, etc.
function pickJobTitle(j) {
return j?.job_title ?? j?.jobTitle ?? j?.[‘job title’] ?? j?.title ?? j?.job_name ?? ‘’;
}
function pickOrganization(j) {
return j?.organization ?? j?.org ?? j?.agency ?? j?.company ?? ‘’;
}
function pickDeadline(j) {
return j?.deadline ?? j?.due_date ?? j?.dueDate ?? j?.closing_date ?? ‘’;
}
return jobs
.filter(j => j && typeof j === ‘object’)
.map(j => ({
json: {
job_title: String(pickJobTitle(j)).trim(),
organization: String(pickOrganization(j)).trim(),
deadline: String(pickDeadline(j)).trim()
}
}));
