← back to Rentv 2026
PR intel: better editorial-contact extraction for news publications
ccbda3ed358228ffd44fdf5f58f9e6b5dcd40e12 · 2026-08-05 13:21:43 -0700 · Steve
- team-page discovery now finds mastheads / editorial-team / contributors
pages (news sites have no /team page)
- TITLE_HINT recognizes editorial titles (editor/reporter/writer/publisher/
columnist/correspondent) so publication staff are captured, not skipped
- headline guard: reject name/title pairs that read like article headlines
(Hires/Names/Expands/... + $/sf/number patterns) and geographic/section
words as names — kills the 'New York City / Arrow ... Hires' class of noise
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M src/pr/adapters/website.js
Diff
commit ccbda3ed358228ffd44fdf5f58f9e6b5dcd40e12
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 5 13:21:43 2026 -0700
PR intel: better editorial-contact extraction for news publications
- team-page discovery now finds mastheads / editorial-team / contributors
pages (news sites have no /team page)
- TITLE_HINT recognizes editorial titles (editor/reporter/writer/publisher/
columnist/correspondent) so publication staff are captured, not skipped
- headline guard: reject name/title pairs that read like article headlines
(Hires/Names/Expands/... + $/sf/number patterns) and geographic/section
words as names — kills the 'New York City / Arrow ... Hires' class of noise
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
src/pr/adapters/website.js | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/pr/adapters/website.js b/src/pr/adapters/website.js
index cf5db05e..080cf0dd 100644
--- a/src/pr/adapters/website.js
+++ b/src/pr/adapters/website.js
@@ -7,7 +7,7 @@ const { normalizeDomain, normalizeEmail } = require('../lib/normalize');
const PAGE_HINTS = {
press: /news(room)?|press|media(-|\s)?(center|centre|room|contact|kit)|announcements/i,
- team: /team|people|leadership|our-people|professionals|staff|about-us\/(team|people|leadership)|management/i,
+ team: /masthead|editorial[\s-]?(team|staff|board)|contributors|our-writers|meet-the-team|team|people|leadership|our-people|our-team|professionals|staff|who-we-are|about-us\/(team|people|leadership|staff|masthead)|management/i,
contact: /contact/i,
// Media/PR outreach pages (publications, trade media, association media):
submission: /submit|press[\s-]?release|news[\s-]?tip|share[\s-]?(your[\s-]?)?news|send[\s-]?(us[\s-]?)?news|contribute|editorial[\s-]?submission|for[\s-]?(the[\s-]?)?press|story[\s-]?ideas?/i,
@@ -36,7 +36,7 @@ function extractLinks(html, baseUrl) {
}
/** Titles worth extracting from team pages (kept intentionally broad; classify later). */
-const TITLE_HINT = /officer|director|president|principal|partner|manager|vp|vice president|head of|chief|founder|lead|communications|marketing|media|escrow|lending|relations/i;
+const TITLE_HINT = /officer|director|president|principal|partner|manager|vp|vice president|head of|chief|founder|lead|communications|marketing|media|escrow|lending|relations|editor|editorial|reporter|writer|journalist|correspondent|columnist|publisher|contributor|newsroom/i;
/** Extract (name, title) candidate pairs from a team/leadership page. Conservative:
* requires a plausible Name-Case name adjacent to a title-looking string. */
@@ -53,14 +53,19 @@ function extractPeople(html) {
// Name-Case headings that are NOT names ("Crisis Communications", "Dream Job") — any
// word from this list disqualifies the candidate. Real ambiguity still lands in
// needs_verification for human review; this just cuts the obvious junk.
- const NOT_NAME = /\b(job|jobs|career|careers|communication|communications|media|crisis|digital|social|marketing|advisor|advisors|strategy|strategies|services|solutions|relations|estate|group|team|about|contact|office|offices|leadership|management|partners|clients|projects|news|insights|events|home|search|menu|privacy|policy|our|the|and)\b/i;
+ const NOT_NAME = /\b(job|jobs|career|careers|communication|communications|media|crisis|digital|social|marketing|advisor|advisors|strategy|strategies|services|solutions|relations|estate|group|team|about|contact|office|offices|leadership|management|partners|clients|projects|news|insights|events|home|search|menu|privacy|policy|our|the|and|york|city|angeles|francisco|diego|health|care|county)\b/i;
+ // On news/publication sites a "title" line is often an article HEADLINE, not a role
+ // (e.g. "Arrow Real Estate Advisors Hires Jeff Wesley"). Reject pairs whose name or
+ // title reads like a headline — a real job title never contains these verbs.
+ const HEADLINE = /\b(hires?|names?|appoints?|promotes?|announces?|expands?|launches?|acquires?|sells?|buys?|opens?|joins?|closes?|signs?|completes?|leases?|inks?|secures?|welcomes?|taps?|raises?|unveils?|debuts?|breaks? ground)\b/i;
+ const isHeadline = (n, t) => HEADLINE.test(n) || HEADLINE.test(t) || /\$\d|\d{3,}|\bsf\b|square feet/i.test(t);
// single-line variant: "Jane Smith, Senior Vice President" / "Jane Smith – CFO" /
// "Jane Smith | Chief Credit Officer" — common on small-bank officer pages (cycle 4:
// 36 team pages yielded 0 because name+title share one line).
const INLINE_RE = /^([A-Z][a-z]+(?:\s+[A-Z]\.?)?(?:\s+[A-Z][a-z'’-]+){1,2})\s*[,|–—-]\s+(.{3,90})$/;
for (let i = 0; i < lines.length; i++) {
const inline = lines[i].match(INLINE_RE);
- if (inline && !NOT_NAME.test(inline[1]) && TITLE_HINT.test(inline[2])) {
+ if (inline && !NOT_NAME.test(inline[1]) && TITLE_HINT.test(inline[2]) && !isHeadline(inline[1], inline[2])) {
people.push({ full_name: inline[1], exact_title: inline[2].trim() });
if (people.length >= 60) break;
continue;
@@ -71,7 +76,7 @@ function extractPeople(html) {
for (let j = 1; j <= 2 && i + j < lines.length; j++) {
const cand = lines[i + j];
if (cand.length > 90 || NAME_RE.test(cand)) break;
- if (TITLE_HINT.test(cand)) { people.push({ full_name: nm[1], exact_title: cand }); break; }
+ if (TITLE_HINT.test(cand) && !isHeadline(nm[1], cand)) { people.push({ full_name: nm[1], exact_title: cand }); break; }
}
if (people.length >= 60) break; // sane cap per page
}
← f6d63f54 Audience: surface the 2,162-contact pr-intelligence CRM (was
·
back to Rentv 2026
·
Audience: CRM contacts drill into their pr-intelligence pers 8c7c2767 →