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

2026-09-20 · exemplo verificado manualmente · alvo: Python 3.12 · biblioteca padrão: csv, pathlib · sem execução

  • Foram contadas manualmente 6 synthetic reference rows, 5 rows com DOI e 1 missing DOI row.
  • R001 e R003 foram normalizados manualmente para o mesmo value, 10.0000/demo.alpha.
  • R005 foi normalizado manualmente de doi:10.0000/DEMO.GAMMA para 10.0000/demo.gamma.
  • Foram calculados 4 distinct normalized DOI values entre as 5 DOI rows não vazias.
  • O expected issue report foi derivado como 2 duplicate-DOI rows mais 1 missing-DOI row.
  • O script foi inspecionado quanto a duplicate ref_id checks, DOI normalization, tratamento separado de missing e duplicate, output collision protection e preservação do CSV original.
Limites da verificação
  • O código não foi executado pelo autor desta resposta; nenhum arquivo CSV de saída foi criado.
  • As synthetic DOI-like strings não foram verificadas quanto a registration ou resolution e não são apresentadas como publicações reais.
  • DOI validity, bibliographic metadata accuracy, title similarity, retractions e related article versions não foram testados.
  • As URLs da documentação oficial foram fornecidas com base em locais de documentação conhecidos, mas não foram verificadas ao vivo.

Princípios de redação e verificação de todo o site

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.