Skip to content

Get started

scopusflow turns a Scopus search into a small, reproducible workflow. You describe a search as a plan, size it before spending quota, retrieve it as a resumable harvest, and then work with the records through one stable schema. This guide walks the whole arc once, lightly, and points to the task guides for the detail. Everything that does not contact the API runs offline, and the few steps that need a key are marked as such.

import scopusflow as sf

A query is only a string, but composing it by hand invites a missing bracket or a mistyped tag. scopus_query builds a field-tagged, boolean query and returns the exact string the API will receive, so you can read it before you run it.

q = sf.scopus_query("graphene", "supercapacitor", field="TITLE-ABS-KEY")
out(q)
TITLE-ABS-KEY(graphene) AND TITLE-ABS-KEY(supercapacitor)

A SearchPlan wraps that query in a description you can print, version-control and partition. Partitioning by year keeps each cell small enough to stay under the API's offset ceiling, and it makes the harvest resumable cell by cell.

plan = sf.SearchPlan(q, years=range(2015, 2025), partition="year")
out([(c.cell, c.year) for c in plan.cells()])
[(1, 2015), (2, 2016), (3, 2017), (4, 2018), (5, 2019), (6, 2020), (7, 2021), (8, 2022), (9, 2023), (10, 2024)]

The Designing queries and Search plans and quota-aware retrieval guides go further into both.

Size it, then fetch

A count tells you what a harvest will cost before you pay for it. Both calls below contact the Scopus API and need a key configured for pybliometrics, so they are shown here but not run.

# Both need a configured Scopus key.
sf.scopus_count(q, years=range(2015, 2025))
records = sf.fetch_plan(plan, cache_dir="harvest", resume=True)

The bundled harvest

Whether records come from the API or from data you already hold, they share one schema, the columns named in RECORD_COLUMNS. To let the rest of this guide, and every other page on this site, run without a key, the package ships a worked example in exactly that shape. example_records returns it.

records = sf.example_records()
out(records[["title", "year", "publication", "citations"]].head())
title year publication citations
Enhanced Capacitive Properties of All-solid-state Symmetric Graphene Supercapacitors by Incorporating Nitrogen-doping and SnO2 Nanoparticles 2015 Journal of Inorganic Materials 1
Fabrication and Characterization of a Vertically-Oriented Graphene Supercapacitor 2015 DigitalCommons - CalPoly (California State Polytechnic University) 0
Flexible and Stackable Laser-Induced Graphene Supercapacitors 2015 ACS Applied Materials & Interfaces 469
Heavily nitrogen doped, graphene supercapacitor from silk cocoon 2015 Electrochimica Acta 195
Graphene-Based Integrated Photovoltaic Energy Harvesting/Storage Device 2015 Small 108

Those are real journal articles on graphene supercapacitors, 138 of them published between 2015 and 2024, carrying their real titles, DOIs, journals, first authors and citation counts. They are deliberately not a Scopus harvest, and no package can ship one, because Elsevier's API terms do not permit redistributing the records a key retrieves. These come instead from OpenAlex, whose metadata is released under CC0 and may therefore travel with the package, reshaped into the schema fetch_plan returns. Run the query above against Scopus and you get a frame of the same shape, with the same columns and the same handling, though not an identical set of records.

Two consequences are worth knowing before the examples below. The harvest is complete, so the number of rows in a year is the number of publications the query matched that year, and a trend drawn from it is a real publication curve. And scopus_id is empty throughout, these records not having come from Scopus, so anything that would key on the Scopus identifier falls back to the DOI. Eleven records carry no DOI and two no source title, left exactly as they arrive, because a real harvest has gaps of the same kind.

top tallies the most frequent sources or authors from the frame, counting each contributor once per record.

out(sf.top(records, by="source", n=5))
value n
ACS Applied Materials & Interfaces 8
Journal of Power Sources 5
Synthetic Metals 5
Electrochimica Acta 4
Scientific Reports 4

Carry the work onward

From the one schema the rest of the workflow follows. You can extract a clean DOI list, compare two harvests to see what changed, summarise growth over time and draw it, compare how sub-topics move within a literature, read fuller records, export to a reference manager, and write the search up for a methods section.

dois = sf.extract_dois(records)
out((len(dois), dois[:3]))
(127, ['10.15541/jim20140527', '10.1021/am509065d', '10.1016/j.electacta.2015.02.019'])

The list is 127 entries long against 138 records, because the eleven without a DOI are dropped, where carrying them through as blanks would put empty identifiers into whatever you import them into.

Each of these has its own guide. Building a reference set covers DOIs and export to BibTeX and RIS. Tracking change over time covers comparing two retrievals. Analysing a literature covers trends, top sources and authors, and abstracts. Comparing topics covers the share of a reference literature each sub-topic holds over time. Author keywords and references covers retrieving a document's own keywords and reference list, and assembling a minimal corpus from them, both at a materially different quota cost from an ordinary search. Search plans and quota covers the resumable harvest and the PRISMA-S record of the search. The code-free app does all of it through a browser tab, with no code at all.