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 código 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.
La ejecución terminó correctamente para 6 referencias.
Se confirmó 1 fila con DOI faltante: R004.
Se confirmó 1 grupo de DOI duplicado: el DOI normalizado 10.0000/demo.alpha compartido por R001 y R003.
Se confirmó que issues.csv contiene exactamente 3 filas de datos: R001 DUPLICATE_DOI, R003 DUPLICATE_DOI y R004 MISSING_DOI.
Se confirmó que R005 se normalizó de doi:10.0000/DEMO.GAMMA a 10.0000/demo.gamma.
Se confirmó que hay 4 valores distintos de DOI normalizado no vacío entre las 5 filas que contienen DOI.
La salida de consola coincidió exactamente con el artículo: 6 referencias, 1 fila con DOI faltante, 1 grupo de DOI duplicado, 3 filas de incidencias y la carpeta de salida esperada.
El issues.csv generado coincidió exactamente con el CSV esperado del artículo.
La segunda ejecución produjo FileExistsError para outputs/reference_list_result, confirmando la protección contra sobrescritura.
La comparación SHA-256 confirmó que references.csv permaneció sin cambios.
Límites de la verificación
El script solo realiza normalización de texto; no se verificaron externamente el registro o resolución de DOI, los metadatos de título o autor, las retractaciones ni la corrección bibliográfica.
El entorno de ejecución emitió en stderr una advertencia no relacionada del calentamiento del entorno de hojas de cálculo; no afectó al estado de salida del script, a los archivos generados ni a los resultados de verificación.
Fuentes de referencia
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.