# Europe PMC Scraper: Life-Science Literature & Full Text (`arman-bd/europe-pmc-scraper`) Actor

Scrape Europe PMC: citations, abstracts and full text where open access, plus MeSH terms, grants and cited-by counts. Broader coverage than PubMed for European research.

- **URL**: https://apify.com/arman-bd/europe-pmc-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Developer tools, AI, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.37 / 1,000 article scrapeds

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Europe PMC Scraper: Life-Science Literature & Full Text

![Europe PMC Scraper: Citations, abstracts, MeSH terms, funder grants and open-access full text, Europe PMC's own query syntax passed through untouched](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/europe-pmc-scraper.jpg)

**Europe PMC Scraper** pulls biomedical literature out of **Europe PMC**, titles, authors, journals, abstracts, DOIs, PMIDs and PMCIDs, MeSH terms, funder grants, cited-by counts, and **the complete open-access full text as clean plain text**.

Europe PMC indexes everything PubMed does and more: preprints, patents, theses, agency reports, and the European funder record that PubMed does not carry. For anyone building a RAG corpus or running a systematic review. It is the better starting point, and unlike PubMed's E-utilities. It has no key, no email-in-the-URL requirement and no 3-requests-per-second ceiling.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/europe-pmc-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/europe-pmc-scraper.md
```

### What you get

| Output field | Meaning |
|---|---|
| `id`, `source` | Europe PMC record ID and which index it came from (`MED`, `PMC`, `PPR` for preprints, `PAT` for patents…) |
| `pmid`, `pmcid`, `doi` | The three identifiers you join on. Any may be `null`, see the FAQ |
| `title`, `authorString` | Article title (markup stripped) and the full author list as one string |
| `journalTitle`, `journalIssn` | Publishing journal |
| `pubYear`, `firstPublicationDate` | Publication year and exact first-publication date |
| `abstractText` | Full abstract as plain text, markup stripped (`core` only) |
| `isOpenAccess`, `inEPMC`, `hasPdf` | Real booleans, converted from Europe PMC's `"Y"`/`"N"` strings |
| `citedByCount` | Citations recorded by Europe PMC |
| `grantsList` | Funder records: `grantId`, `agency`, `acronym`, the field PubMed does not give you |
| `meshTerms` | MeSH descriptor names, e.g. `["Humans","Cardiovascular Diseases"]` |
| `fullTextUrl` | Best available link, open access preferred, DOI as fallback |
| `fullTextUrls` | Every link Europe PMC knows, with its availability |
| `europePmcUrl` | Canonical article page |
| `fullText`, `fullTextChars` | Only when `includeFullText` is on, the article body as clean plain text |
| `query`, `scrapedAt` | Which query produced the row, and the run timestamp |

A `RUN_SUMMARY` record in the key-value store holds per-run counts, the filters used, any query that failed, and how many articles actually yielded full text.

### Common use cases

**1. Build an open-access biomedical corpus for RAG.** Open access on, full text on, `core` for the metadata that makes chunks citable.

```json
{
 "searchQueries": ["CRISPR gene editing"],
 "resultType": "core",
 "openAccessOnly": true,
 "includeFullText": true,
 "maxResults": 500
}
```

**2. Grant and funding analysis.** `grantsList` is the differentiator here, search by agency and get every funded paper with its grant IDs.

```json
{
 "searchQueries": [
 "GRANT_AGENCY:\"Wellcome Trust\" AND PUB_YEAR:2025",
 "GRANT_AGENCY:\"Gates Foundation\" AND PUB_YEAR:2025"
 ],
 "resultType": "core",
 "maxResults": 2000
}
```

**3. Systematic reviews with full-text access.** MeSH-scoped searching plus full text means screening on the body, not just the abstract.

```json
{
 "searchQueries": ["MESH:\"Neoplasms\" AND MESH:\"Immunotherapy\" AND PUB_YEAR:[2023 TO 2026]"],
 "resultType": "core",
 "openAccessOnly": true,
 "includeFullText": true,
 "maxResults": 1000
}
```

### Quick start

Simplest possible run:

```json
{
 "searchQueries": ["long covid"]
}
```

Fast, lightweight sweep for change detection:

```json
{
 "searchQueries": ["long covid"],
 "resultType": "lite",
 "maxResults": 5000
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `searchQueries` | array | - | **Required.** One search per entry. Plain words or Europe PMC field syntax. Each is paginated independently. |
| `resultType` | string | `core` | `core` for abstracts, MeSH and grants; `lite` for identifiers and citation basics only. Both carry the PMCID. |
| `openAccessOnly` | boolean | `false` | Appends `AND (OPEN_ACCESS:y)` to every query. |
| `includeFullText` | boolean | `false` | Downloads the JATS full text for every OA record with a PMCID and adds it as plain text. |
| `maxResults` | integer | `200` | Total cap across all queries. `0` = no cap. |

**Which combinations make sense.** `includeFullText` works with either `resultType`, `lite` returns the PMCID too, and pages 1,000 records at a time instead of 100, so it is the cheaper way to build a full-text corpus when you do not need the abstract and MeSH terms. `includeFullText` really wants `openAccessOnly: true` though: full text exists only for the OA subset, so without the filter most articles come back with `fullText: null` while you still pay for the row. Use `lite` when you are only diffing identifiers on a schedule. It is several times smaller on the wire.

#### Europe PMC query syntax

| Query | Finds |
|---|---|
| `long covid` | Free text across everything |
| `TITLE:"gene therapy"` | Title match |
| `AUTH:"Smith J"` | Author |
| `JOURNAL:"Nature"` | Journal |
| `PUB_YEAR:2025` or `PUB_YEAR:[2023 TO 2026]` | Year, exact or range |
| `MESH:"Neoplasms"` | MeSH term |
| `GRANT_AGENCY:"Wellcome Trust"` | Funder |
| `DOI:"10.1038/s41586-024-00001-0"` | Exact DOI lookup |
| `SRC:PPR` | Preprints only |
| `OPEN_ACCESS:y` | Open-access subset (or just set `openAccessOnly`) |
| `HAS_FT:y` | Records with full text in Europe PMC |

Combine with `AND`, `OR`, `NOT` and parentheses.

### Output example

```json
{
 "query": "long covid",
 "id": "42328279",
 "source": "MED",
 "pmid": "42328279",
 "pmcid": "PMC13279208",
 "doi": "10.1016/j.jhlto.2026.100600",
 "title": "Long COVID sequelae in heart transplant recipients.",
 "authorString": "Lees CR, Morad T, Webb M, Sim MS, Hsu JJ.",
 "journalTitle": "JHLT open",
 "journalIssn": "2950-1334",
 "pubYear": "2026",
 "firstPublicationDate": "2026-05-26",
 "abstractText": "Post acute sequelae of SARS-CoV-2 infection (also known as Long COVID) have been defined as symptoms that persist after 3 months …",
 "isOpenAccess": true,
 "inEPMC": true,
 "hasPdf": false,
 "citedByCount": 0,
 "grantsList": [
 { "grantId": "226350/Z/22/Z", "agency": "Wellcome Trust", "acronym": "WT" }
 ],
 "meshTerms": ["Humans", "Post-Acute COVID-19 Syndrome", "Heart Transplantation"],
 "fullTextUrl": "https://europepmc.org/articles/PMC13279208",
 "fullTextUrls": [
 { "site": "DOI", "url": "https://doi.org/10.1016/j.jhlto.2026.100600", "availability": "Subscription required", "documentStyle": "doi" },
 { "site": "Europe_PMC", "url": "https://europepmc.org/articles/PMC13279208", "availability": "Open access", "documentStyle": "html" }
 ],
 "europePmcUrl": "https://europepmc.org/article/MED/42328279",
 "fullText": "The COVID-19 pandemic has resulted in nearly 800 million confirmed cases and over 7 million deaths globally since its emergence in late 2019. …",
 "fullTextChars": 20665,
 "scrapedAt": "2026-08-06T11:48:44.221Z"
}
```

`RUN_SUMMARY`:

```json
{
 "resultType": "core",
 "queriesRequested": 1,
 "queriesFailed": 0,
 "failures": [],
 "articlesSaved": 500,
 "fullTextFetched": 431,
 "filters": {
 "searchQueries": ["CRISPR gene editing"],
 "resultType": "core",
 "openAccessOnly": true,
 "includeFullText": true,
 "maxResults": 500
 },
 "finishedAt": "2026-08-06T12:04:11.903Z"
}
```

### Limits and behaviour

- **Pagination is cursor-based, and that matters.** Europe PMC's page-number pagination breaks past 1,000 results; `cursorMark` does not. This Actor uses the cursor exclusively, carrying `nextCursorMark` forward and stopping when the API hands back the cursor it was given. Verified live walking six pages deep past the 1,000-record mark with no gap or repeat. There is no depth ceiling, so `maxResults: 0` on a broad query really will try to fetch all 1,063,524 hits, set a cap you mean.
- **Full text is the OA subset only.** Roughly a fifth of Europe PMC has open-access full text. Records without it get `fullText: null` and `fullTextChars: 0`; a missing full text is *not* counted as a failure, because it is the normal case. `RUN_SUMMARY.fullTextFetched` tells you how many actually came back with text.
- **Full text is cleaned, not raw.** Europe PMC serves JATS XML. This Actor extracts `<body>`, drops references, figures, tables and formulae, they become unreadable once markup is stripped, then decodes entities and normalises whitespace. Text is capped at 500,000 characters per article, which no real article reaches.
- **Titles and abstracts are stripped of markup.** Europe PMC returns structured abstracts headed with `<h4>Background</h4>` and chemistry titles containing `<sub>`/`<sup>`. Block tags become a space so sections do not fuse together, inline tags are removed outright so `LaMn<sub>1</sub>O<sub>3</sub>` stays `LaMn1O3`, and what you get is plain text a RAG chunker can ingest.
- **Entities are decoded twice.** Europe PMC double-encodes some free-text fields, one grant agency arrives as `US President&amp;apos;s Malaria Initiative`, so decoding runs twice and you get `US President's Malaria Initiative`.
- **`isOpenAccess` is a real boolean.** Europe PMC returns the string `"Y"`. This Actor converts `isOpenAccess`, `inEPMC` and `hasPdf` to `true`/`false` so you can filter without string comparison.
- **A rejected query doesn't kill the run.** Europe PMC reports bad queries as HTTP 200 with an `errCode` body rather than an error status; the Actor detects that, records the query in `RUN_SUMMARY.failures`, and continues. It only errors out if *every* query fails.
- **Partial results are kept.** If a query fails on its fourth page, the 300 articles from pages 1-3 stay in the dataset and the failure is still reported.
- **Transient errors are retried.** 429 and 5xx get three attempts with linear backoff.
- **Public data only.** No authentication, no personal data, no access-control bypass.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~europe-pmc-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "searchQueries": ["GRANT_AGENCY:\"Wellcome Trust\" AND PUB_YEAR:2025"],
 "resultType": "core",
 "maxResults": 100
 }'
```

### JavaScript example

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/europe-pmc-scraper').call({
 searchQueries: ['MESH:"Neoplasms" AND MESH:"Immunotherapy"'],
 resultType: 'core',
 openAccessOnly: true,
 includeFullText: true,
 maxResults: 200,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const withText = items.filter((a) => a.fullTextChars > 0);
console.log(`${withText.length}/${items.length} articles have full text`);
for (const a of withText.slice(0, 5)) {
 console.log(`${a.pmcid}, ${a.title} (${a.fullTextChars} chars)`);
}
```

### FAQ

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Do I need an account or API key?** No, unlike PubMed's E-utilities, Europe PMC asks for neither.

**What happens if Europe PMC is unavailable?** 429s and 5xx errors are retried with backoff. If a query still fails. It is recorded in `RUN_SUMMARY.failures` and the run continues with the remaining queries.

**Can I schedule it?** Yes. `firstPublicationDate` plus a diff on `id` gives a clean feed of what is new since the last run. `resultType: "lite"` makes those sweeps much cheaper to transfer.

**Why is `pmcid` null on some records?** Only articles deposited in PubMed Central have one. Subscription-only articles are indexed with a PMID and DOI but no PMCID, and therefore no full text.

**Why is `abstractText` null?** Either you used `resultType: "lite"`, which omits it, or the record has no abstract (common for preprints, patents and correspondence).

**Why did `includeFullText` give me mostly nulls?** You almost certainly left `openAccessOnly` off. Full text exists only for the OA subset, turn the filter on and the hit rate jumps.

**How does this differ from PubMed?** Europe PMC is a superset: it mirrors all of MEDLINE and adds preprints, patents, agency reports, theses and the European funder record. `grantsList` and open-access full text are the two things you cannot get this cleanly from PubMed.

**Can I get more than 1,000 results?** Yes, that is exactly what the cursor pagination is for. Raise `maxResults` and it keeps going.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

# Actor input Schema

## `searchQueries` (type: `array`):

One search per entry. Plain words work ('long covid'). For precision use Europe PMC field syntax, e.g. AUTH:"Smith J", TITLE:"gene therapy", JOURNAL:"Nature", PUB_YEAR:2025, GRANT_AGENCY:"Wellcome Trust", MESH:"Neoplasms", DOI:"10.1038/..". Each query is paginated independently.

## `resultType` (type: `string`):

core returns the full record. abstract, MeSH terms, grants, PMCID and full-text links. lite returns only identifiers and citation basics, but is several times faster and cheaper to transfer. lite still carries the PMCID, so full text works in either mode.

## `openAccessOnly` (type: `boolean`):

Append AND (OPEN_ACCESS:y) to every query so only open-access records come back. Turn this on whenever you want full text. the OA subset is the only part of Europe PMC that has any.

## `includeFullText` (type: `boolean`):

For every open-access record with a PMCID, download the JATS XML full text and add it as clean plain text. This costs one extra HTTP request per article, so runs take noticeably longer. Combine with openAccessOnly. Works with either resultType.

## `maxResults` (type: `integer`):

Cap the total articles saved across all queries. Set 0 for no cap. Europe PMC's cursor pagination has no depth limit, so a broad query really will try to fetch every hit.

## Actor input object example

```json
{
  "searchQueries": [
    "long covid",
    "GRANT_AGENCY:\"Wellcome Trust\" AND PUB_YEAR:2025"
  ],
  "resultType": "core",
  "openAccessOnly": false,
  "includeFullText": false,
  "maxResults": 200
}
```

# Actor output Schema

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

Every record the run produced.

## `runsummary` (type: `string`):

The RUN_SUMMARY record from the run's key-value store.

# 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 = {
    "searchQueries": [
        "long covid"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/europe-pmc-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 = { "searchQueries": ["long covid"] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/europe-pmc-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 '{
  "searchQueries": [
    "long covid"
  ]
}' |
apify call arman-bd/europe-pmc-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/europe-pmc-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/dMSw8LWWVrk4x0hIy/builds/o9gh4hz6IatXxJYIK/openapi.json
