# PubMed Scraper - Citations, Abstracts and MeSH (`s-r/pubmed-scraper`) Actor

Search PubMed or pull citations by PMID. Returns title, journal, authors, DOI, PMC id, publication types, citation counts and, where published, the abstract, MeSH terms and keywords.

- **URL**: https://apify.com/s-r/pubmed-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 run start fees

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?

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

## PubMed Scraper

Search **PubMed** or pull citations by PMID. Title, journal, authors, DOI, PMC
id, publication types, citation counts, and where the article has them, the
abstract, MeSH terms and author keywords.

Reads NCBI's official **E-utilities**. No key, no login.

### An empty abstract is usually the record being accurate

This is the distinction that makes the output trustworthy, so it is worth
stating first.

Letters, editorials, comments and news items **genuinely have no abstract**.
They never did. A scraper that reports "abstract missing" for those is
describing its own confusion rather than the literature.

Every row carries `publication_types`, and the run summary counts how many empty
abstracts fall into those categories. On a test run of 25 semaglutide papers, 22
had abstracts and the three that did not were all Letters — nothing was missing.

### Abstracts are enrichment, and they are allowed to fail

The endpoint that serves abstracts, `efetch`, **hangs unpredictably**. Measured
on 3 September 2026 against one query, varying only the batch size:

| Records requested | Result |
|---|---|
| 1 | 200, 4 KB, in 9.6 s |
| 3 | **timed out after 191 s** |
| 10 | 200, 188 KB, in 10.8 s |
| 20 | 200, 307 KB, in 12.6 s |

There is no pattern in size: a three-record request hung while a twenty-record
one finished in twelve seconds.

So this Actor treats abstracts as a bonus rather than a dependency. When a batch
times out, those citations come back with `abstract: null` and the run summary
reports `abstractFetchFailures`, instead of the whole run dying for a field that
supplements the record. The citations, DOIs and metadata come from `esummary`,
which is fast and reliable.

Turn `include_abstracts` off entirely and runs are markedly quicker.

### The DOI is not where you would look first

`elocationid` looks like the DOI field and is a trap: it carries the value
prefixed as `"doi: 10.1136/bmjmed-2026-003026"`, and on some records it holds a
page locator instead. `articleids` has a properly typed entry, and that is what
this Actor reads. On a 25-citation test run, **all 25 had a DOI**.

The same applies to PMC: `articleids` has both a `pmc` entry (`PMC13536023`) and
a `pmcid` entry (`pmc-id: PMC13536023;`). Only the clean one is used, so
`pmc_id` is directly usable as a link to free full text.

### Fields

- **Identity**: `pmid`, `doi`, `pmc_id`, `url`
- **Article**: `title`, `abstract`, `publication_types`, `language`
- **Journal**: `journal`, `journal_abbrev`, `issn`, `volume`, `issue`, `pages`
- **People**: `authors`, `first_author`, `last_author`
- **Dates**: `pub_date`, `epub_date`
- **Subject**: `mesh_terms`, `keywords`
- **Impact**: `citations_in_pmc`

`last_author` is worth knowing about: in biomedical convention that is usually
the senior investigator whose lab produced the work, which is a different
question from who wrote it.

`citations_in_pmc` counts citations from PubMed Central specifically, not the
whole literature, so it is a floor rather than a total citation count.

### Input reference

| Field | Type | Default |
|---|---|---|
| `query` | PubMed search syntax | `semaglutide obesity` |
| `pmids` | specific PubMed ids | — |
| `include_abstracts` | fetch abstracts, MeSH and keywords | `true` |
| `sort` | relevance, date, pub\_date, Author, JournalName | relevance |
| `date_from`, `date_to` | YYYY/MM/DD | — |
| `limit` | 1-5000 | 100 |
| `retries` | 1-6 | 3 |

PubMed's full search syntax works: `"Smith J"[Author]`, `2024[dp]`,
`"diabetes"[MeSH Terms]`, and boolean combinations of them. A search for
`semaglutide obesity` reports 2,833 matching citations.

### Why this Actor connects directly

Measured across every `.gov` host tested on 3 September 2026: a direct request
returns 200, while our residential proxy answers `CONNECT tunnel failed,
response 491`. That is the proxy refusing to tunnel to the host, not NCBI
refusing us. NCBI publishes E-utilities for automated use and asks for no more
than three requests a second without a key; this Actor stays under that.

### Typical uses

- **Literature monitoring.** Run a query on a schedule and join on `pmid` to see
  what is new in a field.
- **Systematic review screening.** Query, pull abstracts and MeSH terms, and
  export the lot for screening.
- **Competitive research.** Search by drug, device or company name and track
  publication volume over time.
- **Author and institution tracking.** `"Name"[Author]` queries, with
  `last_author` showing senior authorship.
- **Open access filtering.** `pmc_id` tells you which papers have free full text
  before you spend anything on access.

### Notes

`pub_date` is returned as PubMed writes it — `2026 Sep 3`, sometimes just a year
or a season. It is deliberately not reformatted into a false precision that the
source does not have. `epub_date` is often the more exact one.

A PMID that does not exist is simply absent from the results rather than
reported per id, because `esummary` accepts a batch and returns what it knows.

# Actor input Schema

## `query` (type: `string`):

PubMed search syntax, for example semaglutide obesity, or a fielded query like "Smith J"\[Author] AND 2024\[dp].

## `pmids` (type: `array`):

Look up specific citations by PubMed id instead of searching.

## `include_abstracts` (type: `boolean`):

Fetch abstracts, MeSH terms and keywords. This uses a separate NCBI endpoint that hangs unpredictably, so it is treated as enrichment: if a batch fails the citations still come back, with the abstract empty.

## `sort` (type: `string`):

How results are ordered.

## `date_from` (type: `string`):

Earliest publication date, as YYYY/MM/DD.

## `date_to` (type: `string`):

Latest publication date, as YYYY/MM/DD.

## `limit` (type: `integer`):

How many citations to return.

## `retries` (type: `integer`):

Retries with backoff before a request is reported as an error.

## Actor input object example

```json
{
  "query": "semaglutide obesity",
  "include_abstracts": true,
  "sort": "relevance",
  "limit": 100,
  "retries": 3
}
```

# Actor output Schema

## `citations` (type: `string`):

One row per PubMed citation.

## `summary` (type: `string`):

Counts, abstract coverage and which missing abstracts are legitimate.

## `errors` (type: `string`):

Failures with a code and a redacted message.

# 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 = {
    "query": "semaglutide obesity",
    "limit": 100,
    "retries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/pubmed-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 = {
    "query": "semaglutide obesity",
    "limit": 100,
    "retries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/pubmed-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 '{
  "query": "semaglutide obesity",
  "limit": 100,
  "retries": 3
}' |
apify call s-r/pubmed-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/pubmed-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/oe5jKagVpR5kRbv4O/builds/r1abfnVIFe1VZkatD/openapi.json
