← back to Sublease Agentabrams

crawl/run.js

55 lines

'use strict';
// CRUnifiedDB crawl dispatcher.
//   node crawl/run.js <slug>        run one firm agent
//   node crawl/run.js all           run every crawl-enabled, low/medium-ToS firm
//   node crawl/run.js <slug> --force run even a high-ToS firm (LoopNet/CoStar) — you accept the risk
const fs = require('fs');
const path = require('path');
const base = require('./base-crawler');

const FIRMS_DIR = path.join(__dirname, 'firms');
const force = process.argv.includes('--force');
const arg = process.argv[2];

function firmModule(slug) {
  const f = path.join(FIRMS_DIR, `${slug}.js`);
  return fs.existsSync(f) ? require(f) : null;
}

async function runOne(slug) {
  const sponsor = await base.getSponsor(slug);
  if (!sponsor) { console.log(`✗ ${slug}: not a sponsor in CRUnifiedDB`); return; }
  if (sponsor.role === 'financing_partner') { console.log(`— ${slug}: financing partner (no listings to crawl)`); return; }
  const mod = firmModule(slug);
  if (!mod || typeof mod.crawl !== 'function') { console.log(`— ${slug}: no crawler module yet (crawl/firms/${slug}.js)`); return; }
  if (sponsor.tos_risk === 'high' && !force) {
    console.log(`⚠ ${slug}: ToS risk = HIGH (${sponsor.notes || ''}). Skipped. Re-run with --force to accept the risk.`);
    return;
  }
  const runId = await base.startRun(sponsor.id);
  console.log(`▶ ${slug}: crawling ${sponsor.listings_url || sponsor.website} …`);
  let found = 0, added = 0, cost = 0;
  try {
    const res = await mod.crawl({ base, sponsor,
      upsert: async (L) => { found++; if (await base.upsertListing(sponsor.id, L)) added++; } });
    cost = res?.cost || 0;
    await base.finishRun(runId, { status: 'ok', found, added, cost, notes: res?.notes || '' });
    console.log(`✓ ${slug}: ${found} found, ${added} new  ($${cost.toFixed(2)})`);
  } catch (e) {
    await base.finishRun(runId, { status: 'error', found, added, cost, notes: String(e).slice(0, 400) });
    console.log(`✗ ${slug}: ${e}`);
  }
}

(async () => {
  if (!arg) { console.log('usage: node crawl/run.js <slug|all> [--force]'); process.exit(1); }
  if (arg === 'all') {
    const { rows } = await base.pool.query(
      `SELECT slug FROM sponsors WHERE crawl_enabled AND role='broker' AND tos_risk <> 'high' ORDER BY slug`);
    for (const r of rows) await runOne(r.slug);
  } else {
    await runOne(arg);
  }
  await base.pool.end();
})();