Pesquisa e fontes

Gerencie uma lista de referências em CSV e verifique DOIs duplicados ou ausentes

Mantenha uma pequena lista de referências de pesquisa em CSV, normalize o texto DOI para comparação e gere um arquivo de revisão para valores DOI duplicados e ausentes. O workflow preserva o CSV original e não afirma que um DOI é válido apenas porque está presente.

Ver o sumário

A tradução foi feita com IA. Confira o código, as unidades e os valores junto com o original. A revisão por falantes nativos de cada idioma ainda não foi concluída. English

Para quem éEste guia é voltado a pesquisadores que mantêm listas de referências em planilhas ou arquivos CSV e querem uma verificação simples de DOIs duplicados e ausentes.

Preparação
  • Python 3.12 e um comando de terminal que inicie essa versão.
  • Um editor de texto ou programa de planilhas capaz de salvar arquivos CSV em UTF-8.
  • Uma pasta de trabalho em que o script possa criar uma nova pasta dentro de outputs.
  • É necessária apenas a biblioteca padrão do Python: csv e pathlib.

01Decida o que a verificação de DOI pode e não pode informar

Este workflow verifica dois problemas práticos: uma reference não tem DOI value, ou duas rows contêm o mesmo DOI após uma simple text normalization. Ele não entra em contato com um DOI registry e, portanto, não prova que um DOI existe, resolve corretamente ou pertence ao article citado.

O script remove textual prefixes comuns, como https://doi.org/ e doi:, remove whitespace nas extremidades e converte o DOI text restante para lowercase para comparação. O original DOI text é preservado no output ao lado do normalized value.

02Crie uma lista de referências sintética

A reference list a seguir é sintética e foi criada especificamente para este artigo. As DOI-like strings são dados de demonstração e não são apresentadas como publicações reais ou resolvíveis. Salve o arquivo como references.csv.

csv
ref_id,title,year,doi
R001,Synthetic bracket study,2024,10.0000/demo.alpha
R002,Synthetic vibration study,2025,10.0000/demo.beta
R003,Synthetic bracket study copy,2024,https://doi.org/10.0000/demo.alpha
R004,Synthetic fatigue note,2023,
R005,Synthetic surrogate study,2026,doi:10.0000/DEMO.GAMMA
R006,Synthetic optimization study,2026,10.0000/demo.delta

Há 6 reference rows. Cinco contêm DOI text e uma, R004, não tem DOI. R001 e R003 usam textual forms diferentes, mas são normalizadas para o mesmo value. R005 demonstra que capitalization e um prefix doi: também podem ser normalizados para comparação.

03Determine manualmente os problemas esperados

ref_idOriginal DOINormalized DOIIssue esperado
R00110.0000/demo.alpha10.0000/demo.alphaDUPLICATE_DOI
R00210.0000/demo.beta10.0000/demo.betaNone
R003https://doi.org/10.0000/demo.alpha10.0000/demo.alphaDUPLICATE_DOI
R004MISSING_DOI
R005doi:10.0000/DEMO.GAMMA10.0000/demo.gammaNone
R00610.0000/demo.delta10.0000/demo.deltaNone

As contagens esperadas são 6 references, 5 rows com DOI text, 1 row com missing DOI e 1 duplicate DOI group contendo 2 rows. Após a normalização, as cinco DOI entries não vazias contêm 4 distinct DOI values.

04Normalize o texto DOI e grave arquivos de revisão

Salve o script a seguir como reference_list_check.py. Ele grava uma normalized copy de toda a reference list e um issues.csv separado contendo apenas as rows que precisam de review. O references.csv original é somente leitura.

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()

05Compare o relatório de revisão esperado

issues.csv deve conter três data rows: R001 e R003 pelo normalized DOI duplicado, mais R004 pelo missing DOI.

csv
ref_id,issue,normalized_doi,related_ref_ids
R001,DUPLICATE_DOI,10.0000/demo.alpha,R001;R003
R003,DUPLICATE_DOI,10.0000/demo.alpha,R001;R003
R004,MISSING_DOI,,

