[Cibersegurança] Validação de usuários
import pandas as pd
from pathlib import Path
# ================== PARÂMETROS ==================
BASE_DIR = Path(r"C:/automação python") #caminho da pasta
ARQ1 = BASE_DIR / "arq1.xlsx" #arquivo1
ARQ2 = BASE_DIR / "arq2.xlsx" #arquivo2
SHEET1 = "Planilha1"
SHEET2 = "Planilha1"
# Nomes dos cabeçalhos da coluna H em cada arquivo:
COL_CPF_1 = "CPF" # arq1.xlsx (coluna H)
COL_CPF_2 = "CPF" # arq2.xlsx (coluna H)
ARQ_SAIDA = BASE_DIR / "comparacao_cpf.xlsx"
# ============== FUNÇÕES AUXILIARES ==============
def as_text(x):
"""Força leitura como texto, preservando NA."""
if pd.isna(x):
return pd.NA
return str(x)
def ler_planilha_coluna_h_como_texto(caminho, sheet, nome_esperado_h):
"""
Lê a planilha garantindo que a coluna H seja tratada como TEXTO.
1) Tenta o cabeçalho (ex.: 'cpf' ou 'CPF');
2) Se não achar, cai para a coluna H (8ª).
"""
df = pd.read_excel(caminho, sheet_name=sheet, engine="openpyxl")
# localizar coluna pelo cabeçalho (strip)
cols_map = {c: str(c).strip() for c in df.columns}
col_real = None
for c, ct in cols_map.items():
if ct == nome_esperado_h:
col_real = c
break
if col_real is None:
# fallback: coluna H
if df.shape[1] >= 8:
col_real = df.columns[7] # H
print(f"Aviso: no arquivo '{caminho.name}' não achei a coluna '{nome_esperado_h}'. "
f"Usando a coluna H: '{col_real}'.")
else:
raise ValueError(f"No arquivo '{caminho}', não encontrei a coluna '{nome_esperado_h}' "
f"e não há coluna H. Colunas: {list(df.columns)}")
# Releitura forçando a coluna identificada como TEXTO
df2 = pd.read_excel(
caminho,
sheet_name=sheet,
engine="openpyxl",
converters={col_real: as_text}
)
return df2, col_real
def compactar_colunas(df: pd.DataFrame) -> pd.DataFrame:
"""
Remove:
- colunas totalmente vazias (NaN ou apenas espaços),
- colunas cujo cabeçalho começa com 'Unnamed' (típico de colunas vazias no Excel).
"""
out = df.copy()
# normaliza nomes (strip) e remove 'Unnamed'
out.columns = [str(c).strip() if c is not None else "" for c in out.columns]
cols_validas = [c for c in out.columns if not str(c).startswith("Unnamed")]
out = out[cols_validas]
# trata strings em branco como NA e remove colunas totalmente vazias
out = out.replace(r"^\s*$", pd.NA, regex=True).dropna(axis=1, how="all")
return out
def garantir_limite_excel(df: pd.DataFrame, nome: str, chave: str = "CPF_chave") -> pd.DataFrame:
"""
Garante que o número de colunas não passe de 16.384 (limite do Excel).
Se exceder, mantém a chave e trunca o restante.
"""
MAX_COLS = 16384
if df.shape[1] > MAX_COLS:
outras = [c for c in df.columns if c != chave]
keep = [chave] + outras[:MAX_COLS - 1]
print(f"Aviso: '{nome}' tem {df.shape[1]} colunas; truncando para {MAX_COLS}.")
return df[keep]
return df
# ==================== LEITURA ====================
t1, col1 = ler_planilha_coluna_h_como_texto(ARQ1, SHEET1, COL_CPF_1)
t2, col2 = ler_planilha_coluna_h_como_texto(ARQ2, SHEET2, COL_CPF_2)
# Compactar colunas (remove vazias e 'Unnamed')
t1 = compactar_colunas(t1)
t2 = compactar_colunas(t2)
# Cria a chave BRUTA (sem normalização)
t1 = t1.copy()
t2 = t2.copy()
t1["CPF_chave"] = t1[col1]
t2["CPF_chave"] = t2[col2]
# Remove linhas sem chave
t1 = t1[~t1["CPF_chave"].isna()].reset_index(drop=True)
t2 = t2[~t2["CPF_chave"].isna()].reset_index(drop=True)
# DEDUP estritamente pela chave
t1_dedup = t1.drop_duplicates(subset=["CPF_chave"]).reset_index(drop=True)
t2_dedup = t2.drop_duplicates(subset=["CPF_chave"]).reset_index(drop=True)
# ================= COMPARAÇÃO ====================
cpfs_t1 = set(t1_dedup["CPF_chave"])
cpfs_t2 = set(t2_dedup["CPF_chave"])
cpfs_ambas = cpfs_t1 & cpfs_t2
cpfs_so_t1 = cpfs_t1 - cpfs_t2
cpfs_so_t2 = cpfs_t2 - cpfs_t1
# ========== MONTAR DATAFRAMES DE SAÍDA ==========
# Em ambas: TODAS as colunas de cada lado (sufixos para nomes iguais)
ambas = (
pd.merge(
t1_dedup, t2_dedup,
on="CPF_chave",
how="inner",
suffixes=("_T1", "_T2")
)
.sort_values("CPF_chave")
.reset_index(drop=True)
)
so_t1 = (
t1_dedup[t1_dedup["CPF_chave"].isin(cpfs_so_t1)]
.sort_values("CPF_chave")
.reset_index(drop=True)
)
so_t2 = (
t2_dedup[t2_dedup["CPF_chave"].isin(cpfs_so_t2)]
.sort_values("CPF_chave")
.reset_index(drop=True)
)
# Garante limite do Excel (após a limpeza, normalmente já fica OK)
ambas = garantir_limite_excel(ambas, "Em_ambas", "CPF_chave")
so_t1 = garantir_limite_excel(so_t1, "So_Tabela1", "CPF_chave")
so_t2 = garantir_limite_excel(so_t2, "So_Tabela_2", "CPF_chave")
# Resumo
resumo = pd.DataFrame({
"Categoria": ["Em ambas", "Só na Tabela 1", "Só na Tabela 2", "Total distintos"],
"Quantidade": [len(cpfs_ambas), len(cpfs_so_t1), len(cpfs_so_t2), len(cpfs_t1 | cpfs_t2)]
})
# =================== EXPORTAR ===================
with pd.ExcelWriter(ARQ_SAIDA, engine="openpyxl") as writer:
resumo.to_excel(writer, sheet_name="Resumo", index=False)
ambas.to_excel(writer, sheet_name="Em_ambas", index=False)
so_t1.to_excel(writer, sheet_name="So_Tabela1", index=False)
so_t2.to_excel(writer, sheet_name="So_Tabela_2", index=False)
# Formatar CPF_chave como TEXTO em todas as abas de dados
wb = writer.book
def formatar_texto(sheet_name, col_name="CPF_chave"):
ws = wb[sheet_name]
headers = [cell.value for cell in ws[1]]
if col_name in headers:
col_idx = headers.index(col_name) + 1 # 1-based
for row in ws.iter_rows(min_row=2, min_col=col_idx, max_col=col_idx):
for cell in row:
cell.number_format = "@"
for aba in ["Em_ambas", "So_Tabela1", "So_Tabela_2"]:
formatar_texto(aba, "CPF_chave")
print(f"Concluído! Resultado salvo em: {ARQ_SAIDA}")
No Comments