Gestionar una lista de referencias en CSV y comprobar DOI duplicados o ausentes
Mantén una pequeña lista de referencias de investigación en CSV, normaliza el texto DOI para compararlo y genera un archivo de revisión para valores DOI duplicados o ausentes. El workflow conserva el CSV original y no afirma que un DOI sea válido solo porque esté presente.
Contenido revisado 2026.09.20Incluye archivos de ejemplo
Ver el índice
La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English
Para quién esEsta guía está dirigida a investigadores que mantienen listas de referencias en hojas de cálculo o archivos CSV y quieren una comprobación sencilla de DOI duplicados y ausentes.
Preparación
Python 3.12 y un comando de terminal que inicie esa versión.
Un editor de texto o programa de hojas de cálculo que pueda guardar archivos CSV en UTF-8.
Una carpeta de trabajo donde el script pueda crear una nueva carpeta dentro de outputs.
Solo se requiere la biblioteca estándar de Python: csv y pathlib.
01Decidir qué puede y qué no puede decirte la comprobación de DOI
Este workflow comprueba dos problemas prácticos: una reference no tiene DOI value, o dos rows contienen el mismo DOI después de una simple text normalization. No contacta con un DOI registry y, por tanto, no demuestra que un DOI exista, se resuelva correctamente o pertenezca al article citado.
El script elimina textual prefixes comunes como https://doi.org/ y doi:, recorta whitespace y convierte el DOI text restante a lowercase para compararlo. El original DOI text se conserva en el output junto al normalized value.
02Crear una lista de referencias sintética
La siguiente reference list es sintética y fue creada específicamente para este artículo. Las DOI-like strings son datos de demostración y no se presentan como publicaciones reales o resolubles. Guarda el archivo como references.csv.
Hay 6 reference rows. Cinco contienen DOI text y una, R004, no tiene DOI. R001 y R003 utilizan textual forms diferentes, pero se normalizan al mismo value. R005 demuestra que la capitalization y un prefix doi: también pueden normalizarse para la comparación.
03Determinar manualmente los problemas esperados
ref_id
Original DOI
Normalized DOI
Issue esperado
R001
10.0000/demo.alpha
10.0000/demo.alpha
DUPLICATE_DOI
R002
10.0000/demo.beta
10.0000/demo.beta
None
R003
https://doi.org/10.0000/demo.alpha
10.0000/demo.alpha
DUPLICATE_DOI
R004
MISSING_DOI
R005
doi:10.0000/DEMO.GAMMA
10.0000/demo.gamma
None
R006
10.0000/demo.delta
10.0000/demo.delta
None
Los recuentos esperados son 6 references, 5 rows con DOI text, 1 row con missing DOI y 1 duplicate DOI group que contiene 2 rows. Después de la normalización, las cinco DOI entries no vacías contienen 4 distinct DOI values.
04Normalizar el texto DOI y escribir archivos de revisión
Guarda el script siguiente como reference_list_check.py. Escribe una normalized copy de toda la reference list y un issues.csv separado que contiene únicamente las rows que necesitan review. El references.csv original es de solo lectura.
python
import csv
from pathlib import Path
SOURCE = Path("references.csv")
OUTPUT_DIR = Path("outputs") / "reference_list_result"
NORMALIZED = OUTPUT_DIR / "normalized_references.csv"
ISSUES = OUTPUT_DIR / "issues.csv"
REQUIRED = {"ref_id", "title", "year", "doi"}
def normalize_doi(value: str) -> str:
text = value.strip()
lowered = text.lower()
prefixes = (
"https://doi.org/",
"http://doi.org/",
"doi:",
)
for prefix in prefixes:
if lowered.startswith(prefix):
text = text[len(prefix):].strip()
break
return text.lower()
def main() -> None:
if not SOURCE.is_file():
raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
if OUTPUT_DIR.exists():
raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")
rows = []
with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream)
if reader.fieldnames is None or not REQUIRED.issubset(reader.fieldnames):
raise ValueError("CSV is missing a required column.")
seen_ids = set()
for row in reader:
ref_id = row["ref_id"].strip()
if not ref_id:
raise ValueError("ref_id must not be blank.")
if ref_id in seen_ids:
raise ValueError(f"Duplicate ref_id: {ref_id}")
seen_ids.add(ref_id)
rows.append({
"ref_id": ref_id,
"title": row["title"],
"year": row["year"],
"doi": row["doi"],
"normalized_doi": normalize_doi(row["doi"]),
})
by_doi = {}
for row in rows:
doi = row["normalized_doi"]
if doi:
by_doi.setdefault(doi, []).append(row["ref_id"])
duplicate_dois = {
doi: ids for doi, ids in by_doi.items() if len(ids) > 1
}
issue_rows = []
for row in rows:
doi = row["normalized_doi"]
if not doi:
issue_rows.append({
"ref_id": row["ref_id"],
"issue": "MISSING_DOI",
"normalized_doi": "",
"related_ref_ids": "",
})
elif doi in duplicate_dois:
issue_rows.append({
"ref_id": row["ref_id"],
"issue": "DUPLICATE_DOI",
"normalized_doi": doi,
"related_ref_ids": ";".join(duplicate_dois[doi]),
})
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir()
normalized_fields = ["ref_id", "title", "year", "doi", "normalized_doi"]
with NORMALIZED.open("x", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=normalized_fields)
writer.writeheader()
writer.writerows(rows)
issue_fields = ["ref_id", "issue", "normalized_doi", "related_ref_ids"]
with ISSUES.open("x", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=issue_fields)
writer.writeheader()
writer.writerows(issue_rows)
missing_count = sum(row["issue"] == "MISSING_DOI" for row in issue_rows)
print(f"References checked: {len(rows)}.")
print(f"Missing DOI rows: {missing_count}.")
print(f"Duplicate DOI groups: {len(duplicate_dois)}.")
print(f"Issue rows: {len(issue_rows)}.")
print(f"Output folder: {OUTPUT_DIR.as_posix()}")
if __name__ == "__main__":
main()
05Comparar el informe de revisión esperado
issues.csv debería contener tres data rows: R001 y R003 por el normalized DOI duplicado, más R004 por el missing DOI.
La salida de consola esperada que aparece a continuación se obtuvo manualmente a partir de la synthetic list y el script. No es un execution log capturado.
text
References checked: 6.
Missing DOI rows: 1.
Duplicate DOI groups: 1.
Issue rows: 3.
Output folder: outputs/reference_list_result
06Revisar las filas marcadas en lugar de eliminarlas automáticamente
Confirma que R001 y R003 se normalicen al mismo DOI text.
Confirma que R004 se conserve en la normalized reference list aunque falte su DOI.
Comprueba paper title, authors, year y publication details antes de decidir si las duplicate-DOI rows deben fusionarse.
Para las missing DOI rows, verifica si la publication tiene realmente un DOI antes de añadir uno.
Ejecuta de nuevo el script sin cambiar OUTPUT_DIR. Debería detenerse con FileExistsError en lugar de sustituir la review anterior.
Problema
Qué comprobar
El mismo paper se introdujo dos veces con distinto DOI formatting
Normaliza los DOI URL y prefixes doi: comunes antes de comparar.
Un DOI en blanco se trata como duplicado
Mantén la missing DOI detection separada del duplicate DOI grouping.
Papers diferentes comparten un title parecido
No utilices únicamente title similarity como prueba de que los records son duplicates.
Hay una string que parece un DOI
La presencia no demuestra validity; verifica las references importantes con una trusted metadata source.
Se sobrescribe un result existente
Utiliza una output folder nueva para conservar la previous review evidence.
07Comprender los límites de la deduplicación basada en DOI
DOI matching es útil cuando hay DOI metadata, pero un missing DOI no significa que una reference sea invalid o incomplete. Algunas publications o document types pueden no tener DOI, y older records pueden requerir otros identifiers o manual bibliographic checks.
Este script realiza únicamente text normalization. No resuelve DOI links, consulta Crossref u otra registration agency, verifica titles o authors, detecta retractions ni determina si dos DOI values distintos corresponden a related versions de una obra.
Para una research database real, conserva fields como authors, title, year, journal, volume, pages, DOI y another local reference ID. Trata la automatic duplicate detection como una ayuda para review y no como una instrucción para eliminar bibliographic records.
Se contaron manualmente 6 synthetic reference rows, 5 rows con DOI y 1 missing DOI row.
Se normalizaron manualmente R001 y R003 al mismo value, 10.0000/demo.alpha.
Se normalizó manualmente R005 de doi:10.0000/DEMO.GAMMA a 10.0000/demo.gamma.
Se calcularon 4 distinct normalized DOI values entre las 5 DOI rows no vacías.
Se obtuvo el expected issue report como 2 duplicate-DOI rows más 1 missing-DOI row.
Se revisó el script para duplicate ref_id checks, DOI normalization, tratamiento separado de missing y duplicate, output collision protection y conservación del CSV original.
Límites de la verificación
El código no fue ejecutado por el autor de esta respuesta; no se crearon archivos CSV de salida.
Las synthetic DOI-like strings no se comprobaron para registration o resolution y no se presentan como publicaciones reales.
No se probaron DOI validity, bibliographic metadata accuracy, title similarity, retractions ni related article versions.
Las URL de la documentación oficial se proporcionaron a partir de ubicaciones de documentación conocidas, pero no se comprobaron en línea.
Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.
En lugar de recopilar solo un título y una URL, registra en una sola fila la fecha de referencia, la fecha de publicación, la fecha de acceso, las unidades y las condiciones de uso. Incluye una plantilla CSV y una lista de comprobación que no requieren código.
Separa los ID duplicados de las respuestas vacías en 10 respuestas sintéticas. Indica el denominador de respuestas válidas y guarda en un archivo nuevo los recuentos y porcentajes por opción, además de los motivos de exclusión.
Documenta qué significa cada columna del conjunto de datos antes del análisis, incluida su unidad, tipo de dato y valores permitidos. Un pequeño conjunto de datos sintético muestra cómo el mismo diccionario también puede servir para una validación automática sencilla.