# PR Newswire Personnel Scraper — Executive Moves (`scrapersdelight/prnewswire-personnel-scraper`) Actor

Scrape PR Newswire's personnel-announcement feed into executive-move rows: company, publication date, signal type (appointment, promotion, board, departure) and, where the release text states it plainly, the executive's name and new title. No login.

- **URL**: https://apify.com/scrapersdelight/prnewswire-personnel-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, News, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 per announcement returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 📣 PR Newswire Personnel Scraper — executive moves as structured rows

Turn PR Newswire's **personnel-announcements feed** into a job-change signal. One row per
announcement: **company, publication date, signal type** (appointment, promotion, board seat,
departure), the headline, the release URL, the company's own domain — and, where the release text
states it plainly, **the executive's name and new title**.

A newly appointed CxO changes vendors in their first 90 days. This is the open version of that
signal.

### What does this actor do?

It walks PR Newswire's personnel subject list, 100 releases per page. The list advertises ~13,700
releases but PR Newswire stops paginating at 10,000 results, which is as deep as any scraper can go
— roughly the last 9 months. It opens each release and returns:

- 🏢 **`company`** — the issuing company, taken from the release's own `SOURCE` line
- 🌐 **`companyDomain`** — the company's website domain, so the row joins to your CRM
- 📅 **`datePublished`** — exact publication timestamp
- 🏷️ **`signalType`** — `appointment` · `promotion` · `board-appointment` · `departure` · `transition`
- 👤 **`person`** — the executive, validated (see below) · 💼 **`title`** — the role text as the
  release words it. `person` is validated; `title` is not, so about 1 in 5 titles carries a trailing
  clause ("…, effective September 14") or is cut mid-phrase. Treat it as a hint, not a clean field.
- 🧭 **`parsedFrom`** — `release-text` or `headline`, so you know how the name was obtained
- 📰 **`headline`**, **`releaseUrl`**, **`listedAt`**, **`dateline`**
- 📝 **`releaseSummary`** — the first 400 characters of the release, currently prefixed with a stray
  `release-body container ">` marker

### Read this before you buy: what actually parses

Being straight about this, because it decides whether the actor is useful to you.

| Field | Fill | Note |
|---|---|---|
| `headline`, `releaseUrl`, `listedAt` | **100%** | straight from the list page |
| `datePublished` | **100%** | from the release page — see note |
| `company` | **94%** | from the release's `SOURCE` line; names containing periods ("The J. M. Smucker Company") can truncate |
| `signalType` | **92%** | classified from the announcement wording |
| `companyDomain` | **71-78%** | the company's own site, not a wire domain; taken from the first non-wire URL in the release, so occasionally it is a partner's domain |
| `dateline` | ~50% | city/state where the release was issued |
| **`person` + `title`** | **30-45%** | varies with the week's release mix — see below |

Everything below `listedAt` comes from opening the release. Leave the `fetchReleaseText` input on
(the default); if a release page cannot be read, the row still ships and is still charged with those
fields empty.

