← Files Pesquisa MirandasTechARCHIVED FILE
scripts/auditar_biblioteca.py
3.59 KB · Oct 3, 2026 · 06:34 UTC
#!/usr/bin/env python3
"""Auditoria da biblioteca Zotero pela API local (Zotero 7+ aberto, com «Permitir que outros
aplicativos neste computador se comuniquem com o Zotero» ligado em Configurações → Avançado).
Aponta o que sai errado na lista de referências: itens sem DOI, sem PDF, capturados como
«Página da web», títulos em CAIXA ALTA, sem autor, sem ano, e prováveis duplicados.
Só lê; não altera nada. Uso:
python3 auditar_biblioteca.py # biblioteca inteira
python3 auditar_biblioteca.py --colecao "Revisao sistematica 2026"
python3 auditar_biblioteca.py --csv auditoria.csv
"""
import argparse, csv, json, re, sys, urllib.parse, urllib.request
BASE="http://127.0.0.1:23119/api/users/0"
def get(path, **q):
url=f"{BASE}{path}?{urllib.parse.urlencode(q)}"
try:
with urllib.request.urlopen(url, timeout=60) as r: return json.load(r)
except Exception as e:
sys.exit(f"Não consegui falar com o Zotero em {BASE} ({e}).\nAbra o Zotero e ligue a API local em Configurações → Avançado.")
def todos(path, **q):
out=[]; start=0
while True:
lote=get(path, limit=100, start=start, **q)
out+=lote
if len(lote)<100: return out
start+=100
ap=argparse.ArgumentParser(); ap.add_argument("--colecao"); ap.add_argument("--csv"); a=ap.parse_args()
path="/items/top"
if a.colecao:
cols=[c for c in todos("/collections") if c["data"]["name"].lower()==a.colecao.lower()]
if not cols: sys.exit(f"Coleção não encontrada: {a.colecao}")
path=f"/collections/{cols[0]['key']}/items/top"
itens=todos(path)
filhos={}
for it in todos("/items"):
p=it["data"].get("parentItem")
if p: filhos.setdefault(p,[]).append(it["data"])
CITAVEIS={"journalArticle","book","bookSection","thesis","conferencePaper","report","preprint","standard","statute","document","manuscript","dataset","patent","magazineArticle","newspaperArticle"}
problemas=[]; vistos={}
for it in itens:
d=it["data"]; tipo=d["itemType"]; titulo=(d.get("title") or "").strip(); k=it["key"]; probs=[]
if tipo in ("attachment","note"): probs.append("anexo/nota sem item pai")
if tipo=="webpage": probs.append("capturado como Página da web")
if not titulo: probs.append("sem título")
letras=[c for c in titulo if c.isalpha()]
if len(letras)>12 and sum(c.isupper() for c in letras)/len(letras)>0.8: probs.append("título em CAIXA ALTA")
if tipo in CITAVEIS and not d.get("creators"): probs.append("sem autor")
if tipo in CITAVEIS and not re.search(r"\d{4}", d.get("date","") or ""): probs.append("sem ano")
if tipo in ("journalArticle","conferencePaper","preprint","dataset") and not (d.get("DOI") or "").strip(): probs.append("sem DOI")
doi=(d.get("DOI") or "").strip()
if doi and not re.match(r"^10\.\S+/\S+$", doi): probs.append("DOI mal formado (deve ser só 10.xxxx/…)")
tem_pdf=any(f.get("contentType")=="application/pdf" for f in filhos.get(k,[]))
if tipo in CITAVEIS and not tem_pdf: probs.append("sem PDF anexado")
chave=(doi.lower() if doi else re.sub(r"\W+","",titulo.lower()))
if chave:
if chave in vistos: probs.append(f"provável duplicado de {vistos[chave]}")
else: vistos[chave]=k
if probs: problemas.append((k,tipo,titulo[:70],"; ".join(probs)))
print(f"{len(itens)} itens auditados · {len(problemas)} com pendências\n")
for k,tipo,titulo,p in problemas: print(f"[{k}] {tipo:16s} {titulo}\n → {p}")
if a.csv:
with open(a.csv,"w",newline="",encoding="utf-8") as f:
w=csv.writer(f); w.writerow(["chave","tipo","titulo","pendencias"]); w.writerows(problemas)
print(f"\ncsv: {a.csv}")
SHA-256: 9d31d4b3c749ff62f5024bcebcd824e6370bd7dd2752197b9c8742a89216dacf