# Financial Press Release Scraper (All In One) (`scrapyx/newswire-press-releases-scraper`) Actor

Scrapes corporate press releases from PR Newswire, GlobeNewswire and Business Wire into one unified schema. Query by stock ticker, keyword or URL. Detected tickers, ISO 8601 UTC timestamps, and release bodies as clean Markdown — AI-ready for sentiment analysis.

- **URL**: https://apify.com/scrapyx/newswire-press-releases-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** Automation, News, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Financial Press Release Scraper

Scrapes corporate press releases from the three major financial newswires —
**[PR Newswire](https://www.prnewswire.com)**,
**[GlobeNewswire](https://www.globenewswire.com)** and
**[Business Wire](https://www.businesswire.com)** — and returns them in **one
unified schema**.

Query by **stock ticker**, **keyword**, or a **specific wire URL**. The Actor
works out which wire each input belongs to and routes it automatically, so a
single run covers all three.

Public data only. No login, no cookies, no browser.

### What makes this one honest

The three wires are **not** equally open, and this Actor does not pretend
otherwise. Every query writes a `QUERY_SUMMARY` row naming the mode it actually
used and how many releases it inspected versus matched.

| | PR Newswire | GlobeNewswire | Business Wire |
| --- | --- | --- | --- |
| **Search** | ✅ server-side, deep archive | ✅ server-side, **20 results max** | ⚠️ filters a recent-news feed |
| **Full body** | ✅ | ✅ | ❌ **headline + first paragraph only** |
| **Deep pagination** | ✅ up to 100/page | via category pages | ❌ fixed window |

**Business Wire denies its own article pages to every automated client.** Its
`/news/home/…` URLs return a cached 403 at the CDN edge — verified across both
schemes, a warmed session, a `Referer`, and four TLS fingerprints. That is a
configuration rule, not a bot score, so no amount of stealth changes it. What
*is* reachable is the MRSS feed Business Wire itself declares in its
`robots.txt`, which carries the headline, link, timestamp and opening
paragraph. Those rows are flagged **`contentComplete: false`**.

### What you get

Three record types share one dataset, told apart by `recordType`.

#### `PRESS_RELEASE` — one row per release

Identical fields regardless of which wire it came from:

```jsonc
{
  "articleId": "3134590",
  "source": "GlobeNewswire",
  "title": "CBAK Energy Expands Model 32140 Capacity…",
  "url": "https://www.globenewswire.com/news-release/2026/08/20/…",
  "publishedDate": "2026-08-20T13:20:08Z",   // ISO 8601, always UTC
  "updatedDate": "2026-08-20T13:20:08Z",

  "companyTickers": ["CBAT"],
  "tickerDetails": [
    { "exchange": "NASDAQ", "symbol": "CBAT", "raw": "NASDAQ: CBAT" }
  ],

  "issuer": "CBAK Energy Technology Limited",
  "categories": ["Product / Services Announcement", "…"],
  "language": "en",
  "summary": "…",

  "contentMarkdown": "NEWTON, Mass., Aug. 20, 2026 (GLOBE NEWSWIRE) -- …",
  "contentText": "…",
  "contentHtml": "…",
  "contentComplete": true,
  "wordCount": 1842,

  "raw": { /* upstream's JSON-LD / feed item, verbatim */ }
}
```

#### `QUERY_SUMMARY` — one row per query

Which wire, which search mode, `candidatesScanned` vs `returnedCount`, request
count, and the content ceiling for that wire.

#### `ERROR` — one row per failed input

**Every input produces at least one row**, so an unroutable URL or a denied
path is visible in the dataset rather than silently missing.

### Ticker matching — read this before querying a mega-cap

Both PR Newswire and GlobeNewswire search **text**, not a symbol index. So a
query for `AAPL` finds releases containing that *string*.

This Actor then **re-verifies** every result against an exchange-qualified
ticker in the release body (`NASDAQ: AAPL`, `NasdaqGS: AAPL`, `$AAPL`) and
drops the rest — so `companyTickers` is never empty on a row the ticker filter
admitted.

The consequence is worth knowing up front: **for a mega-cap like AAPL, most
matches are securities-litigation notices**, because law firms print the ticker
while Apple's own investor relations does not distribute through these wires.
Ticker search works best for small- and mid-cap issuers, who *do* announce
through the wires and print their symbol in the dateline.

`candidatesScanned` vs `returnedCount` on the summary row shows exactly how
much was filtered.

### Markdown cleansing

`contentMarkdown` is the release body with publisher chrome removed **before**
conversion — share bars, multimedia widgets, related-links rails, tracking
pixels and scripts are stripped from the DOM, then trailing boilerplate
(`SOURCE …`, `View original content…`, `View source version on
businesswire.com…`) is dropped from the Markdown. Headings are ATX (`##`) so
they survive chunking for embedding.

### Input

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `tickers` | array | — | `AAPL`, `NASDAQ:NVDA`. Verified against the body. |
| `keywords` | array | — | Free text. |
| `startUrls` | array | — | A release, or a news list / category page. |
| `sources` | array | all three | Which wires to query. |
| `includeContent` | boolean | `true` | Fetch bodies and build Markdown. |
| `maxItems` | integer | `0` | Caps **output** rows per query. |
| `maxPages` | integer | `2` | Listing depth per query. |
| `scanCap` | integer | `60` | Caps **candidates inspected** for ticker queries. |
| `globenewswireCategories` | array | all six | Narrow the GlobeNewswire archive walk. |
| `pageSize` | integer | `100` | PR Newswire only (its real ceiling). |
| `maxConcurrency` | integer | `5` | Total requests in flight. |
| `minRequestInterval` | integer | `0` | Seconds between request starts. |
| `proxyConfiguration` | object | Residential | Not a bot-wall bypass — see below. |

`maxItems` and `scanCap` are deliberately separate. A ticker query has to fetch
releases *before* it can tell whether they name the ticker, so capping the
fetch at `maxItems` would starve the filter and report a confident zero.

### Known limits

- **Business Wire: no full bodies.** Headline + lede only, `contentComplete:
  false`. Its article pages are closed to automated clients.
- **GlobeNewswire keyword search caps at 20 results** and does not paginate
  (`?page=2` silently re-serves page 1). For depth, pass a category URL in
  `startUrls` — those pages *do* paginate genuinely.
- **GlobeNewswire `/search` is not used at all** — its `robots.txt` disallows
  it. Ticker and keyword queries go through the allowed `/RssFeed/` instead;
  a `/search` URL is refused up front with an explanation.
- **PR Newswire is the only wire whose search reaches the deep archive.**
- Multi-language releases are deduplicated on the release id, preferring the
  English rendition.

### Notes on politeness and proxies

PR Newswire and GlobeNewswire answered **every TLS fingerprint tested** from a
plain residential connection — there is no bot wall to defeat on either. The
residential proxy default exists because a cloud run shares datacenter egress
with every other Actor, and large publisher CDNs rate-limit by IP long before
they fingerprint.

`minRequestInterval`, not `maxConcurrency`, is the honest speed control.

### Related

For GlobeNewswire alone at greater depth, this portfolio also ships
[`globenewswire-press-releases-scraper`](../globenewswire-press-releases-scraper).
This Actor's distinct value is the **cross-wire unified schema** and
ticker-centric querying; the single-wire Actor goes deeper on one source.

See [`CRAWLING_METHOD.md`](CRAWLING_METHOD.md) for the full recon trail, every
endpoint, and each trap found while building this.

# Actor input Schema

## `tickers` (type: `array`):

Stock symbols to track — e.g. `AAPL`, `TSLA`, `NASDAQ:NVDA`. Each is queried against every newswire you enable.

**How matching works differs by wire, and the difference is real:**

- **PR Newswire** runs a genuine server-side search, so it reaches the full archive.
- **GlobeNewswire** and **Business Wire** are matched by *scanning recent news* and reading the ticker out of each release — because GlobeNewswire's robots.txt disallows its search endpoint and Business Wire exposes no reachable search at all. Those two therefore find recent mentions, not history.

Every query writes a `QUERY_SUMMARY` row saying which mode it used and how many releases it inspected.

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

Free-text phrases to search for — e.g. `quarterly results`, `FDA approval`, `merger`. Same per-wire matching rules as tickers.

On PR Newswire an unmatched keyword returns a clean zero rather than a full unfiltered list, so an empty result genuinely means 'no matches'.

## `startUrls` (type: `array`):

Specific URLs to scrape directly. Accepts:

- **A press release** — `https://www.prnewswire.com/news-releases/<slug>-<id>.html`, `https://www.globenewswire.com/news-release/2026/08/21/<id>/0/en/<slug>.html`
- **A news list** — `https://www.prnewswire.com/news-releases/all-public-company-news/`, `https://www.globenewswire.com/news/energy`

Business Wire article URLs are **not** fetchable: that site denies its own `/news/home/` pages to every HTTP client at the CDN edge. Reach Business Wire through tickers or keywords instead, which read its sitemap-declared feed.

## `sources` (type: `array`):

Which wires to query. Defaults to all three.

Note that **Business Wire returns headline + opening paragraph only** — its full article pages are closed to automated clients, so those rows carry `contentComplete: false`.

## `includeContent` (type: `boolean`):

Fetch each release's body and convert it to clean Markdown (`contentMarkdown`), with share bars, multimedia widgets and tracking pixels stripped out first.

Turning this off keeps headlines, tickers, timestamps and URLs while skipping the body — but note that on GlobeNewswire the ticker is only printed in the body, so ticker matching there needs this on.

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

Stop after this many releases per query. 0 means no cap.

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

How deep to page through each news list or search result.

PR Newswire returns up to 100 releases per page; GlobeNewswire is fixed at 10 per page by upstream. Raising this is the main way to reach further back in time.

## `scanCap` (type: `integer`):

Upper bound on how many releases are fetched and inspected when matching a ticker or keyword on the two wires that cannot be searched server-side.

This is a cost control: those wires require reading each release to find the ticker. 0 disables the cap.

## `globenewswireCategories` (type: `array`):

Restrict the GlobeNewswire scan to specific sections. Leave empty to scan all six.

Narrowing to the relevant sector makes ticker matching far cheaper — a biotech ticker will not appear under Energy.

## `pageSize` (type: `integer`):

Releases per listing request on PR Newswire, which is the only wire with an honest page-size parameter. Capped at 100, its real ceiling.

GlobeNewswire ignores this (fixed at 10) and Business Wire's feed is a fixed rolling window.

## `maxConcurrency` (type: `integer`):

Upper bound on requests in flight across the whole run — listing pages and article fetches share this budget.

## `minRequestInterval` (type: `integer`):

Seconds between the START of one request and the next. These are large ad-supported publisher sites, so the default paces politely rather than sprinting.

This, not concurrency, is the honest speed control: once a rate cap binds, extra concurrency buys nothing.

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

PR Newswire and GlobeNewswire answered every TLS fingerprint tested from a plain residential connection, so a proxy is not a bot-wall bypass here.

Residential is still the default on the platform, because a cloud run shares datacenter egress with every other Actor and large publisher CDNs rate-limit by IP long before they fingerprint.

## Actor input object example

```json
{
  "tickers": [
    "AAPL"
  ],
  "keywords": [],
  "startUrls": [],
  "sources": [
    "prnewswire",
    "globenewswire",
    "businesswire"
  ],
  "includeContent": true,
  "maxItems": 0,
  "maxPages": 2,
  "scanCap": 60,
  "globenewswireCategories": [],
  "pageSize": 100,
  "maxConcurrency": 5,
  "minRequestInterval": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "tickers": [
        "AAPL"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/newswire-press-releases-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 = { "tickers": ["AAPL"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/newswire-press-releases-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 '{
  "tickers": [
    "AAPL"
  ]
}' |
apify call scrapyx/newswire-press-releases-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/newswire-press-releases-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/2qmTwEWe0MzInaJZs/builds/yxYYkvfn1BnpfhUMB/openapi.json