A saída esperada do console abaixo foi derivada manualmente a partir da synthetic list e do script. Não é um execution log capturado.

text
References checked: 6.
Missing DOI rows: 1.
Duplicate DOI groups: 1.
Issue rows: 3.
Output folder: outputs/reference_list_result

06Revise as linhas sinalizadas em vez de excluí-las automaticamente

  • Confirme que R001 e R003 são normalizados para o mesmo DOI text.
  • Confirme que R004 é mantido na normalized reference list mesmo sem DOI.
  • Verifique paper title, authors, year e publication details antes de decidir se duplicate-DOI rows devem ser combinadas.
  • Para missing DOI rows, verifique se a publication realmente tem um DOI antes de adicioná-lo.
  • Execute o script novamente sem alterar OUTPUT_DIR. Ele deve parar com FileExistsError em vez de substituir a review anterior.
ProblemaO que verificar
O mesmo paper foi inserido duas vezes com DOI formatting diferenteNormalize DOI URL e prefixes doi: comuns antes da comparação.
DOI em branco tratado como duplicadoMantenha missing DOI detection separada de duplicate DOI grouping.
Papers diferentes compartilham title semelhanteNão use title similarity sozinha como prova de que os records são duplicates.
Uma string com aparência de DOI está presentePresença não prova validity; verifique references importantes com uma trusted metadata source.
Um result existente é sobrescritoUse uma output folder nova para manter a previous review evidence disponível.

07Entenda os limites da deduplicação baseada em DOI

DOI matching é útil quando DOI metadata está presente, mas missing DOI não significa que uma reference seja invalid ou incomplete. Algumas publications ou document types podem não ter DOI, e older records podem exigir outros identifiers ou manual bibliographic checks.

Este script realiza apenas text normalization. Ele não resolve DOI links, consulta Crossref ou outra registration agency, verifica titles ou authors, detecta retractions ou determina se dois DOI values diferentes se referem a related versions de um trabalho.

Para uma research database real, mantenha fields como authors, title, year, journal, volume, pages, DOI e another local reference ID. Trate automatic duplicate detection como uma ajuda para review, e não como uma instrução para excluir bibliographic records.

Registro de execução e verificação

ChatGPT Python tool · Python 3.x · standard library: csv, pathlib

  • A execução foi concluída com sucesso para 6 referências.
  • Foi confirmada 1 linha com DOI ausente: R004.
  • Foi confirmado 1 grupo de DOI duplicado: o DOI normalizado 10.0000/demo.alpha compartilhado por R001 e R003.
  • Foi confirmado que issues.csv contém exatamente 3 linhas de dados: R001 DUPLICATE_DOI, R003 DUPLICATE_DOI e R004 MISSING_DOI.
  • Foi confirmado que R005 foi normalizado de doi:10.0000/DEMO.GAMMA para 10.0000/demo.gamma.
  • Foi confirmado que há 4 valores distintos de DOI normalizado não vazio entre as 5 linhas que contêm DOI.
  • A saída do console correspondeu exatamente ao artigo: 6 referências, 1 linha com DOI ausente, 1 grupo de DOI duplicado, 3 linhas de problemas e a pasta de saída esperada.
  • O issues.csv gerado correspondeu exatamente ao CSV esperado do artigo.
  • A segunda execução gerou FileExistsError para outputs/reference_list_result, confirmando a proteção contra sobrescrita.
  • A comparação SHA-256 confirmou que references.csv permaneceu inalterado.
Limites da verificação
  • O script realiza apenas normalização de texto; registro de DOI, resolução, metadados de título/autor, retratações e correção bibliográfica não foram verificados externamente.
  • O ambiente de execução emitiu no stderr um aviso não relacionado de aquecimento do ambiente de planilhas; isso não afetou o status de saída do script, os arquivos gerados nem os resultados da verificação.

Fontes de referência

As explicações e os exemplos são de elaboração própria. Os comportamentos e conceitos relacionados podem ser consultados nas fontes oficiais abaixo.