-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsinarc.py
8291 lines (6099 loc) · 343 KB
/
sinarc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#########################################################
# S I N A R C
# Sistema Integrado de Análise de Redes Complexas
#########################################################
#######################
# DESCRIÇÃO DO PROGRAMA
#######################
# O SINARC - Sistema Integrado de Análise de Redes Complexas é um programa open source experimental, em fase de desenvolvimento (versão Alfa),
# destinado à análise computacional exploratória de redes complexas por meio de grafos.
# O objeto de análise do SINARC são as relações existentes entre pessoas físicas, pessoas jurídicas, telefones, endereços e e-mails constantes da base de dados pública de CNPJ da Receita Federal.
# Para sua criação, adotou-se uma metodologia de codificação simplificada, porém funcional, concentrando todo o código Python e Javascript em um único arquivo.
# O objetivo é demonstrar a possibilidade de criação de ferramentas tecnológicas open source que utilizam dados abertos, bem como incentivar o desenvolvimento de projetos semelhantes pela sociedade.
# O SINARC foi desenvolvido a partir do código fonte do projeto Rede CNPJ (https://github.com/rictom/rede-cnpj/).
# O programa realiza o processamento de dados públicos da Recceita Federal previamente tratados e disponibilizados à sociedade para consulta pelo projeto Rede CNPJ e
# gera uma interface gráfica interativa (página web) com recursos para:
# a) exploração visual da rede por meio de um ambiente que combina princípios da Física (gravitação universal) e da Psicologia (percepção visual segundo a Gestalt); e
# b) exploração automática usando algoritmos computacionais que identificam informações ocultas (nós centrais, caminhos mais curtos entre nós etc.) de acordo com a metodologia de análise definida pelo usuário.
# Ao executar o SINARC, o projeto Rede CNPJ também é carregado em outra aba do navegador, permitindo a utilização simultânea dos dois sistemas pelo usuário.
# As ferramentas de análise do SINARC são complementares às disponibilizadas pelo Rede CNPJ.
#####################################
# DESCRIÇÃO RESUMIDA DO FUNCIONAMENTO
#####################################
# A comunicação entre o front-end (Javascript) e o back-end (Python) se dá por meio da função cópia (CTRL + C) do sistema operacional.
# O programa realiza a consulta de parâmetros (CNPJ, nome do sócio, rezão social, nome fantasia etc.) à API, criada pelo Rede CNPJ, usando a biblioteca Requests do Python.
# A partir da resposta JSON do servidor (Flask), o SINARC cria dois dataframes (df_no e df_ligacao) com a biblioteca Pandas do Python.
# Esses dataframes são usados para gerar o grafo com a biblioteca Networkx do Python e aplicar os algoritmos nela disponíveis.
# O grafo no formato Networkx é utlizado pela biblioteca Pyvis do Python (criada sobre a biblioteca vis.js do Javascript) para gerar o arquivo HTML interativo do front-end.
# O arquivo HTML é então alterado via Regex com a inserção de scripts da biblioteca vis.js do Javascript.
# Por fim, o arquivo HTML é aberto com a módulo Webbrowser do Python e exibido em nova aba do navegador.
# O grafo gerado exibe as ligações existentes entre pessoas físicas, pessoas jurídicas, telefones, endereços e e-mails contantes da bases de dados pública de CNPJ da Receita Federal.
##############################################################################
# PROCEDIMENTO PARA GERAR O ARQUÍVO EXECUTÁVEL ÚNICO DO SINARC COM PYINSTALLER
##############################################################################
# 1) Abrir o prompt de comando e navegar até a pasta onde se encontram os arquivos 'sinarc.py' e 'logo.ico'
# 2) Ativar o ambiente virtual criado, onde se encontram o interpretador Python e os módulos instalados usados no programa (conda activate [nome_do_ambiente_virtual])
# 3) Instalar o módulo pyinstaller (https://pyinstaller.org/en/stable/#) no ambiente virtual (pip install pyinstaller)
# 4) Digitar no prompt de comando: pyinstaller -F --icon=logo.ico sinarc.py <<<<<<<<<<<<<<<<<<<<<<<< CÓDIGO PARA INSERIR NO TERMINAL
# É possível substituir logo.ico e sinarc.py pelos endereços completos dos arquivos
# Para gerar o arquivo 'requirements.txt': entrar no ambiente virtual e digitar 'pip freeze > requirements.txt'. O arquivo será criado no diretório local.
# O arquivo 'requirements.txt' contém todos os módulos Python usados pelo SINARC
#################################################################################################################
# PROCEDIMENTO PARA GERAR O ARQUÍVO EXECUTÁVEL (PASTA DE ARQUIVOS) DO SINARC COM INTERFACE GRÁFICA AUTO-PY-TO-EXE
#################################################################################################################
# 1) Abrir o prompt de comando e navegar até a pasta onde se encontram os arquivos 'sinarc.py' e 'logo.ico'
# 2) Ativar o ambiente virtual criado, onde se encontram o interpretador Python e os módulos instalados usados no programa (conda activate [nome_do_ambiente_virtual])
# 3) Instalar o módulo auto-py-to-exe (https://pypi.org/project/auto-py-to-exe/) no ambiente virtual (pip install auto-py-to-exe)
# 4) Digitar no prompt de comando: auto-py-to-exe <<<<<<<<<<<<<<<<<<<<<<<< CÓDIGO PARA INSERIR NO TERMINAL
# 5) Configurar parâmetros de criação usando a interface gráfica
# Obs.: Incluir como arquivos: "sinarc.py" "help.html" "README.txt" "requirements.txt" "arquivo_network_do_pyvis_com_alteracoes.py" "print_screen.png" "exemplo_resposta_pequeno.json" "exemplo_resposta_grande.json" "logo.png" "logo.ico"
# Se a inclusão dos arquivos não funcionar. copiar e colar manualmente na pasta 'output\sinarc'
#############################################################
# SCRIPT PARA GERAR O HASH DO ARQUIVO ZIP A SER COMPARTILHADO
#############################################################
# Criado com o Chat-GPT e NÃO TESTADO!
#import hashlib
#def zip_hash_sha3(file_path):
# sha3 = hashlib.sha3_256()
# with open(file_path, 'rb') as f:
# while True:
# data = f.read(1024)
# if not data:
# break
# sha3.update(data)
# return sha3.hexdigest()
#print(zip_hash_sha3("example.zip"))
# SITE PARA VERIFICAÇÃO DO HASH
# https://emn178.github.io/online-tools/sha256_checksum.html
##########################################################################################
# ALTERAÇÃO NECESSÁRIA NO CÓDIGO DO ARQUIVO network.py DO MÓDULO PYVIS NO AMBIENTE VIRTUAL
##########################################################################################
# Para poder gerar o arquivo executável com o módulo pyinstaller, é necessário alterar
# o arquivo 'network.py', localizado nas pastas 'site-package\pyvis' do ambiente virtual criado (Ex.: C:\Users\<nome_usuario>\.conda\envs\sinarc\Lib\site-packages\pyvis).
# O texto a ser alterado no arquivo 'network.py' tem a função de copiar todo o conteúdo da pasta pyvis que acompanha o programa (extraída do módulo pyvis) e
# colar na pasta temporária criada pelo programa e gerenciada pelo Windows.
# Foram inseridos no arquivo network.py 2 comandos 'print()', que imprimem o local da pasta onde se encontra o arquivo original network.py (source) e a pasta destino (destination).
########### INICIO PARTE ALTERADA DO ARQUIVO network.py ##########
"""
# with tempfile.mkdtemp() as tempdir: # JÁ ESTAVA COMENTADO ANTES DA ALTERAÇÃO
# LINHAS COMENTADAS PELO AUTOR:
#if os.path.exists(f"{tempdir}/lib"):
# shutil.rmtree(f"{tempdir}/lib")
#shutil.copytree(f"{os.path.dirname(__file__)}/templates/lib", f"{tempdir}/lib")
# LINHAS INCLUÍDAS PELO AUTOR:
source = ".\\pyvis"
print('source:', source)
destination = f"{os.path.dirname(__file__)}"
print('destination:', destination)
if not os.path.exists(destination):
shutil.copytree(source, destination)
with open(f"{tempdir}/{name}", "w+") as out:
out.write(self.html)
#webbrowser.open(f"{tempdir}/{name}") # <<<<<<<<< COMENTADO PORQUE ESTAVA ABRINDO O NAVEGADOR INTERNET EXPLORER E NAO O CHROME
"""
########### FINAL PARTE ALTERADA ##########
#################################################
# ALTERAÇÕES REALIZADAS NOS ARQUIVOS DO REDE CNPJ
#################################################
# ARQUIVO: 'SINARC\rede-cnpj-master\rede\rede.ini'
# parametros para o flask-limiter (COMENTADO POR SINARC - PADRÃO)
#limiter_padrao =2/second;20/minute;200/hour;400/day
#limiter_dados =10/second;600/minute
#limiter_arquivos =2/minute;30/hour;100/day
# parametros para o flask-limiter (INCLUÍDO POR SINARC - x1000)
"""
limiter_padrao =2000/second;20000/minute;200000/hour;400000/day
limiter_dados =10000/second;600000/minute
limiter_arquivos =2000/minute;30000/hour;100000/day
"""
# ARQUIVO: SINARC\rede-cnpj-master\rede\rede.py
# app.run(host='0.0.0.0',debug=True, use_reloader=False, port=porta) # Parâmetro 'use_realoader' alterado para 'False'. Em 'True' faz com que uma segunda aba do navegador seja aberta.
# ===============================================
# PARA FUNCIONAR A GEOLOCALIZAÇÃO NO REDE CNPJ
# No arquivo mapa.py, substituir "r = requests.get(url+'&format=json')" por "r = requests.get(url+'&format=json', verify=False)" para evitar erro de SSL
###################
# INFORMAÇÕES ÚTEIS
###################
# PLAYGROUND JS:
# https://jsfiddle.net/tsee1ap0/1/
# https://plnkr.co/edit/zECQ2zQiCcD71oeReEj6?p=preview&preview
# https://stackoverflow.com/questions/43886139/how-to-use-modifiers-with-click-event-in-vis-js
# CONSULTA DE CNPJS POR CIDADES PARA TESTE
# https://cadastroempresa.com.br/
# API CNPJRECEITA FEDERAL - CIDADÃO NÃO PODE CONSULTAR (WHY?)
# https://www.gov.br/conecta/catalogo/apis/consulta-cnpj
# PGFN
# https://www.regularize.pgfn.gov.br/
# SITE REDE CNPJ
# https://www.redecnpj.com.br/rede/
# https://dadosabertos.social/t/dados-relacionados-a-base-do-cnpj-liberados-pela-receita-federal-do-brasil/23
# REQUISIÇÃO FETCH API NO DEV TOOLS - MÉTODO GET
# https://stackoverflow.com/questions/14248296/making-http-requests-using-chrome-developer-tools
# MÉTODO GET:
# fetch('https://viacep.com.br/ws/01001000/json/')
# .then(res => res.json())
# .then(console.log)
# OU
# var a = ''
# fetch('https://viacep.com.br/ws/01001000/json/')
# .then(resposta => {
# return resposta.json()
# })
# .then(corpo => {
# console.log(corpo)
# a = corpo
# });
# MÉTODO POST:
#
#############
# PARA TESTAR
#############
# TESTAR: Atribuir à aresta um tamanho que seja proporcional ao grau do seu nó de origem/destino/ambos (sem precisar alterar as massas)
###############
# PARA CORRIGIR
###############
# Quando se seleciona um nó e clica e arrasta outro nó, na sequência, o nó anteriormente selecionado permanece com texto vermelho (convertido em funcionalidade: mudança da cor do rótulo para vermelho)
#########################################
# LISTA DE COMANDOS DO PROMPT DO ANACONDA
#########################################
# COMO ABRIR O PROMPT DO ANACONDA: NA BARRA DE PESQUISA DO WINDOWS, DIGITE: anaconda prompt
# COMO CRIAR UM ABIENTE VIRTUAL COM O ANACONDA PROMPT: conda create --name myenv
# COMO ATIVAR O AMBIENTE VIRTUAL: conda activate <nome do ambiente virtual>
# COMO DESATIVAR O AMBIENTE VIRTUAL: conda deactivate <nome do ambiente virtual>
# COMO INSTALAR O PIP NO AMBIENTE VIRTUAL CRIADO COM O ANACONDA PROMPT: conda install pip
# COMO VERIFICAR OS MÓDULOS INSTALADOS EM UM AMBIENTE VIRTUAL: conda list
# COMO LISTAR TODOS OS AMBIENTES VIRTUAIS: conda env list
# COMO DELETAR UM AMBIENTE VIRTUAL: conda env remove --name myenv
# DOWNOLOAD PORTABLE VSCODE: https://code.visualstudio.com/Download
#################
# IMPORTA MÓDULOS
#################
# MÓDULOS NATIVOS DO PYTHON
from copy import copy # https://docs.python.org/3/library/copy.html
import json # https://docs.python.org/3/library/json.html
from pprint import pprint # https://docs.python.org/3/library/pprint.html
import time # https://docs.python.org/3/library/time.html
import winsound # https://docs.python.org/pt-br/3.7/library/winsound.html
import re # https://docs.python.org/3/library/re.html
import os # https://docs.python.org/3/library/os.html
import glob # https://docs.python.org/3/library/glob.html
from itertools import combinations # https://docs.python.org/3/library/itertools.html
import webbrowser # https://docs.python.org/3/library/webbrowser.html
from io import BytesIO # https://docs.python.org/3/library/io.html
from datetime import datetime # https://docs.python.org/3/library/datetime.html
from traceback import format_exc # https://docs.python.org/dev/library/traceback.
import shutil # https://docs.python.org/3/library/shutil.html
from zipfile import ZipFile # https://docs.python.org/3/library/zipfile.html
# MÓDULOS EXTERNOS INSTALADOS NO AMBIENTE VIRTUAL
import pyperclip # https://pyperclip.readthedocs.io/en/latest/
import requests # https://requests.readthedocs.io/en/latest/
import pandas as pd # https://pandas.pydata.org/
import numpy as np # https://numpy.org/
import networkx as nx # https://networkx.org/
from pyvis.network import Network # https://networkx.org/documentation/stable/reference/index.html
from PIL import Image # https://python-pillow.org/
from openpyxl import styles # https://openpyxl.readthedocs.io/en/stable/
from openpyxl import load_workbook
from openpyxl.worksheet.table import Table, TableStyleInfo
from openpyxl.utils import get_column_letter
###############################
# IMPRIME PREÂMBULO NO TERMINAL
###############################
print()
print('#' * 56)
print(' S I S T E M A S I N A R C ')
print('#' * 56)
print()
########################
# CONFIGURAÇÕES INICIAIS
########################
# Define título da janela do programa (janela do terminal)
os.system("title S I N A R C - Sistema Integrado de Análise de Redes Complexas")
# Extrai altura e largura da tela do monitor para ajustar a janela do navegador
# O ajuste da altura da área do grafo é realizado apertando-se a TECLA b
cmd = "wmic path Win32_VideoController get CurrentVerticalResolution,CurrentHorizontalResolution"
screen_width, screen_height = tuple(map(int,os.popen(cmd).read().split()[-2::]))
print('Dimensões da tela do monitor:', screen_width, 'x', screen_height)
# Desabilita a mensagem de erro 'InsecureRequestWarning' do módulo requests. Não há necessidade de validação da autenticidade da conexão com o site consultado.
requests.packages.urllib3.disable_warnings()
# Remove os arquivos Excel criados pela execução anterior do programa
files = glob.glob('arquivo_excel*.xlsx')
for file in files:
try:
# Remove arquivo que não esteja aberto
os.remove(file)
except:
pass
# Extrai configuração inicial do arquivo 'config_sinarc.txt'
# Contém as variáveis 'cnpj_inicial' e 'num_inicial_camadas'
configs = {}
with open('config_sinarc.txt') as file:
for line in file:
key, value = line.strip().split('=')
key = key.strip()
value = value.strip()
configs[key] = value
###################
# VARIÁVEIS GLOBAIS
###################
# Variável global criada para tentar resolver erro intermitente ainda não localizado
# A variável 'b' está apresentando o seguinte erro:
# b = '--> ' + node2[:3] + df_no.loc[df_no['id'] == node2, 'descricao'].iloc[0] + f'{situacao}' + f' ({a})'
# UnboundLocalError: local variable 'situacao' referenced before assignment
situacao = ''
# Cria variável global para ser usada nos nomes dos arquivos Excel
num_camadas = None
# Variável global criada para poder ser usada nos nomes dos arquivos Excel
lista_de_nos = None
# Cira variável global para amazenar lista de nós da requisição imediatamente anterior (usado para destacar nós acrescentados pela requisição seguinte)
lista_nos_anterior = None
# Contador de arquivos excel abertos
#c_excel = len(glob.glob('arquivo_excel*.xlsx')) + 1
# Nome do arquivo Excel inicial
nome_do_arquivo = 'arquivo_excel.xlsx'
#
#inserir_borda_vermelha = None
def captura_cnpj():
"""
Utiliza a função CTRL + C do computador para:
a) copiar números de CNPJ nos formatos 00.000.000/0000-00 ou 00000000000000 contiddos no texto selecionado;
b) copiar nome de pessoas físicas e de pessoas jurídicas;
c) copiar lista de 'id' dos nós selecionados diretamente da página HTML do grafo.
Esta função é usada para receber dados da página do grafo. Ela também invoca as duas outras funções do programa (cria_dataframes, gera_grafo).
Funciona de forma permanente no modo de captura, aguardando algum texto para ser copiado.
Retorna None
"""
###################################################################################
# MÓDULO PYPERCLIP
###################################################################################
global num_camadas
global lista_nos_anterior
#global inserir_borda_vermelha
# Variável de controle do beep no início do script
play_beep_inicial = True
# Submete CNPJ da Chocolates Garoto Ltda com 1 camadas (CNPJ para testes) para permitir a exploração da rede camada por camada.
#cnpj_consulta_inicial = '28053619000183***1***' # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Inicia com 1 camadas
cnpj_consulta_inicial = configs['cnpj_inicial'] + '***' + configs['num_inicial_camadas'] + '***'
# Variável global que armazena o algorítmo utilizado para destacar nós centrais
# O usuário pressiona a TECLA z e seleciona o algorítmo desejado que é copiado ('===nome_do_algoritmo===') por script Javascript (front end) e capturado por script Python (back end).
# O back end salva o nome do algorítmo nesta variável global para uso na montagem das redes seguintes
# Para saber mais sobre algorítmos de centralidade, vide: What is Network Centrality? (https://www.youtube.com/watch?v=teRuQnQ3v7o)
#algoritmo = 'betweenness'
# Variável que armazena parâmetros da consulta anterior para uso cumulativo com a próxima consulta à base de dados
#par_anterior = ''
# Variável que controla ativação e desativação do modo de captura
ativa_modo_espera = True
cont_excel = 1
# Inicia loop infinito do modo de captura
while True:
#inserir_borda_vermelha = True
start = time.time()
if ativa_modo_espera == True:
print('\n' * 10)
print('Selecione e copie o texto que contém números de CNPJs nos formatos 00.000.000/0000-00 ou 00000000000000...')
print()
print()
############### FASE DE CAPTURA DO TEXTO ###############
# Copia '' para o clipboard (deixa o clipboard vazio)
if ativa_modo_espera == True:
pyperclip.copy('')
# Beep grave curto tocado para indicar que o programa iniciou funcionamento no modo de captura
if play_beep_inicial == True and ativa_modo_espera == True:
winsound.Beep(600, 300) # (frequência, tempo em ms)
# Copia para o clipboard o CNPJ da Chocolates Garoto Ltda apenas na primeira execução do script
if len(cnpj_consulta_inicial) > 0:
pyperclip.copy(cnpj_consulta_inicial)
cnpj_consulta_inicial = ''
# Monitora o clipboard aguardando até que seja copiado qualquer texto (entra no modo de captura)
# O script para aqui para aguardar um texto ser copiado
text = pyperclip.waitForPaste() # <<<<<<<<<<<<<<<<<<<<<<< PONTO DE ESPERA
# Controle do destaque das arestas coloridas nos caminhos mais curtos
destacar_ligacoes = True
if '***true' in text:
destacar_ligacoes = True
text = text.replace('***true', '***')
elif '***false' in text:
destacar_ligacoes = False
text = text.replace('***false', '***')
#print('destacar_ligações:', destacar_ligacoes)
# Controle de ativação do modo de captura
if 'AAAdesativaAAA' in text and ativa_modo_espera == True:
ativa_modo_espera = False
pyperclip.copy(text[14:]) # Copia id do nó selecionado para a área de transferência
print('Modo de captura desativado')
print()
elif 'AAAativaAAA' in text:
ativa_modo_espera = True
print('Modo de captura ativado')
print()
continue
if ativa_modo_espera == False:
time.sleep(1) # Intervalo entre cada loop quando o modo de captura está desativado
continue
elif ativa_modo_espera == True:
pass
# ABRE ARQUIVOS EXCEL DE NÓS E ARESTAS (O ARQUIVO DE ARESTAS PODE NÃO EXISTIR NO CASO DE NÓS ÚNICOS)
if text == '***ABRE-EXCEL***':
try:
# criando uma cópia do arquivo original (sempre existe)
shutil.copy("arquivo_excel.xlsx", f"arquivo_excel_{num_camadas}_{len(lista_de_nos)}.xlsx")
#try:
# criando uma cópia do arquivo original de arestas (pode não existir)
# shutil.copy("edges.csv", f"edges_{num_camadas}_{len(lista_de_nos)}.csv")
# abrindo a cópia do arquivo de arestas (pode não existir)
# os.system(f"start EXCEL.EXE edges_{num_camadas}_{len(lista_de_nos)}.csv")
#except:
# pass
# abrindo a cópia do arquivo original (sempre existe)
os.system(f"start EXCEL.EXE arquivo_excel_{num_camadas}_{len(lista_de_nos)}.xlsx")
continue
# Se o arquivo estiver aberto, cria outro arquivo com um número de índice ao final (cont_excel)
except:
cont_excel += 1
# criando uma cópia do arquivo original de nós
shutil.copy("arquivo_excel.xlsx", f"arquivo_excel_{num_camadas}_{len(lista_de_nos)}_{cont_excel}.xlsx")
#try:
# criando uma cópia do arquivo original de arestas (pode não existir)
# shutil.copy("edges.csv", f"edges_{num_camadas}_{len(lista_de_nos)}_{cont_excel}.csv")
# abrindo a cópia do arquivo de arestas (pode não existir)
# os.system(f"start EXCEL.EXE edges_{num_camadas}_{len(lista_de_nos)}_{cont_excel}.csv")
#except:
# pass
# abrindo a cópia do arquivo de nós
os.system(f"start EXCEL.EXE arquivo_excel_{num_camadas}_{len(lista_de_nos)}_{cont_excel}.xlsx")
continue
# ABRE ÚLTIMO ARQUIVO HTML SALVO
if text.lower().strip().replace('"', '') == 'abrir':
# Exibe grafo no navegador padrão
webbrowser.open('grafo_final.html', new=1, autoraise=True)
continue
# Indica início da fase de captura de texto
print('#' * 56)
print('# FASE DE CAPTURA DO TEXTO')
print('#' * 56)
print()
# Exibe os primeiros 500 caracteres do texto copiado
print('Texto copiado (500 primeiros caracteres):', text[:500])
print()
# Beep agudo longo avisa que um texto foi copiado
winsound.Beep(1000, 300)
#print(text)
## Hipóteses de erros ocorridos durante testes para PJ_08334818000152***NULL***, PJ_00000000000000
#if ('500 Internal Server Error' in text) or ('O FOI LOCALIZADO NA BASE' in text) or ('URL was not found' in text):
# winsound.Beep(600, 1000)
# winsound.Beep(600, 1000)
# continue
############################################################### INCLUÍDO POR ÚLTIMO
# Verifica se o texto contém nomes de pessoas físicas ou jurídicas e inclui caracteres delimitadores para requisição à API
temp = text.split(' ')
lista_4 = []
for i in temp:
# Número máximo de strings do nome da empresa ou da pessoa física
if len(temp) >= 10:
lista_4 = []
break
i = i.replace('\n', ' ').replace('\r', ' ').replace('#_#', '')
i = i.strip()
# Elimina caracteres especiais
#i = ''.join(e for e in i if e.isalnum())
if len(i) >= 1:
# Se houver CNPJ na string
if len(i) >= 3:
if re.search(r'\d\d\.\d\d\d\.\d\d\d/\d\d\d\d-\d\d', i) != None or re.search(r'\d{14}', i) != None or i[:3] in ['PF_', 'PJ_', 'PE_', 'EN_', 'EM_', 'TE_']:
lista_4 = []
break
# Filtra itens e insere em lista_4
#if (i[0].isupper() and i != '' and i != ' ') or (i in ['e', 'da', 'das', 'do', 'dos', 'de', 'dos', "d'", "D'"]):
#if (i != '' and i != ' ') or (i in ['e', 'da', 'das', 'do', 'dos', 'de', 'dos', "d'", "D'"]):
# lista_4.append(i)
lista_4.append(i)
if lista_4 != []:
temp = ' '.join(lista_4)
nomes_pf_pj = "#_#" + temp + "#_#"
print(nomes_pf_pj)
##############################################################################
# PARA ABERTURA DO MÓDULO D-TALE (NÃO IMPLEMENTADO)
#if text == '***D-TALE***':
#if os.path.exists('nodes.csv'):
# df = pd.read_csv('nodes.csv', sep=';')
# d = dtale.show(df)
# d.open_browser()
#continue
#else:
# print('Arquivo "nodes.csv" não localizado no diretório local.')
# print()
# # PARA SELEÇÃO DE ALGORITMO - TECLA z
# # Extrai padrão da string recebida da função cópia acionada a partir da página do grafo (TECLA z)
# # Vide script Javascript que é acionado pela TECLA z
# temp1 = re.search(r'===(.+)===', text)
# # Verifica se foi passado algum nome de algorítmo e se o texto é uma das opções disponíveis
# if temp1 != None: # Quando o padrão regex não é localizado, o método re.search retorna None
# algoritmo = temp1.group(1)
# if algoritmo in ['betweenness', 'closeness', 'eigenvector']:
# if algoritmo == 'betweenness':
# algoritmo = 'betweenness'
# elif algoritmo == 'closeness':
# algoritmo = 'closeness'
# elif algoritmo == 'eigenvector':
# algoritmo = 'eigenvector'
# print('Algorítmo selecionado:', algoritmo)
# print()
# # Recomeça loop
# continue
############### FASE DE TRATAMENTO DO TEXTO COPIADO (INDEPENDENTEMENTE DO TAMANHO) ###############
# Gera uma lista de strings a partir do texto copiado, usando como caractere separador ' ' (espaço)
# Deve oferecer tratamentos diferentes para os três tipos de textos copiados
# 1) CNPJ copiados do texto:
# 00.000.000/0000-00
# 00000000000000
# 2) IDs dos nós copiados diretamente da página do grafo (começam com prefixo):
# TE_11 33343232
# PJ_12345678901234
# PF_***000000**
# EN_..., EM_..., PE_...
# 3) Consulta livre por meio da digitação dos parâmetros de busca
# Verifica se o usuário solicitou cumulação de pesquisa
#cumular_pesquisa = False
#par_ = re.search(r'AAA(\d)AAA', text)
#if par_ != None:
# if int(par_.group(1)) == 1:
# cumular_pesquisa = True
# text = re.sub('AAA(\d)AAA', '', text)
# Extrai (recorta) o numero de camadas passado pelo texto copiado diretamente da página do grafo (incluído pelo script Javascript), caso existente.
# O script em Javascript da página do grafo copia o texto e acrescenta ao final o padrão '***[número de camadas]***'
# A função cópia serve como meio de comunicação entre o script Javascript e o script Python
num_camadas = re.search(r'\*\*\*(\d+)\*\*\*', text)
if num_camadas != None:
num_camadas = int(num_camadas.group(1))
text = re.sub('\*\*\*(\d+)\*\*\*', '', text)
print('Número de camadas recebido no formato ***[número]*** (requisição inicial ou página do grafo):', num_camadas)
print()
# Substitui 0 por 1
if num_camadas == 0:
num_camadas = 1
# Se não for fornecido número de camadas
if re.search(r'\*\*\*\*\*\*', text):
num_camadas = 1
# Ocorreu um erro em que o parâmetro de busca passado pela TECLA 'o' apresentou a string 'NULL' (em maiúsculas) como número de camadas
# Nessa hipótese, o número de camadas deve ser 1
# Se digitar qualquer coisa diferente de número, faz num_camada == 1
if num_camadas == 'NULL' or num_camadas == 'null' or num_camadas == None:
num_camadas = 1
# Elimina sequência de asteriscos e parâmetros passados pela consulta realizada na página do grafo
text = re.sub('\*\*\*\*\*\*', '', text)
text = text.replace('******', '')
# Substitui um ou mais '\n' por ' ' (espaço)
# Esses critérios de formatação foram definidos a partir de testes práticos
text = re.sub(r'\\n+', ' ', text)
# Substitui um ou mais '\t' por ' '
text = re.sub(r'\\t+', ' ', text)
# Substitui um ou mais '\s' por um único ' '
text = re.sub(r'\s+', ' ', text) # deve ficar por último
# Cria primeira lista de strings usando como separador ' ' (espaço)
# A definição do caractere separador é muito importante, pois é ele quem define os limites da string a ser pesquisada no texto
lista_1 = text.split(' ')
# Elimina itens vazios ou com apenas 1 caractere
lista_1 = [x for x in lista_1 if len(x) > 1]
############### FASE DE CRIAÇÃO DO CONJUNTO DE PALAVRAS (PARÂMETROS DE BUSCA) ###############
# Cria conjunto (set) vazio para armazenar strings únicas
conjunto_parametros = set()
# PARA CNPJ NOS FORMATOS 00.000.000/0000-00 OU 00000000000000
# Loop sobre a primeira lista de strings do texto copiado
for i in lista_1:
############### EXTRAI NÚMEROS DE CNPJ DO TEXTO COPIADO ###############
# Substitui todos os demais caracteres por '' (nada)
i = re.sub(r'[^\d\./-]+', '', i)
# Substitui caracteres quando iniciam a string
i = re.sub(r'^[\./-]+', '', i)
# Substitui caracteres quando terminam a string
i = re.sub(r'[\./-]+$', '', i)
#################################################
# Extrai CNPJ com o padrão 00.000.000/0000-00
cnpj = re.search(r'\d\d\.\d\d\d\.\d\d\d/\d\d\d\d-\d\d', i)
# Adiciona CNPJ ao conjunto de strings únicas
if cnpj != None:
# Elimina caracteres que não sejam números
cnpj = cnpj.group(0).replace('.', '').replace('/', '').replace('-', '')
# Adiciona prefixo 'PJ_' e inclui CNPJ ao conjunto de strings únicas (set)
# O prefixo PJ_ é exigido pela API do site Rede CNPJ
cnpj = 'PJ_' + str(cnpj) # Novo formato de texto submetido ao site Rede CNPJ (o anterior não incluía o prefixo)
conjunto_parametros.add(cnpj)
#inserir_borda_vermelha = False
##################################################
# Verifica se o número possui 14 caracteres (se tiver mais, o regex captura os 14 primeiros - não localizando na base de dados)
if len(i) == 14:
# Extrai CNPJ com o padrão 00000000000000
cnpj = re.search(r'\d{14}', i)
# Adiciona prefixo 'PJ_' e inclui CNPJ ao conjunto de strings únicas (set)
if cnpj != None:
cnpj = cnpj.group(0)
cnpj = 'PJ_' + str(cnpj)
conjunto_parametros.add(cnpj)
#inserir_borda_vermelha = False
######################################################
# PARA NÓS SELECIONADOS NA PÁGINA DO GRAFO - TECLA o
# Cria segunda lista de strings a partir dos IDs copiados usando como separador ',' (vírgula)
lista_2 = text.split(',')
cont = 0
#destacar_ligacoes = True
# Loop sobre itens da segunda lista de strings
for i in lista_2:
# Elimina espaço inicial nos itens copiados da página do grafo
i = i.strip()
# Verifica se a string foi copiada da página do grafo (padrões únicos identificados pelos 3 primeiros caracteres)
# O número de camadas foi extraído no passo anterior
# Exemplo: "PJ_09251692000115, PJ_08283040000108" (#_#12345678;12334567#_#***2*** < PODE SER CONSULTADO VIA TECLA s)
if i[:3] == 'PF_' or i[:3] == 'PJ_' or i[:3] == 'PE_' or i[:3] == 'EN_' or i[:3] == 'EM_' or i[:3] == 'TE_':
# Elimina as aspas
i = i.replace('"', '')
# Adiciona à lista de strings únicas (set). Como já está no formato correto para consulta, não precisa formatar.
conjunto_parametros.add(i)
# Destaca ligações (arestas) entre nós copiados da página do grafo somente se apenas dois nós forem copiados
# Quando os CNPJ são copiados do texto, essa variável é sempre 'True'
# TRECHO COMENTADO PARA DESTACAR AS LIGAÇÕES DE QUALQUER QUANTIDADE DE NÓS SELECIONADOS NO GRAFO. *** EM TESTE ***
#cont += 1
#if cont == 2:
# destacar_ligacoes = True
#else:
# destacar_ligacoes = False
#continue
######################################################
# PARA CONSULTA LIVRE - TECLA s
# Cria terceira lista de strings a partir do parâmetro de busca digitado pelo usuário na página do grafo
lista_3 = re.search(r"#_#(.+)#_#", text)
if lista_3 != None:
lista_3 = lista_3.group(1)
lista_3 = lista_3.split(';')
# Loop sobre itens da terceira lista
for i in lista_3:
# Elimina espaço inicial nos itens copiados da página do grafo
# ATENÇÃO! A ausência de tratamento do parâmetro recebido pode gerar erro na pesquisa
i = i.strip()
# Adiciona à lista de strings únicos (set)
conjunto_parametros.add(i)
continue
####################################################### INCLUÍDO POR ÚLTIMO
if lista_4 != []:
lista_3 = re.search(r"#_#(.+)#_#", nomes_pf_pj)
if lista_3 != None:
lista_3 = lista_3.group(1)
lista_3 = lista_3.split(';')
# Loop sobre itens da terceira lista
for i in lista_3:
# Elimina espaço inicial nos itens copiados da página do grafo
# ATENÇÃO! A ausência de tratamento do parâmetro recebido pode gerar erro na pesquisa
i = i.strip()
# Adiciona à lista de strings únicos (set)
conjunto_parametros.add(i)
continue
#######################################################
# PARA OBTENÇÃO DE CNPJ ALEATÓRIO NA CONSULTA LIVRE (TESTE)
if text.lower() == 'teste':
conjunto_parametros = {'TESTE'}
num_camadas = 1
# Exibe parâmetros da consulta e número de camadas a ser solicitado à API
print('Conjunto de parâmetros para consulta:', conjunto_parametros)
print('Nº de camadas:', num_camadas)
############### PREPARA DADOS PARA REQUISIÇÃO À API DO SITE REDE CNPJ ###############
# Verifica se foi identificado algum CNPJ
if len(conjunto_parametros) > 0:
# Reproduz beep grave longo para indica a existência de CNPJ no texto selecionado
winsound.Beep(600, 1000)
# imprime lista de CNPJ no terminal
print()
print('CNPJs e Ids identificados:', len(conjunto_parametros))
if len(conjunto_parametros) > 0:
print(conjunto_parametros)
print()
# Gera string com CNPJs no formato correto para envio ao site Rede CNPJ (separador ';')
parametro = ';'.join(conjunto_parametros)
# Define número de camadas caso não seja fornecido pelo usuário
if num_camadas == None:
num_camadas = 1
# Verifica se o usuário solicitou consulta cumulativa via tecla o
#if cumular_pesquisa == True and par_anterior != '':
# parametro = parametro + ';' + par_anterior
#par_anterior = copy(parametro)
print('Parametros de requisição:', parametro)
print('Nº de camadas:', num_camadas)
print()
############### REQUISITA DADOS À API DO SITE REDE CNPJ E CRIA DATAFRAMES ###############
# Indica início da fase de captura de texto
print('#' * 56)
print('# FASE DE REQUISIÇÃO E DE CRIAÇÃO DOS DATAFRAMES')
print('#' * 56)
print()
############## ATUALIZA HISTÓRICO DE PARÂMETROS DE CONSULTAS ###############
with open('historico_de_consultas.txt', 'a') as f:
now = datetime.now()
agora = now.strftime(r"%A %d/%m/%Y %H:%M:%S")
temp = agora + ' - ' + parametro + ' - ' + str(num_camadas) + '\n'
f.write(temp)
# Invoca função que requisita dados ao site Rede CNPJ e cria dataframes
# recebendo como parâmetros as variáveis 'parametro' (parâmetros de busca separados por ';') e 'num_camadas' (número de camadas)
df_no, df_ligacao = cria_dataframes(parametro, num_camada=num_camadas)
# A não localização do parâmetro de busca retorna dataframes vazios. Reinicia loop infinito.
if df_no.empty and df_ligacao.empty:
print('Parâmetro de pesquisa não localizado na base de dados.')
winsound.Beep(600, 300)
print()
continue
# Parâmetro localizado!
else:
############### GERA GRAFO ###############
# Indica início da fase de geração do grafo
print('#' * 56)
print('# FASE DE GERAÇÃO DO GRAFO')
print('#' * 56)
print()
# Invoca função que gera grafo
gera_grafo(parametro, num_camadas, lista_3, destacar_ligacoes=destacar_ligacoes, df_no=df_no, df_ligacao=df_ligacao)
# Ativa beep, caso tenha sido desativado por não ter sido identificado CNPJ na consulta anterior (vide cláusula else abaixo)
play_beep_inicial = True
# Caso o texto copiado não contenha número de CNPJ ou itens capturados da página do grafo
else:
# Imprime texto copiado e número de camadas no caso do texto não conter CNPJ nos formatos corretos
print()
print('Texto copiado (CNPJ não identificados):', text[:500])
print('Nº de camadas (CNPJ não identificados):', num_camadas)
# Indica a inexistência de CNPJ no texto selecionado por meio de 2 beeps curtos
winsound.Beep(600, 300)
winsound.Beep(600, 300)
# Desativa o beep inicial para não ter um terceiro beep (o retorno sonoro é padronizado)
play_beep_inicial = False
print()
stop = time.time() - start
print('Tempo de execução:', round(stop, 2))
# Variável global usada dentro da função 'cria_dataframe' (em teste)
mensagem = ''
def cria_dataframes(parametro, num_camada=''):
"""
Requisita parâmetros de busca e número de camadas à API do site Rede CNPJ (www.redecnpj.com.br).
Recebe como parâmetros os itens a serem pesquisados, separados por ';', e o número de camadas.
Retorna 2 dataframes: um dataframe com os nós (df_no) e outro com as arestas (df_ligacao).
"""
#global c_excel
global nome_do_arquivo
###################################################################################
# MÓDULOS REQUESTS E PANDAS
###################################################################################
if len(parametro.split(';')) > 1:
# Gera lista 'final' com parâmetros para submeter à API do site Rede CNPJ
lista_cnpj = parametro.split(';')
final = [x.replace('\n', '') for x in lista_cnpj]
else:
final = [parametro]
# BAIXA DADOS DO SITE REDE CNPJ E CRIA DATAFRAMES DE NÓS (df_no) E DE ARESTAS (df_ligacao)
# Cria dataframes vazios
df_no = pd.DataFrame()
df_ligacao = pd.DataFrame()
contador = 1
cnpj_nao_localizados = []
for i in final:
# Verifica se o item é e-mail para deixar em letras minúsculas (exigido pela API)
if i[:3].upper() == 'EM_':
temp = i[3:].lower()
i = 'EM_' + temp
# Caso contrário, converte para maiúsculas (exigidos pela API)
else:
i = i.upper()
#########################################
# HEADERS PARA VERSÃO ONLINE DA REDE CNPJ
#########################################
# Request code / headers da consulta à API do site Rede CNPJ obtidos por meio dos seguintes passos:
# Chrome Browser -> inspect -> Network -> Fetch/XHR -> Select desired file -> Copy all as cURL bash -> Colar em https://curlconverter.com/
# headers = {
# 'Accept': '*/*',
# 'Accept-Language': 'en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7',
# 'Connection': 'keep-alive',
# # Already added when you pass json=
# # 'Content-type': 'application/json',
# 'Origin': 'https://www.redecnpj.com.br',
# 'Referer': 'https://www.redecnpj.com.br/rede/',
# 'Sec-Fetch-Dest': 'empty',
# 'Sec-Fetch-Mode': 'cors',
# 'Sec-Fetch-Site': 'same-origin',
# 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
# 'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"',
# 'sec-ch-ua-mobile': '?0',
# 'sec-ch-ua-platform': '"Windows"',
# }
############################################
# HEADERS PARA INSTALAÇÃO LOCAL DO REDE CNPJ