← back to Sublease Agentabrams

crawl/firms/premierworkspaces.js

73 lines

'use strict';
// Premier Workspaces — flexible office / executive-suite operator (WordPress + JetEngine).
// Public location pages carry a LocalBusiness JSON-LD with a full PostalAddress; we parse
// that, geocode via the free Census API, and store each center as a flexible-space listing.
const LOCATIONS = 'https://premierworkspaces.com/locations/';

const titleCase = (s) => (s || '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());

function parseJsonLd(html) {
  const out = [];
  for (const m of html.matchAll(/<script[^>]+application\/ld\+json[^>]*>([\s\S]*?)<\/script>/g)) {
    try { const j = JSON.parse(m[1]); (j['@graph'] || [j]).forEach(n => out.push(n)); } catch { /* skip */ }
  }
  return out;
}

module.exports = {
  meta: { firm: 'Premier Workspaces', space_type: 'flex' },
  async crawl({ base, sponsor, upsert }) {
    const home = await base.httpGet(LOCATIONS, { timeout: 20000 });
    if (!home.ok) throw new Error('locations page ' + home.status);

    // Individual center pages: /locations/<state>/<city>/<center>/
    const links = [...new Set(
      [...home.text.matchAll(/href="(https:\/\/premierworkspaces\.com\/locations\/[a-z0-9\-]+\/[a-z0-9\-]+\/[a-z0-9\-]+\/)"/g)]
        .map(m => m[1]))].filter(u => !/\/view-all\//.test(u));

    // Focus on California first (the LA market); env override to widen.
    const onlyState = process.env.PW_STATE || 'california';
    const scoped = links.filter(u => u.includes(`/locations/${onlyState}/`));
    const cap = Number(process.env.PW_CAP || 30);
    const targets = (scoped.length ? scoped : links).slice(0, cap);

    let geocoded = 0;
    for (const url of targets) {
      const parts = url.split('/').filter(Boolean);        // .. locations, state, city, center
      const state = parts[parts.length - 3], city = titleCase(parts[parts.length - 2]);
      const centerName = titleCase(parts[parts.length - 1]);
      const page = await base.httpGet(url, { timeout: 18000 });
      let address = null, zip = null, image = null, lat = null, lng = null;
      if (page.ok) {
        for (const node of parseJsonLd(page.text)) {
          const addr = node.address;
          if (addr && (addr.streetAddress || addr['@type'] === 'PostalAddress')) {
            address = [addr.streetAddress, addr.addressLocality].filter(Boolean).join(', ');
            zip = addr.postalCode || null;
          }
          if (node.geo) { lat = Number(node.geo.latitude) || null; lng = Number(node.geo.longitude) || null; }
          if (!image && node.image) image = typeof node.image === 'string' ? node.image : node.image.url;
        }
      }
      const full = [address, `${titleCase(state)}`, zip].filter(Boolean).join(', ');
      if ((lat == null || lng == null) && (address || city)) {
        const g = await base.geocodeCensus(full || `${city}, ${titleCase(state)}`);
        if (g) { lat = g.lat; lng = g.lng; geocoded++; await base.sleep(300); }
      }
      await upsert({
        external_id: url.replace('https://premierworkspaces.com', '').replace(/\/$/, ''),
        title: `${centerName} — flexible office space`,
        address: address || null, city, state: state === 'california' ? 'CA' : titleCase(state), zip,
        lat, lng, space_type: 'flex', is_sublease: true,
        size_sf: null, price_amount: null, price_unit: 'call for terms',
        term: 'flexible (month-to-month & short-term)',
        source_url: url, image_url: image,
        description: `Premier Workspaces flexible workspace: private offices, coworking & meeting rooms at ${centerName}, ${city}.`,
        raw: { center: centerName, city, state },
      });
      await base.sleep(700); // polite
    }
    return { cost: 0, notes: `${targets.length} centers scanned, ${geocoded} geocoded (Census, $0)` };
  },
};