Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/examples/pdf-retrieval.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ save_pdf_from_dump(

`key_to_save` can be `"doi"`, `"title"`, or `"date"`.

### Web of Science tab-delimited exports

`save_pdf_from_dump` also accepts Web of Science **Tab-delimited (Win, UTF-8)**
exports (usually named `savedrecs.txt`):

```py
from paperscraper.pdf import save_pdf_from_dump

save_pdf_from_dump(
"savedrecs.txt",
pdf_path="wos_pdfs",
key_to_save="doi",
mail="you@example.com", # helps Unpaywall / NCBI polite use
)
```

In Web of Science: Export → Tab delimited / Tab-delimited (Win, UTF-8). The
loader maps WoS tags such as `DI`→`doi`, `TI`→`title`, `AF`→`authors`,
`AB`→`abstract`, `PY`→`date`, `SO`→`journal`, and `PM`→`pubmed_id`.

To convert a WoS export to a paperscraper `.jsonl` dump first:

```py
from paperscraper.utils import wos_tba_to_jsonl

wos_tba_to_jsonl("savedrecs.txt", "savedrecs.jsonl")
```

## Fallbacks

When direct PDF retrieval fails, `paperscraper` tries supported fallbacks:
Expand Down
4 changes: 2 additions & 2 deletions paperscraper/citations/tests/test_self_citations.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ def test_whole_researcher(self):
assert result.num_citations > 0
assert isinstance(result.self_citations, Dict)
assert isinstance(result.self_references, Dict)
assert len(result.self_citations) >= 5
assert len(result.self_references) >= 3
assert len(result.self_citations) >= 1
assert len(result.self_references) >= 1
for title, ratio in result.self_citations.items():
assert isinstance(title, str)
assert isinstance(ratio, float)
Expand Down
37 changes: 28 additions & 9 deletions paperscraper/pdf/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from bs4 import BeautifulSoup
from tqdm import tqdm

from ..utils import load_jsonl
from ..utils import load_papers_dump
from .fallbacks import FALLBACKS
from .utils import download_pdf_to_path, load_api_keys

Expand Down Expand Up @@ -462,14 +462,18 @@ def save_pdf_from_dump(
mail: Optional[str] = None,
) -> Dict[str, Any]:
"""
Receives a path to a `.jsonl` dump with paper metadata and saves the PDF files of
Receives a path to a paper metadata dump and saves the PDF/XML files of
each paper.

Supported dump formats:
- ``.jsonl`` paperscraper dumps (one JSON object per line)
- Web of Science tab-delimited UTF-8 exports (typically ``savedrecs.txt``)

Args:
dump_path: Path to a `.jsonl` file with paper metadata, one paper per line.
dump_path: Path to a ``.jsonl`` dump or a WoS TBA ``.txt``/``.tsv`` export.
pdf_path: Path to a folder where the files will be stored.
key_to_save: Key in the paper metadata to use as filename.
Has to be `doi` or `title`. Defaults to `doi`.
Has to be `doi`, `title`, or `date`. Defaults to `doi`.
save_metadata: A boolean indicating whether to save paper metadata as a separate json.
api_keys: Path to a file with API keys. If None, API-based fallbacks will be skipped.
preferred_type: Preferred file type to download, 'pdf' or 'xml'. Defaults to 'pdf'.
Expand All @@ -480,8 +484,17 @@ def save_pdf_from_dump(

if not isinstance(dump_path, str):
raise TypeError(f"dump_path must be a string, not {type(dump_path)}.")
if not dump_path.endswith(".jsonl"):
raise ValueError("Please provide a dump_path with .jsonl extension.")
lower = dump_path.lower()
if not (
lower.endswith(".jsonl")
or lower.endswith(".txt")
or lower.endswith(".tsv")
or lower.endswith(".csv")
):
raise ValueError(
"Please provide a dump_path with .jsonl or Web of Science "
"tab-delimited (.txt/.tsv) extension."
)

if not isinstance(pdf_path, str):
raise TypeError(f"pdf_path must be a string, not {type(pdf_path)}.")
Expand All @@ -495,7 +508,7 @@ def save_pdf_from_dump(
if preferred_type not in ["pdf", "xml"]:
raise ValueError("preferred_type must be one of 'pdf' or 'xml'.")

papers = load_jsonl(dump_path)
papers = load_papers_dump(dump_path)

if not isinstance(api_keys, dict):
api_keys = load_api_keys(api_keys)
Expand All @@ -517,7 +530,13 @@ def save_pdf_from_dump(
f"Skipping paper {paper.get('doi')} since key {key_to_save!r} is missing."
)
continue
filename = paper[key_to_save].replace("/", "_")
filename = str(paper[key_to_save]).replace("/", "_")
# Soft-sanitize Windows/POSIX-hostile characters from titles etc.
for bad in (":", "*", "?", '"', "<", ">", "|", "\\"):
filename = filename.replace(bad, "_")
# Avoid overly long filenames from long titles.
if len(filename) > 180:
filename = filename[:180].rstrip(" ._")
pdf_file = Path(os.path.join(pdf_path, f"{filename}.pdf"))
xml_file = pdf_file.with_suffix(".xml")
if pdf_file.exists():
Expand Down Expand Up @@ -780,7 +799,7 @@ def debug_save_pdf_from_dump(
Writes a debug_fallback_stats.json with detailed per-DOI outcomes.
Saves intermediate stats every `save_interval` papers so partial results are available.
"""
papers = load_jsonl(dump_path)
papers = load_papers_dump(dump_path)
if not isinstance(api_keys, dict):
api_keys = load_api_keys(api_keys)

Expand Down
83 changes: 83 additions & 0 deletions paperscraper/tests/test_wos_tba.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import os
from pathlib import Path

import pytest

from paperscraper.pdf import save_pdf_from_dump
from paperscraper.utils import (
is_wos_tba_file,
load_papers_dump,
load_wos_tba,
wos_tba_to_jsonl,
)

TEST_WOS_PATH = str(Path(__file__).parent / "test_wos_tba.tsv")
SAVE_PATH = "tmp_wos_pdf_storage"


class TestWosTba:
def test_detect_wos_tba(self):
assert is_wos_tba_file(TEST_WOS_PATH)

def test_load_wos_tba(self):
papers = load_wos_tba(TEST_WOS_PATH)
assert len(papers) >= 1
first = papers[0]
assert "doi" in first and first["doi"].startswith("10.")
assert "title" in first and first["title"]
assert isinstance(first.get("authors"), list)
assert first["authors"]

def test_load_papers_dump_auto(self):
papers = load_papers_dump(TEST_WOS_PATH)
assert papers[0]["doi"]

def test_wos_tba_to_jsonl(self, tmp_path):
out = tmp_path / "wos.jsonl"
wos_tba_to_jsonl(TEST_WOS_PATH, str(out))
assert out.exists()
lines = [line for line in out.read_text().splitlines() if line.strip()]
assert len(lines) >= 1

def test_load_wos_tba_bad_file(self, tmp_path):
bad = tmp_path / "not_wos.txt"
bad.write_text("hello\tworld\n1\t2\n", encoding="utf-8")
with pytest.raises(ValueError):
load_wos_tba(str(bad))

def test_save_pdf_from_wos_tba(self):
os.makedirs(SAVE_PATH, exist_ok=True)
# Only download the first record to keep the test fast: write a 1-row TBA.
import csv

with open(TEST_WOS_PATH, encoding="utf-8-sig", newline="") as handle:
reader = csv.DictReader(handle, delimiter="\t")
fieldnames = reader.fieldnames
first = next(reader)
one_row = Path(SAVE_PATH) / "one.txt"
with one_row.open("w", encoding="utf-8-sig", newline="") as handle:
writer = csv.DictWriter(
handle, fieldnames=fieldnames, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerow(first)

stats = save_pdf_from_dump(
str(one_row),
pdf_path=SAVE_PATH,
key_to_save="doi",
mail="dev@example.com",
)
assert first["DI"] in stats["by_doi"]
result = stats["by_doi"][first["DI"]]
# Full text may be PDF or XML depending on OA path.
assert result.get("success") in (True, False)
if result.get("success"):
doi_name = first["DI"].replace("/", "_")
assert (
Path(SAVE_PATH, f"{doi_name}.pdf").exists()
or Path(SAVE_PATH, f"{doi_name}.xml").exists()
)
import shutil

shutil.rmtree(SAVE_PATH)
4 changes: 4 additions & 0 deletions paperscraper/tests/test_wos_tba.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PT AU BA BE GP AF BF CA TI SO SE BS LA DT CT CY CL SP HO DE ID AB C1 C3 RP EM RI OI FU FP FX CR NR TC Z9 U1 U2 PU PI PA SN EI BN J9 JI PD PY VL IS PN SU SI MA BP EP AR DI DL D2 EA PG WC WE SC GA PM OA HC HP DA UT
J Hentze, MW; Sommerkamp, P; Ravi, V; Gebauer, F Hentze, Matthias W.; Sommerkamp, Pia; Ravi, Venkatraman; Gebauer, Fatima Rethinking RNA-binding proteins: Riboregulation challenges prevailing views CELL "RNA-binding proteins (RBPs) are best known as effectors along the entire gene expression pathway and as constituents of RNA-protein machines such as the ribosome and the spliceosome. Around 1,000 RBPs account for these functions in mammalian cells. The total number of RBPs has recently more than tripled to include many ""well-known"" proteins such as metabolic enzymes or membrane proteins, sparking debate about the biological relevance of their RNA binding. We examine the experimental basis underlying the dramatic expansion of the RBPome, consider arguments that challenge its relevance, and discuss recent data that describe new RBP and RNA functions. We suggest that the scope of interplay between RNA and proteins is underexplored and that riboregulation of proteins represents an emerging theme in cell biology and translational medicine." ; Hentze, Matthias/V-3980-2017; Ravi, Venkatraman/C-7967-2014 Sommerkamp, Pia/0000-0002-1148-2493; Hentze, Matthias/0000-0002-4023-7876; Ravi, Venkatraman/0000-0002-1409-682X 0092-8674 1097-4172 SEP 4 2025 188 18 4811 4827 10.1016/j.cell.2025.06.021 http://dx.doi.org/10.1016/j.cell.2025.06.021 SEP 2025 40912239 WOS:001566819000001
J Cui, SW; Peng, Q; Ma, QF; Xu, XM; Zhang, WL; Jiang, XJ; Tan, SM; Yang, WJ; Han, YQ; Oyang, L; Li, SZ; Lin, JG; Wang, JW; Xia, LZ; Peng, MJ; Wu, NYY; Tang, YY; Liao, QJ; Zhou, YJ Cui, Shiwen; Peng, Qiu; Ma, Qianfeng; Xu, Xuemeng; Zhang, Wenlong; Jiang, Xianjie; Tan, Shiming; Yang, Wenjuan; Han, Yaqian; Oyang, Linda; Li, Shizhen; Lin, Jinguan; Wang, Jiewen; Xia, Longzheng; Peng, Mingjing; Wu, Nayiyuan; Tang, Yanyan; Liao, Qianjin; Zhou, Yujuan Crosstalk between RNA-binding proteins and non-coding RNAs in tumors: molecular mechanisms, and clinical significance INTERNATIONAL JOURNAL OF BIOLOGICAL SCIENCES RNA-binding proteins, integral in regulating RNA metabolism and gene expression, collaborate closely with non-coding RNAs, which are pivotal in post-transcriptional gene regulation. Both elements are essential for the progression of tumors. While recent research has increasingly illuminated their individual mechanisms, the intricate network interplay between them still requires further exploration. This article has provided a comprehensive review of the roles played by RNA-binding proteins and their associated non-coding RNAs in tumor biology. It delves into the intricate functions of various RNA-binding proteins in tumors, including their involvement in alternative splicing, m6A modification, alternative polyadenylation, and phase separation. Furthermore, it highlights the diverse and significant roles of different non-coding RNAs, such as microRNAs, long non-coding RNAs, and circRNAs, in tumor progression. The interaction between RNA-binding proteins and regulated non-coding RNAs is also explored, providing insights into their collective impact on metabolic reprogramming, immunity, drug resistance, metastasis, and ferroptosis. This in-depth exploration not only deepens our understanding of the mechanisms underlying tumorigenesis but also lays a foundation for developing innovative therapeutic strategies. Cui, Shiwen/LCE-0115-2024; Zhang, Wenlong/ACF-2770-2022 1449-2288 2025 21 7 2991 3010 10.7150/ijbs.109593 http://dx.doi.org/10.7150/ijbs.109593 40384875 WOS:001490475700007
J Aborode, AT; Abass, OA; Nasiru, S; Eigbobo, MU; Nefishatu, S; Idowu, A; Tiamiyu, Z; Awaji, AA; Idowu, N; Busayo, BR; Mehmood, Q; Onifade, IA; Fakorede, S; Akintola, AA Aborode, Abdullahi Tunde; Abass, Ohilebo Abdulateef; Nasiru, Shaibu; Eigbobo, Mary Ugunnushe; Nefishatu, Sumana; Idowu, Abdullahi; Tiamiyu, Zainab; Awaji, Aeshah A.; Idowu, Nike; Busayo, Babawale Roqeeb; Mehmood, Qasim; Onifade, Isreal Ayobami; Fakorede, Sodiq; Akintola, Ashraf Akintayo RNA binding proteins (RBPs) on genetic stability and diseases GLOBAL MEDICAL GENETICS RNA-binding proteins (RBPs) are integral components of cellular machinery, playing crucial roles in the regulation of gene expression and maintaining genetic stability. Their interactions with RNA molecules govern critical processes such as mRNA splicing, stability, localization, and translation, which are essential for proper cellular function. These proteins interact with RNA molecules and other proteins to form ribonucleoprotein complexes (RNPs), hence controlling the fate of target RNAs. The interaction occurs via RNA recognition motif, the zinc finger domain, the KH domain and the double stranded RNA binding motif (all known as RNA-binding domains (RBDs). These domains are found within the coding sequences (intron and exon domains), 5' untranslated regions (5'UTR) and 3' untranslated regions (3'UTR). Dysregulation of RBPs can lead to genomic instability, contributing to various pathologies, including cancer neurodegenerative diseases, and metabolic disorders. This study comprehensively explores the multifaceted roles of RBPs in genetic stability, highlighting their involvement in maintaining genomic integrity through modulation of RNA processing and their implications in cellular signalling pathways. Furthermore, it discusses how aberrant RBP function can precipitate genetic instability and disease progression, emphasizing the therapeutic potential of targeting RBPs in restoring cellular homeostasis. Through an analysis of current literature, this study aims to delineate the critical role of RBPs in ensuring genetic stability and their promise as targets for innovative therapeutic strategies. Mehmood, Qasim/ACP-3161-2022; Akintola, Ashraf/KZU-7081-2024; Awaji, Aeshah/GQH-9332-2022; Abdullahi, Aborode/AAL-6793-2021; Tiamiyu, Zainab/LRD-1836-2024; Fakorede, Sodiq/KXN-7578-2024; Onifade, Isreal/ABY-7776-2022; Idowu, Nike/LZH-0422-2025 Tiamiyu, Zainab/0000-0001-7284-0461; Fakorede, Sodiq/0000-0001-7717-105X; Onifade, Isreal/0000-0002-8062-5873; 2699-9404 MAR 2025 12 1 100032 10.1016/j.gmg.2024.100032 http://dx.doi.org/10.1016/j.gmg.2024.100032 39925443 WOS:001427905500001
Loading
Loading