**The executive name parses on a minority of releases, and that is deliberate.** Release text
states a move in a regular way ("…today announced the appointment of Travis Hendren as Chief
Executive Officer") and that parses cleanly. Headlines do not: a naive headline parser turned
"UT Health San Antonio names NIH expert inaugural director of…" into the person *"NIH expert
inaugural"*. So this actor parses the release body first, uses only strict headline patterns as a
fallback, and rejects any candidate containing a lowercase word.

**It emits `null` for `person` rather than a guess.** Every candidate name must pass a validator
that rejects any phrase containing a lowercase word, which is what stops "NIH expert inaugural"
becoming a person. A wrong executive name is worse than a missing one. Note the same validation is
not applied to `title`.

If you only want rows with a confirmed person, set `requirePerson` — you will get the 30-45% of the
feed that parses, and you are not charged for the rows that are dropped.

**What you still get on the rest of the rows:** company, domain, date, signal type and the headline
— which usually names the person in plain English for a human or an LLM to read.

### Example input

```json
{
  "feed": "personnel",
  "keywords": ["CEO", "Chief Executive", "CFO"],
  "requirePerson": true,
  "maxItems": 500
}
```

### Who is this for?

Executive-search firms, GTM teams who sell to newly appointed leaders, vendors tracking buying-
committee change, and anyone building a job-change signal without paying per-contact for one.

### Honest limits

- **No media-contact emails.** PR Newswire does not publish contact blocks on the public web
  release — we checked releases across four subject lists and found none. If you need journalist
  contacts, this source cannot give them to you and neither can any other actor reading it.
- **The personnel list is not purely personnel.** Some releases in it are funding or expansion news;
  `signalType` is null on those and they are easy to filter out.
- **One release can announce several people.** Only the first parsed executive is returned.
- **The same move can appear as several rows.** PR Newswire re-posts releases as French/German/
  Spanish translations and sometimes twice in English, each under its own URL — so each is a
  separate row and a separate charge. Roughly 3-7% of the feed. The person/title parser is
  English-only, so those rows come back with `person` empty. Dedupe on `headline` or on the person
  if you only want unique moves.
- **`company` is the issuing organisation**, which for an agency-issued release is occasionally the
  agency rather than the employer.

### Pricing

| What | Price |
|---|---|
| Per announcement returned | **$0.004** |

You are charged only for rows delivered — filtered rows cost nothing.

### FAQ

**How far back does the feed go?**
About 10,000 releases — roughly the last 9 months. The list advertises ~13,700 but PR Newswire
refuses to paginate past the 10,000th result, so that is the hard ceiling regardless of `maxItems`.

**Can I run it daily for new moves only?**
Yes — sort order is newest-first, so a small `maxItems` each day gives you the latest.

**Why is `person` empty on some rows?**
The release text did not state the move in a form that could be parsed with confidence. The actor
returns null rather than risk a wrong name. The headline is still there.

**Can I filter to CEOs only?**
Put `CEO` and `Chief Executive` in `keywords` — the filter runs on the headline.

**Do I need a proxy?**
No. The site is plain server-rendered HTML with no anti-bot.

**Can I use another PR Newswire section?**
Yes — `feed` also accepts all releases, financial services, health and science/technology.

**What is `parsedFrom` for?**
It tells you whether the name came from the release text (most reliable) or a strict headline
pattern, so you can trust-rank rows.

**Is this legal?**
These are press releases — published specifically to be read and redistributed. Only public pages
are read, and no login or paywall is involved.

### Data source and fair use

Data comes from PR Newswire's public news-release pages. Factual fields are extracted (who, what
role, which company, when); release prose is not reproduced beyond a short summary field.

# Actor input Schema

## `feed` (type: `string`):

Which PR Newswire subject list to read. 'personnel' is the executive-moves list (~13,700 releases).

## `keywords` (type: `array`):

Keep only announcements whose headline contains one of these words, e.g. CEO, CFO, board. Leave empty for the whole feed.

## `fetchReleaseText` (type: `boolean`):

Open every release to get the company, publication date, domain and the person/title parsed from the release text. Turning this OFF is ~10x faster but leaves those fields empty.

## `requirePerson` (type: `boolean`):

Drop announcements where no executive name could be parsed with confidence. Roughly a third of releases parse — see the README before relying on this.

## `maxItems` (type: `integer`):

Stop after this many announcements. 0 = unlimited.

## `maxPages` (type: `integer`):

Safety cap on how many 100-release list pages to walk. 0 = automatic.

## `proxyConfiguration` (type: `object`):

Optional. The site has no anti-bot; a proxy is not required.

## Actor input object example

```json
{
  "feed": "personnel",
  "keywords": [],
  "fetchReleaseText": true,
  "requirePerson": false,
  "maxItems": 25,
  "maxPages": 0
}
```

# Actor output Schema

## `announcements` (type: `string`):

The dataset of scraped announcements.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "feed": "personnel",
    "maxItems": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/prnewswire-personnel-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "feed": "personnel",
    "maxItems": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/prnewswire-personnel-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "feed": "personnel",
  "maxItems": 25
}' |
apify call scrapersdelight/prnewswire-personnel-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/prnewswire-personnel-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/hiwvBEQsZwwQPa3So/builds/W5X10RjYgLrb8urqh/openapi.json
