├── schema ├── cnae_secundaria.csv ├── cnae_cnpj.csv ├── holding.csv ├── cnae.csv ├── socio.csv └── empresa.csv ├── schema-full ├── cnae_secundaria.csv ├── cnae_cnpj.csv ├── holding.csv ├── cnae.csv ├── socio.csv └── empresa.csv ├── requirements-development.txt ├── .github └── FUNDING.yml ├── requirements.txt ├── sql ├── 01-create-pks.sql ├── 00-create-tables.sql ├── 02-create-fks.sql ├── 03-create-indexes.sql └── 04-create-old-views.sql ├── Makefile ├── .gitignore ├── check_pais_socio.py ├── headers ├── header.csv ├── trailler.csv ├── cnae_secundaria.csv ├── socio.csv └── empresa.csv ├── extract_cnae_cnpj.py ├── import-postgresql.sh ├── run.sh ├── natureza_juridica.py ├── extract_holding.py ├── qualificacao-socio.csv ├── historia-do-dataset.md ├── data └── natureza-juridica.csv ├── cnae.py ├── test_parser.py ├── LICENSE ├── README.md └── extract_dump.py /schema/cnae_secundaria.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | cnpj,text 3 | cnae,integer 4 | -------------------------------------------------------------------------------- /schema-full/cnae_secundaria.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | cnpj,text 3 | cnae,integer 4 | -------------------------------------------------------------------------------- /requirements-development.txt: -------------------------------------------------------------------------------- 1 | autoflake 2 | black 3 | flake8 4 | ipython 5 | isort 6 | pytest 7 | -------------------------------------------------------------------------------- /schema/cnae_cnpj.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | cnpj,text 3 | cnae,integer 4 | primaria,bool 5 | -------------------------------------------------------------------------------- /schema-full/cnae_cnpj.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | cnpj,text 3 | cnae,integer 4 | primaria,bool 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://apoia.se/brasilio 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | git+https://github.com/turicas/rows.git@develop#egg=rows 3 | lxml 4 | requests 5 | requests-cache 6 | scrapy 7 | tqdm==4.66.3 8 | -------------------------------------------------------------------------------- /sql/01-create-pks.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE empresa ADD CONSTRAINT pk_empresa_id PRIMARY KEY (cnpj); 2 | ALTER TABLE cnae ADD CONSTRAINT pk_cnae_id PRIMARY KEY (id); 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | lint: 2 | autoflake --in-place --recursive --remove-unused-variables --remove-all-unused-imports . 3 | isort . 4 | black -l 120 . 5 | flake8 6 | 7 | .PHONY: lint 8 | -------------------------------------------------------------------------------- /schema/holding.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | holding_cnpj,text 3 | holding_razao_social,text 4 | cnpj,text 5 | razao_social,text 6 | codigo_qualificacao_socia,integer 7 | qualificacao_socia,text 8 | -------------------------------------------------------------------------------- /schema-full/holding.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | holding_cnpj,text 3 | holding_razao_social,text 4 | cnpj,text 5 | razao_social,text 6 | codigo_qualificacao_socia,integer 7 | qualificacao_socia,text 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | *.pyc 3 | *~ 4 | .*.sw? 5 | .DS_Store 6 | .activate 7 | .coverage 8 | .directory 9 | .env 10 | .idea/* 11 | .tox 12 | MANIFEST 13 | build/* 14 | dist/* 15 | download.sh 16 | data/* 17 | reg_settings.py 18 | -------------------------------------------------------------------------------- /check_pais_socio.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from collections import Counter 3 | 4 | import rows 5 | from tqdm import tqdm 6 | 7 | reader = csv.DictReader(rows.utils.open_compressed("data/output/socio.csv.gz")) 8 | paises = Counter((row["codigo_pais"], row["nome_pais"]) for row in tqdm(reader)) 9 | print(paises.most_common()) 10 | -------------------------------------------------------------------------------- /schema/cnae.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | id_subclasse,text 3 | descricao_subclasse,text 4 | id_classe,text 5 | descricao_classe,text 6 | id_grupo,text 7 | descricao_grupo,text 8 | id_divisao,text 9 | descricao_divisao,text 10 | id_secao,text 11 | descricao_secao,text 12 | notas_explicativas,text 13 | url,text 14 | id,integer 15 | versao,text 16 | -------------------------------------------------------------------------------- /schema-full/cnae.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | id_subclasse,text 3 | descricao_subclasse,text 4 | id_classe,text 5 | descricao_classe,text 6 | id_grupo,text 7 | descricao_grupo,text 8 | id_divisao,text 9 | descricao_divisao,text 10 | id_secao,text 11 | descricao_secao,text 12 | notas_explicativas,text 13 | url,text 14 | id,integer 15 | versao,text 16 | -------------------------------------------------------------------------------- /schema/socio.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | cnpj,text 3 | identificador_de_socio,integer 4 | nome_socio,text 5 | cnpj_cpf_do_socio,text 6 | codigo_qualificacao_socio,integer 7 | percentual_capital_social,integer 8 | data_entrada_sociedade,date 9 | cpf_representante_legal,text 10 | nome_representante_legal,text 11 | codigo_qualificacao_representante_legal,integer 12 | -------------------------------------------------------------------------------- /schema-full/socio.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | cnpj,text 3 | identificador_de_socio,integer 4 | nome_socio,text 5 | cnpj_cpf_do_socio,text 6 | codigo_qualificacao_socio,integer 7 | percentual_capital_social,integer 8 | data_entrada_sociedade,date 9 | codigo_pais,integer 10 | nome_pais,text 11 | cpf_representante_legal,text 12 | nome_representante_legal,text 13 | codigo_qualificacao_representante_legal,integer 14 | -------------------------------------------------------------------------------- /headers/header.csv: -------------------------------------------------------------------------------- 1 | start_column,name,size,type,description 2 | 1,TIPO DE REGISTRO,1,A,CONTEM O VALOR 0 PARA IDENTIFICAR O REGISTRO HEADER 3 | 2,FILLER,16,A,BRANCOS 4 | 18,NOME DO ARQUIVO,11,A,NOME DO ARQUIVO: F.KXXXXXA 5 | 29,DATA DE GRAVAÇÃO,8,N,DATA DE GRAVAÇÃO DO ARQUIVO NO FORMATO ‘AAAAMMDD’ 6 | 37,NUMERO DA REMESSA,8,N,"PARTINDO DE ‘00000001’, DEVENDO SER INCREMENTADO DE 1 A CADA NOVO ARQUIVO (quando exceder 99999999 retornar a 000001)" 7 | 45,FILLER,1155,A,BRANCOS 8 | 1200,FIM,1,A,CONTEÚDO 'F' - INDICA FINAL DE REGISTRO 9 | -------------------------------------------------------------------------------- /headers/trailler.csv: -------------------------------------------------------------------------------- 1 | start_column,name,size,type,description 2 | 1,TIPO DE REGISTRO,1,A,CONTEM O VALOR 9 PARA IDENTIFICAR O REGISTRO HEADER 3 | 2,FILLER,16,A,NORMALIZADO COM '99999999999999' 4 | 18,TOTAL DE REGISTROS T1,9,N,TOTAL DE REGISTROS TIPO 1 DO ARQUIVO 5 | 27,TOTAL DE REGISTROS T2,9,N,TOTAL DE REGISTROS TIPO 2 DO ARQUIVO 6 | 36,TOTAL DE REGISTROS T3,9,N,TOTAL DE REGISTROS TIPO 3 DO ARQUIVO 7 | 45,TOTAL DE REGISTROS,11,N,"TOTAL DE REGISTROS DO ARQUIVO, INCLUINDO REGISTROS HEADER/TRAILER" 8 | 56,FILLER,1144,C,BRANCOS 9 | 1200,FIM,1,A,CONTEÚDO 'F' - INDICA FINAL DE REGISTRO 10 | -------------------------------------------------------------------------------- /sql/00-create-tables.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS cnae; 2 | CREATE TABLE cnae AS SELECT * FROM cnae_23; 3 | INSERT INTO cnae SELECT c22.* FROM cnae_22 AS c22 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c22.id); 4 | INSERT INTO cnae SELECT c21.* FROM cnae_21 AS c21 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c21.id); 5 | INSERT INTO cnae SELECT c20.* FROM cnae_20 AS c20 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c20.id); 6 | INSERT INTO cnae SELECT c11.* FROM cnae_11 AS c11 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c11.id); 7 | INSERT INTO cnae SELECT c10.* FROM cnae_10 AS c10 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c10.id); 8 | -------------------------------------------------------------------------------- /sql/02-create-fks.sql: -------------------------------------------------------------------------------- 1 | -- CNPJ 2 | ALTER TABLE cnae_cnpj ADD CONSTRAINT fk_cnae_cnpj_cnpj FOREIGN KEY (cnpj) REFERENCES empresa (cnpj); 3 | ALTER TABLE holding ADD CONSTRAINT fk_holding_cnpj FOREIGN KEY (cnpj) REFERENCES empresa (cnpj); 4 | ALTER TABLE holding ADD CONSTRAINT fk_holding_holding_cnpj FOREIGN KEY (holding_cnpj) REFERENCES empresa (cnpj); 5 | ALTER TABLE socio ADD CONSTRAINT fk_socio_cnpj FOREIGN KEY (cnpj) REFERENCES empresa (cnpj); 6 | 7 | -- CNAE 8 | ALTER TABLE empresa ADD CONSTRAINT fk_empresa_cnae_fiscal FOREIGN KEY (cnae_fiscal) REFERENCES cnae (id); 9 | ALTER TABLE cnae_cnpj ADD CONSTRAINT fk_cnae_cnpj_cnae FOREIGN KEY (cnae) REFERENCES cnae (id); 10 | -------------------------------------------------------------------------------- /schema/empresa.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | cnpj,text 3 | identificador_matriz_filial,integer 4 | razao_social,text 5 | nome_fantasia,text 6 | situacao_cadastral,integer 7 | data_situacao_cadastral,date 8 | motivo_situacao_cadastral,integer 9 | nome_cidade_exterior,text 10 | codigo_natureza_juridica,integer 11 | data_inicio_atividade,date 12 | cnae_fiscal,integer 13 | descricao_tipo_logradouro,text 14 | logradouro,text 15 | numero,text 16 | complemento,text 17 | bairro,text 18 | cep,integer 19 | uf,text 20 | codigo_municipio,integer 21 | municipio,text 22 | ddd_telefone_1,text 23 | ddd_telefone_2,text 24 | ddd_fax,text 25 | qualificacao_do_responsavel,integer 26 | capital_social,decimal 27 | porte,integer 28 | opcao_pelo_simples,bool 29 | data_opcao_pelo_simples,text 30 | data_exclusao_do_simples,text 31 | opcao_pelo_mei,bool 32 | situacao_especial,text 33 | data_situacao_especial,text 34 | -------------------------------------------------------------------------------- /headers/cnae_secundaria.csv: -------------------------------------------------------------------------------- 1 | start_column,name,size,type,description 2 | 1,TIPO DE REGISTRO,1,A,CONTEM O VALOR 6 PARA IDENTIFICAR AS CNAES SECUNDÁRIAS 3 | 2,INDICADOR-FULL-DIARIO,1,A,"INDICA FORMA DE ENVIO DO ARQUIVO: 4 | F – QUANDO FULL 5 | D – QUANDO DIÁRIO 6 | M – QUANDO MENSAL 7 | T – QUANDO TRIMEST" 8 | 3,TIPO DE ATUALIZACAO,1,A,"NO CASO DE ENVIO FULL ESTE CAMPO ESTA EM BRANCO. NO CASO DE ENVIO PERÍODICO, TERÁ OS SEGUINTES DOMÍNIOS: 9 | A – ATUALIZAÇÃO DO ESTABELECIMENTO 10 | I – INCLUSÃO DE UM NOVO ESTABELECIMENTO 11 | E – EXCLUSÃO DO ESTABELECIMENTO" 12 | 4,CNPJ,14,A,CONTEM O NÚMERO DE INSCRIÇÃO NO CNPJ (CADASTRO NACIONAL DA PESSOA JURÍDICA). 13 | 18,CNAE,693,A,"TAMANHO DE CADA CNAE SECUNDÁRIA: 7 14 | OCORRÊNCIA =99. COMO SE TRATA DE UM ATRIBUTO OPCIONAL, QUANDO UMA DAS OCORRENCIAS NÃO FOR INFORMADA, ESTE ATRIBUTO ESTARA PREENCHIDO COM ZEROS." 15 | 711,FILLER,489,A,BRANCOS 16 | 1200,FIM,1,A,PREENCHIDO COM ‘F’ INDICANDO FINAL DE REGISTRO 17 | -------------------------------------------------------------------------------- /schema-full/empresa.csv: -------------------------------------------------------------------------------- 1 | field_name,field_type 2 | cnpj,text 3 | identificador_matriz_filial,integer 4 | razao_social,text 5 | nome_fantasia,text 6 | situacao_cadastral,integer 7 | data_situacao_cadastral,date 8 | motivo_situacao_cadastral,integer 9 | nome_cidade_exterior,text 10 | codigo_pais,integer 11 | nome_pais,text 12 | codigo_natureza_juridica,integer 13 | data_inicio_atividade,date 14 | cnae_fiscal,integer 15 | descricao_tipo_logradouro,text 16 | logradouro,text 17 | numero,text 18 | complemento,text 19 | bairro,text 20 | cep,integer 21 | uf,text 22 | codigo_municipio,integer 23 | municipio,text 24 | ddd_telefone_1,text 25 | ddd_telefone_2,text 26 | ddd_fax,text 27 | correio_eletronico,text 28 | qualificacao_do_responsavel,integer 29 | capital_social,decimal 30 | porte,integer 31 | opcao_pelo_simples,bool 32 | data_opcao_pelo_simples,text 33 | data_exclusao_do_simples,text 34 | opcao_pelo_mei,bool 35 | situacao_especial,text 36 | data_situacao_especial,text 37 | -------------------------------------------------------------------------------- /extract_cnae_cnpj.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import csv 3 | 4 | import rows 5 | from tqdm import tqdm 6 | 7 | 8 | def main(): 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument("empresa_csv_filename") 11 | parser.add_argument("cnae_secundaria_csv_filename") 12 | parser.add_argument("output_csv_filename") 13 | args = parser.parse_args() 14 | 15 | writer = rows.utils.CsvLazyDictWriter(args.output_csv_filename) 16 | 17 | fobj = rows.utils.open_compressed(args.empresa_csv_filename) 18 | reader = csv.DictReader(fobj) 19 | for row in tqdm(reader): 20 | writer.writerow({"cnpj": row["cnpj"], "cnae": row["cnae_fiscal"], "primaria": "t"}) 21 | fobj.close() 22 | 23 | fobj = rows.utils.open_compressed(args.cnae_secundaria_csv_filename) 24 | reader = csv.DictReader(fobj) 25 | for row in tqdm(reader): 26 | writer.writerow({"cnpj": row["cnpj"], "cnae": row["cnae"], "primaria": "f"}) 27 | fobj.close() 28 | 29 | writer.close() 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /import-postgresql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCHEMA_PATH="schema" 4 | OUTPUT_PATH="data/output" 5 | 6 | function import_table() { 7 | tablename="$1" 8 | 9 | echo "DROP TABLE IF EXISTS ${tablename};" | psql "$POSTGRESQL_URI" 10 | time rows pgimport \ 11 | --schema="$SCHEMA_PATH/${tablename}.csv" \ 12 | --input-encoding="utf-8" \ 13 | --dialect="excel" \ 14 | "$OUTPUT_PATH/${tablename}.csv.gz" \ 15 | "$POSTGRESQL_URI" \ 16 | "$tablename" 17 | } 18 | 19 | function import_cnae_tables() { 20 | schema="cnae" 21 | for filename in $OUTPUT_PATH/cnae_1*.csv* $OUTPUT_PATH/cnae_2*.csv*; do 22 | versao="$(basename $filename | sed 's/.csv.*//; s/cnae_//; s/\.//')" 23 | filename=$(basename $filename | sed 's/.csv.gz//') 24 | import_table $filename $schema "cnae_$versao" 25 | done 26 | } 27 | 28 | function execute_sql_files() { 29 | ## Execute SQL queries in sql/*.sql 30 | for filename in sql/*.sql; do 31 | echo "Executing ${filename}..." 32 | time cat $filename | psql $POSTGRESQL_URI 33 | done 34 | } 35 | 36 | import_table empresa 37 | import_table cnae_cnpj 38 | import_table socio 39 | import_table holding 40 | import_cnae_tables 41 | execute_sql_files 42 | -------------------------------------------------------------------------------- /headers/socio.csv: -------------------------------------------------------------------------------- 1 | start_column,name,size,type,description 2 | 1,TIPO DE REGISTRO,1,N,CONTEM O VALOR 2 PARA IDENTIFICAR O REGISTRO DETALHE SOCIOS 3 | 2,INDICADOR-FULL-DIARIO,1,A,"INDICA FORMA DE ENVIO DO ARQUIVO : 4 | F – QUANDO FULL" 5 | 3,TIPO DE ATUALIZACAO,1,A,"NO CASO DE ENVIO FULL, ESTE CAMPO ESTA EM BRANCO." 6 | 4,CNPJ,14,A,CONTEM O NÚMERO DE INSCRIÇÃO NO CNPJ (CADASTRO NACIONAL DA PESSOA JURÍDICA). 7 | 18,IDENTIFICADOR DE SOCIO,1,N,"1 – PESSOA JURÍDICA 8 | 2 – PESSOA FISICA 9 | 3 – ESTRANGEIRO" 10 | 19,NOME SOCIO,150,A,"CORRESPONDE AO NOME SOCIO PESSOA FISICA, RAZÃO SOCIAL E/OU NOME EMPRESARIAL DA PESSOA JURÍDICA E NOME DO SÓCIO/RAZAO SOCIAL DO SOCIO ESTRANGEIRO" 11 | 169,CNPJ/CPF DO SOCIO,14,A,"É PREENCHIDO COM CPF OU CNPJ DO SOCIO, NO CASO DE SÓCIO ESTRANGEIRO É PREENCHIDO COM ‘NOVES’ O ALINHAMENTO PARA CPF É FORMATADO COM ZEROS À ESQUERDA." 12 | 183,CODIGO QUALIFICACAO SOCIO,2,A,CODIGO QUALIFICACAO SOCIO 13 | 185,PERCENTUAL CAPITAL SOCIAL,5,N,PERCENTUAL CAPITAL SOCIAL 14 | 190,DATA ENTRADA SOCIEDADE,8,N,DATA DE ENTRADA NA SOCIEDADE 15 | 198,CODIGO PAIS,3,A,CODIGO PAIS DO SOCIO ESTRANGEIRO 16 | 201,NOME PAIS,70,A,CORRESPONDE AO NOME DO PAIS DO SÓCIO 17 | 271,CPF REPRESENTANTE LEGAL,11,A,CORRESPONDE AO NÚMERO DO CPF DO REPRESENTANTE LEGAL 18 | 282,NOME REPRESENTANTE LEGAL,60,A,CORRESPONDE AO NOME DO REPRESENTANTE LEGAL 19 | 342,CODIGO QUALIFICACAO REPRESENTANTE LEGAL,2,A,CORRESPONDE AO CÓDIGO DA QUALIFICACAO DO REPRESENTANTE LEGAL 20 | 344,FILLER,855,A,BRANCOS 21 | 1199,CAMPO DESCONHECIDO,1,A,Essa parte do registro não veio especificada pela Receita Federal 22 | 1200,FIM,1,A,PREENCHIDO COM ‘F’ INDICANDO FINAL DE REGISTRO. 23 | -------------------------------------------------------------------------------- /sql/03-create-indexes.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX IF NOT EXISTS idx_socio_cnpj ON socio (cnpj); 2 | CREATE INDEX IF NOT EXISTS idx_socio_documento_socio ON socio (cnpj_cpf_do_socio, cnpj); 3 | CREATE INDEX IF NOT EXISTS idx_holding_cnpjs ON holding (cnpj, holding_cnpj); 4 | CREATE INDEX IF NOT EXISTS idx_cnae_cnpj_cnpj ON cnae_cnpj (cnpj); 5 | CREATE INDEX IF NOT EXISTS idx_cnae_cnpj_cnae ON cnae_cnpj (cnae, primaria); 6 | CREATE INDEX IF NOT EXISTS idx_cnae_id ON cnae (id); 7 | 8 | CREATE INDEX IF NOT EXISTS idx_empresa_cnae_fiscal ON empresa (cnae_fiscal); 9 | CREATE INDEX IF NOT EXISTS idx_empresa_location ON empresa (uf, municipio, bairro); 10 | CREATE INDEX IF NOT EXISTS idx_empresa_mei_simples ON empresa (opcao_pelo_mei, opcao_pelo_simples); 11 | CREATE INDEX IF NOT EXISTS idx_empresa_situacao_cadastral ON empresa (situacao_cadastral); 12 | 13 | CREATE EXTENSION IF NOT EXISTS pg_trgm; -- PostgreSQL-only 14 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_socio_nome_socio ON socio USING gin (nome_socio gin_trgm_ops); 15 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_razao_social ON empresa USING gin (razao_social gin_trgm_ops); 16 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_nome_fantasia ON empresa USING gin (nome_fantasia gin_trgm_ops); 17 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_cnae_descricao ON cnae USING gin (descricao_subclasse gin_trgm_ops); 18 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_uf ON empresa USING gin (uf gin_trgm_ops); 19 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_municipio ON empresa USING gin (municipio gin_trgm_ops); 20 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_bairro ON empresa USING gin (bairro gin_trgm_ops); 21 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DOWNLOAD_PATH=data/download 6 | OUTPUT_PATH=data/output 7 | mkdir -p $DOWNLOAD_PATH $OUTPUT_PATH 8 | 9 | if [ "$1" = "--use-mirror" ]; then 10 | USE_MIRROR=true 11 | else 12 | USE_MIRROR=false 13 | fi 14 | 15 | function download_data() { 16 | CONNECTIONS=4 17 | DOWNLOAD_URL="https://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/dados-publicos-cnpj" 18 | FILE_URLS=$(wget --quiet --no-check-certificate -O - "$DOWNLOAD_URL" \ 19 | | grep --color=no DADOS_ABERTOS_CNPJ \ 20 | | grep --color=no ".zip" \ 21 | | sed 's/.*"http:/http:/; s/".*//' \ 22 | | sort) 23 | MIRROR_URL="https://data.brasil.io/mirror/socios-brasil" 24 | 25 | for url in $FILE_URLS; do 26 | if $USE_MIRROR; then 27 | url="$MIRROR_URL/$(basename $url)" 28 | fi 29 | time aria2c \ 30 | --auto-file-renaming=false \ 31 | --continue=true \ 32 | -s $CONNECTIONS \ 33 | -x $CONNECTIONS \ 34 | --dir="$DOWNLOAD_PATH" \ 35 | "$url" 36 | done 37 | } 38 | 39 | function extract_data() { 40 | time python extract_dump.py $OUTPUT_PATH $DOWNLOAD_PATH/DADOS_ABERTOS_CNPJ*.zip 41 | time python extract_cnae_cnpj.py $OUTPUT_PATH/{empresa,cnae_secundaria,cnae_cnpj}.csv.gz 42 | } 43 | 44 | function extract_holding() { 45 | time python extract_holding.py $OUTPUT_PATH/{socio,empresa,holding}.csv.gz 46 | } 47 | 48 | function extract_cnae() { 49 | for versao in "1.0" "1.1" "2.0" "2.1" "2.2" "2.3"; do 50 | filename="$OUTPUT_PATH/cnae_$versao.csv" 51 | rm -rf "$filename" 52 | time scrapy runspider \ 53 | -s RETRY_HTTP_CODES="500,503,504,400,404,408" \ 54 | -s HTTPCACHE_ENABLED=true \ 55 | --loglevel=INFO \ 56 | -a versao="$versao" \ 57 | -o "$filename" \ 58 | cnae.py 59 | gzip "$filename" 60 | done 61 | } 62 | 63 | download_data 64 | extract_data 65 | extract_holding 66 | extract_cnae 67 | -------------------------------------------------------------------------------- /natureza_juridica.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import io 3 | from itertools import chain 4 | 5 | import requests 6 | import rows 7 | 8 | 9 | def clear_text(text): 10 | if text is None: 11 | return text 12 | text = text.replace("\t", " ").replace("\n", " ") 13 | while " " in text: 14 | text = text.replace(" ", " ") 15 | return text 16 | 17 | 18 | def extract_data(): 19 | url = "https://www.receita.fazenda.gov.br/pessoajuridica/cnpj/tabelas/natjurqualificaresponsavel.htm" 20 | response = requests.get(url, verify=False) 21 | table_1 = rows.import_from_html( 22 | io.BytesIO(response.content), encoding=response.encoding, index=0, ignore_colspan=False 23 | ) 24 | table_2 = rows.import_from_html( 25 | io.BytesIO(response.content), encoding=response.encoding, index=1, ignore_colspan=False 26 | ) 27 | 28 | categoria, codigo_categoria = None, None 29 | for row in chain(table_1, table_2): 30 | row = {key: clear_text(value) for key, value in row._asdict().items()} 31 | 32 | codigo = row["codigo"] 33 | if ". " in codigo: 34 | categoria = codigo.title() 35 | split_index = categoria.find(". ") 36 | codigo_categoria, categoria = categoria[:split_index], categoria[split_index + 2 :] 37 | continue 38 | else: 39 | row["codigo"] = int(codigo.replace("-", "")) 40 | row["categoria"] = categoria 41 | row["codigo_categoria"] = codigo_categoria 42 | row["qualificacao"] = [item.strip() for item in row["qualificacao"].replace(" ou ", ", ").split(",")] 43 | 44 | yield row 45 | 46 | 47 | if __name__ == "__main__": 48 | filename = "data/natureza-juridica.csv" 49 | 50 | writer = None 51 | with open(filename, mode="w") as fobj: 52 | for row in extract_data(): 53 | row["qualificacao"] = "|".join(row["qualificacao"]) 54 | if writer is None: 55 | writer = csv.DictWriter(fobj, fieldnames=list(row.keys())) 56 | writer.writeheader() 57 | writer.writerow(row) 58 | -------------------------------------------------------------------------------- /extract_holding.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | from csv import DictReader 3 | from pathlib import Path 4 | 5 | import rows 6 | from rows.utils import CsvLazyDictWriter, open_compressed 7 | from tqdm import tqdm 8 | 9 | QUALIFICACAO_SOCIO = {row.codigo: row.descricao for row in rows.import_from_csv("qualificacao-socio.csv")} 10 | 11 | 12 | def convert_socio(row): 13 | codigo_qualificacao = int(row["codigo_qualificacao_socio"]) 14 | return { 15 | "holding_cnpj": row["cnpj_cpf_do_socio"], 16 | "holding_razao_social": row["nome_socio"], 17 | "cnpj": row["cnpj"], 18 | "razao_social": "", 19 | "codigo_qualificacao_socia": codigo_qualificacao, 20 | "qualificacao_socia": QUALIFICACAO_SOCIO.get(codigo_qualificacao, None), 21 | } 22 | 23 | 24 | def convert_empresa(row): 25 | return { 26 | "cnpj": row["cnpj"], 27 | "razao_social": row["razao_social"], 28 | } 29 | 30 | 31 | def filter_csv(input_filename, filter_function, convert_function, progress=True): 32 | fobj_reader = open_compressed(input_filename, mode="r") 33 | csv_reader = DictReader(fobj_reader) 34 | if progress: 35 | csv_reader = tqdm(csv_reader, desc=f"Reading {Path(input_filename).name}") 36 | for row in csv_reader: 37 | if filter_function(row): 38 | yield convert_function(row) 39 | fobj_reader.close() 40 | 41 | 42 | def main(): 43 | parser = ArgumentParser() 44 | parser.add_argument("socio_filename") 45 | parser.add_argument("empresa_filename") 46 | parser.add_argument("output_filename") 47 | args = parser.parse_args() 48 | 49 | holdings_it = filter_csv( 50 | args.socio_filename, 51 | lambda row: row["identificador_de_socio"] == "1", 52 | convert_function=convert_socio, 53 | progress=True, 54 | ) 55 | holdings = {row["cnpj"]: row for row in holdings_it} 56 | 57 | cnpjs = set(holdings.keys()) 58 | company_names_it = filter_csv( 59 | args.empresa_filename, lambda row: row["cnpj"] in cnpjs, convert_function=convert_empresa, progress=True, 60 | ) 61 | company_names = {row["cnpj"]: row["razao_social"] for row in company_names_it} 62 | 63 | fobj_writer = open_compressed(args.output_filename, mode="w") 64 | csv_writer = CsvLazyDictWriter(fobj_writer) 65 | for holding in tqdm(holdings.values(), desc="Writting output file"): 66 | holding["razao_social"] = company_names.get(holding["cnpj"], "") 67 | csv_writer.writerow(holding) 68 | fobj_writer.close() 69 | 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /qualificacao-socio.csv: -------------------------------------------------------------------------------- 1 | codigo,descricao,coletado_atualmente 2 | 1,Acionista,Não 3 | 2,Acionista Controlador,Não 4 | 3,Acionista Diretor,Não 5 | 4,Acionista Presidente,Não 6 | 5,Administrador,Sim 7 | 6,Administradora de consórcio de Empresas ou Grupo de Empresas,Não 8 | 7,Comissário,Não 9 | 8,Conselheiro de Administração,Sim 10 | 9,Curador,Sim 11 | 10,Diretor,Sim 12 | 11,Interventor,Sim 13 | 12,Inventariante,Sim 14 | 13,Liquidante,Sim 15 | 14,Mãe,Sim 16 | 15,Pai,Sim 17 | 16,Presidente,Sim 18 | 17,Procurador,Sim 19 | 18,Secretário,Sim 20 | 19,Síndico (Condomínio),Sim 21 | 20,Sociedade Consorciada,Sim 22 | 21,Sociedade Filiada,Sim 23 | 22,Sócio,Sim 24 | 23,Sócio Capitalista,Sim 25 | 24,Sócio Comanditado,Sim 26 | 25,Sócio Comanditário,Sim 27 | 26,Sócio de Indústria,Sim 28 | 27,Sócio Residente ou Domiciliado no Exterior,Não 29 | 28,Sócio-Gerente,Sim 30 | 29,Sócio ou Acionista Incapaz ou Relativamente Incapaz (exceto menor),Sim 31 | 30,Sócio ou Acionista Menor (Assistido/Representado),Sim 32 | 31,Sócio Ostensivo,Sim 33 | 32,Tabelião,Sim 34 | 33,Tesoureiro,Sim 35 | 34,Titular de Empresa Individual Imobiliária,Sim 36 | 35,Tutor,Sim 37 | 36,Gerente-Delegado,Não 38 | 37,Sócio Pessoa Jurídica Domiciliado no Exterior,Sim 39 | 38,Sócio Pessoa Física Residente ou Domiciliado no Exterior,Sim 40 | 39,Diplomata,Sim 41 | 40,Cônsul,Sim 42 | 41,Representante de Organização Internacional,Sim 43 | 42,Oficial de Registro,Sim 44 | 43,Responsável,Sim 45 | 44,Sócio Participante,Não 46 | 45,Sócio Investidor,Não 47 | 46,Ministro de Estado das Relações Exteriores,Sim 48 | 47,Sócio Pessoa Física Residente no Brasil,Sim 49 | 48,Sócio Pessoa Jurídica Domiciliado no Brasil,Sim 50 | 49,Sócio-Administrador,Sim 51 | 50,Empresário,Sim 52 | 51,Candidato a Cargo Político Eletivo,Sim 53 | 52,Sócio com Capital,Sim 54 | 53,Sócio sem Capital,Sim 55 | 54,Fundador,Sim 56 | 55,Sócio Comanditado Residente no Exterior,Sim 57 | 56,Sócio Comanditário Pessoa Física Residente no Exterior,Sim 58 | 57,Sócio Comanditário Pessoa Jurídica Domiciliado no Exterior,Sim 59 | 58,Sócio Comanditário Incapaz,Sim 60 | 59,Produtor Rural,Sim 61 | 60,Cônsul Honorário,Sim 62 | 61,Responsável Indigena,Sim 63 | 62,Representante das Instituições Extraterritoriais,Sim 64 | 63,Cotas em Tesouraria,Sim 65 | 64,Administrador Judicial,Sim 66 | 65,Titular Pessoa Física Residente ou Domiciliado no Brasil,Sim 67 | 66,Titular Pessoa Física Residente ou Domiciliado no Exterior,Sim 68 | 67,Titular Pessoa Física Incapaz ou Relativamente Incapaz (exceto menor),Sim 69 | 68,Titular Pessoa Física Menor (Assistido/Representado),Sim 70 | 69,Beneficiário Final,Não 71 | 70,Administrador Residente ou Domiciliado no Exterior,Sim 72 | 71,Conselheiro de Administração Residente ou Domiciliado no Exterior,Sim 73 | 72,Diretor Residente ou Domiciliado no Exterior,Sim 74 | 73,Presidente Residente ou Domiciliado no Exterior,Sim 75 | 74,Sócio-Administrador Residente ou Domiciliado no Exterior,Sim 76 | 75,Fundador Residente ou Domiciliado no Exterior,Sim 77 | 76,Protetor,Sim 78 | 77,Vice-Presidente,Sim 79 | 78,Titular Pessoa Jurídica Domiciliada no Brasil,Sim 80 | 79,Titular Pessoa Jurídica Domiciliada no Exterior,Sim 81 | -------------------------------------------------------------------------------- /headers/empresa.csv: -------------------------------------------------------------------------------- 1 | start_column,name,size,type,description 2 | 1,TIPO DE REGISTRO,1,N,CONTEM O VALOR 1 PARA IDENTIFICAR O REGISTRO 3 | 2,INDICADOR-FULL-DIARIO,1,A,"INDICA FORMA DE ENVIO DO ARQUIVO: 4 | F – QUANDO FULL" 5 | 3,TIPO DE ATUALIZACAO,1,A,NO CASO DE ENVIO FULL ESTE CAMPO ESTA EM BRANCO. 6 | 4,CNPJ,14,A,CONTEM O NÚMERO DE INSCRIÇÃO NO CNPJ (CADASTRO NACIONAL DA PESSOA JURÍDICA). 7 | 18,IDENTIFICADOR MATRIZ/FILIAL,1,N,"1: MATRIZ 8 | 2: FILIAL" 9 | 19,RAZAO SOCIAL,150,A,CORRESPONDE AO NOME EMPRESARIAL DA PESSOA JURÍDICA 10 | 169,NOME FANTASIA,55,A,CORRESPONDE AO NOME FANTASIA 11 | 224,SITUACAO CADASTRAL,2,N,"2 DIGITOS CÓDIGO DA SITUAÇÃO CADASTRAL 12 | 01: NULA 13 | 02: ATIVA 14 | 03: SUSPENSA 15 | 04: INAPTA 16 | 08: BAIXADA" 17 | 226,DATA SITUACAO CADASTRAL,8,N,DATA DO EVENTO DA SITUACAO CADASTRAL 18 | 234,MOTIVO SITUACAO CADASTRAL,2,N,CÓDIGO DO MOTIVO DA SITUAÇÃO CADASTRAL 19 | 236,NOME CIDADE EXTERIOR,55,A,NOME DA CIDADE NO EXTERIOR 20 | 291,CODIGO PAIS,3,A,CODIGO DO PAIS 21 | 294,NOME PAIS,70,A,NOME DO PAIS 22 | 364,CODIGO NATUREZA JURIDICA,4,N,CÓDIGO DA NATUREZA JURÍDICA 23 | 368,DATA INICIO ATIVIDADE,8,N,DATA DE INICIO DA ATIVIDADE 24 | 376,CNAE-FISCAL,7,N,INDICA O CÓDIGO DA ATIVIDADE ECONÔMICA PRINCIPAL DO ESTABELECIMENTO 25 | 383,DESCRICAO TIPO LOGRADOURO,20,A,CORRESPONDE A DESCRIÇÃO DO LOGRADOURO 26 | 403,LOGRADOURO,60,A,CORRESPONDE AO NOME DO LOGRADOURO ONDE SE LOCALIZA O ESTABELECIMENTO. 27 | 463,NUMERO,6,A,"CORRESPONDE AO NÚMERO ONDE SE LOCALIZA O ESTABELECIMENTO, QUANDO NÃO HOUVER PREENCHIMENTO DO NÚMERO HAVERÁ ‘S/N’." 28 | 469,COMPLEMENTO,156,A,CORRESPONDE AO COMPLEMENTO PARA O ENDEREÇO DE LOCALIZAÇÃO DO ESTABELECIMENTO 29 | 625,BAIRRO,50,A,CORRESPONDE AO BAIRRO ONDE SE LOCALIZA O ESTABELECIMENTO. 30 | 675,CEP,8,N,CÓDIGO DE ENDEREÇAMENTO POSTAL REFERENTE AO LOGRADOURO NO QUAL O ESTABELECIMENTO ESTA LOCALIZADO 31 | 683,UF,2,A,CORRESPONDE A SIGLA DA UNIDADE DA FEDERAÇÃO EM QUE SE ENCONTRA O ESTABELECIMENTO 32 | 685,CODIGO MUNICIPIO,4,N,CORRESPONDE AO CODIGO DO MUNICIPIO DE JURISDIÇÃO ONDE SE ENCONTRA O ESTABELECIMENTO 33 | 689,MUNICIPIO,50,A,CORRESPONDE AO MUNICIPIO DE JURISDIÇÃO ONDE SE ENCONTRA O ESTABELECIMENTO 34 | 739,DDD-TELEFONE-1,12,A,"DDD-1: tamanho 4, TELEFONE-1: tamanho 8" 35 | 751,DDD-TELEFONE-2,12,A,"DDD-2: tamanho 4, TELEFONE-2: tamanho 8" 36 | 763,DDD-FAX,12,A,"DDD-FAX: tamanho 4, TELEFONE-FAX: tamanho 8" 37 | 775,CORREIO ELETRONICO,115,A,E-MAIL DO CONTRIBUINTE 38 | 890,QUALIFICACAO DO RESPONSAVEL,2,N,QUALIFICAÇÃO DA PESSOA FÍSICA RESPONSÁVEL PELA EMPRESA 39 | 892,CAPITAL SOCIAL,14,N,CAPITAL SOCIAL DA EMPRESA 40 | 906,PORTE,2,A,"CÓDIGO DO PORTE DA EMPRESA 41 | 00: NAO INFORMADO 42 | 01: MICRO EMPRESA 43 | 03: EMPRESA DE PEQUENO PORTE 44 | 05: DEMAIS" 45 | 908,OPCAO PELO SIMPLES,1,A,"INDICADOR DA EXISTÊNCIA DA OPÇÃO PELO SIMPLES. 46 | 0 OU BRANCO: NÃO OPTANTE 47 | 5 E 7: OPTANTE PELO SIMPLES 48 | 6 E 8: EXCLUÍDO DO SIMPLES" 49 | 909,DATA OPCAO PELO SIMPLES,8,N,DATA DE OPÇÃO PELO SIMPLES 50 | 917,DATA EXCLUSAO DO SIMPLES,8,N,DATA DE EXCLUSÃO DO SIMPLES 51 | 925,OPCAO PELO MEI,1,A,"INDICADOR DA EXISTÊNCIA DA OPÇÃO PELO MEI 52 | S: SIM 53 | N: NÃO" 54 | 926,SITUACAO ESPECIAL,23,A,SITUACAO ESPECIAL DA EMPRESA 55 | 949,DATA SITUACAO ESPECIAL,8,N,DATA EM QUE A EMPRESA ENTROU EM SITUACAO ESPECIAL (AAAAMMDD) 56 | 957,FILLER,243,A,BRANCOS 57 | 1200,FIM,1,A,CONTEÚDO 'F' - INDICA FINAL DE REGISTRO 58 | -------------------------------------------------------------------------------- /historia-do-dataset.md: -------------------------------------------------------------------------------- 1 | # História do dataset socios-brasil 2 | 3 | 1. No fim de 2017 a Receita Federal do Brasil [liberou um dataset com os sócios 4 | das empresas 5 | brasileiras](http://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/dados-abertos-do-cnpj); 6 | 7 | 2. No início de 2018 [criei um programa para baixar, corrigir e converter os 8 | dados para CSV](https://github.com/turicas/socios-brasil/); 9 | 10 | 3. No meio de 2018 fiz junto com alguns amigos um pedido de acesso à informação 11 | para termos mais dados, como identificador único dos sócios (CPF) e CNAEs 12 | das empresas - [parte da história foi contada 13 | aqui](https://medium.com/serenata/o-dia-que-a-receita-nos-mandou-pagar-r-500-mil-para-ter-dados-públicos-8e18438f3076). 14 | 15 | 4. Meses (e vários recursos no pedido via LAI) depois, recebemos um pendrive 16 | pelos Correios com os dados e eu criei um outro script (dentro do mesmo 17 | repositório de código no GitHub) para converter os novos dados para CSV - o 18 | formato é parecido, mas diferente, dado que tem informações diferentes; eu 19 | subi os dados originais e convertidos para o Google Drive e compartilhei o 20 | link publicamente, para que todos pudessem acessar os dados sem precisar 21 | fazer o pedido como fizemos (afinal, é dado público). Nota: outras pessoas 22 | também fizeram pedidos similares e receberam o dump. 23 | 24 | 5. Um tempo depois que recebemos o pendrive a [RFB liberou no próprio site os 25 | arquivos](http://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/dados-publicos-cnpj), 26 | atualizei então o programa para baixar essa nova versão dos dados do site e 27 | convertê-los. 28 | 29 | 6. Após rodar análises nos dados, percebi que a residência dos sócios está 30 | errada - [veja mais 31 | detalhes](https://gist.github.com/turicas/ad0ab80b8eb3337dafb62fcfd2924ccd) 32 | - e criei um chamado na Ouvidoria do Ministério da Economia alertando sobre 33 | o problema; responderam dizendo que passariam para o setor técnico, mas 34 | depois nunca mais me atualizaram sobre. 35 | 36 | 7. No dia 05 de abril de 2019 tentei baixar o arquivo (que parece ter sido 37 | atualizado) no site deles e a previsão de download era de 4 dias; consegui 38 | baixar em umas 7h porque fiz o download em paralelo de diversos servidores 39 | do [Brasil.IO](https://brasil.io/). Abri um protocolo na ouvidoria com a 40 | reclamação e a resposta não foi muito diferente da resposta do item 41 | anterior. Deixei então os dados originais disponíveis para download no 42 | Brasil.IO (além dos dados convertidos), assim quem quiser acesso ao dado 43 | original poderá baixá-lo de 10 a 100x mais rápido que usando o site da RFB - 44 | [veja mais 45 | detalhes](https://twitter.com/turicas/status/1114185311372873729). 46 | 47 | 8. A partir de então, toda vez que a RFB atualiza os dados em seu site eu faço 48 | o processo de baixar os arquivos originais, deixá-los disponíveis para 49 | download mais rápido no Brasil.IO, converter os dados para CSV e também 50 | disponibilizar os dados já convertidos. Em geral publico no Twitter - [veja 51 | a última conversão que 52 | fiz](https://twitter.com/turicas/status/1197125153047662592). Ainda preciso 53 | adicionar esses detalhes na [página desse dataset no 54 | Brasil.IO](https://brasil.io/dataset/socios-brasil). 55 | -------------------------------------------------------------------------------- /data/natureza-juridica.csv: -------------------------------------------------------------------------------- 1 | codigo,natureza_juridica,representante_da_entidade,qualificacao,categoria,codigo_categoria 2 | 1015,Órgão Público do Poder Executivo Federal,Administrador,05,Administração Pública,1 3 | 1023,Órgão Público do Poder Executivo Estadual ou do Distrito Federal,Administrador,05,Administração Pública,1 4 | 1031,Órgão Público do Poder Executivo Municipal,Administrador,05,Administração Pública,1 5 | 1040,Órgão Público do Poder Legislativo Federal,Administrador,05,Administração Pública,1 6 | 1058,Órgão Público do Poder Legislativo Estadual ou do Distrito Federal,Administrador,05,Administração Pública,1 7 | 1066,Órgão Público do Poder Legislativo Municipal,Administrador,05,Administração Pública,1 8 | 1074,Órgão Público do Poder Judiciário Federal,Administrador,05,Administração Pública,1 9 | 1082,Órgão Público do Poder Judiciário Estadual,Administrador,05,Administração Pública,1 10 | 1104,Autarquia Federal,Administrador ou Presidente,05|16,Administração Pública,1 11 | 1112,Autarquia Estadual ou do Distrito Federal,Administrador ou Presidente,05|16,Administração Pública,1 12 | 1120,Autarquia Municipal,Administrador ou Presidente,05|16,Administração Pública,1 13 | 1139,Fundação Federal,Presidente,16,Administração Pública,1 14 | 1147,Fundação Estadual ou do Distrito Federal,Presidente,16,Administração Pública,1 15 | 1155,Fundação Municipal,Presidente,16,Administração Pública,1 16 | 1163,Órgão Público Autônomo Federal,Administrador,05,Administração Pública,1 17 | 1171,Órgão Público Autônomo Estadual ou do Distrito Federal,Administrador,05,Administração Pública,1 18 | 1180,Órgão Público Autônomo Municipal,Administrador,05,Administração Pública,1 19 | 1198,Comissão Polinacional,Administrador,05,Administração Pública,1 20 | 1201,Fundo Público,Administrador,05,Administração Pública,1 21 | 1210,Associação Pública,Presidente,16,Administração Pública,1 22 | 2011,Empresa Pública,"Administrador, Diretor ou Presidente",05|10|16,Entidades Empresariais,2 23 | 2038,Sociedade de Economia Mista,Diretor ou Presidente,10|16,Entidades Empresariais,2 24 | 2046,Sociedade Anônima Aberta,"Administrador, Diretor ou Presidente",05|10|16,Entidades Empresariais,2 25 | 2054,Sociedade Anônima Fechada,"Administrador, Diretor ou Presidente",05|10|16,Entidades Empresariais,2 26 | 2062,Sociedade Empresária Limitada,Administrador ou Sócio-Administrador,05|49,Entidades Empresariais,2 27 | 2070,Sociedade Empresária em Nome Coletivo,Sócio-Administrador,49,Entidades Empresariais,2 28 | 2089,Sociedade Empresária em Comandita Simples,Sócio Comanditado,24,Entidades Empresariais,2 29 | 2097,Sociedade Empresária em Comandita por Ações,Diretor ou Presidente,10|16,Entidades Empresariais,2 30 | 2127,Sociedade em Conta de Participação,Procurador ou Sócio Ostensivo,17|31,Entidades Empresariais,2 31 | 2135,Empresário (Individual),Empresário,50,Entidades Empresariais,2 32 | 2143,Cooperativa,Diretor ou Presidente,10|16,Entidades Empresariais,2 33 | 2151,Consórcio de Sociedades,Administrador,05,Entidades Empresariais,2 34 | 2160,Grupo de Sociedades,Administrador,05,Entidades Empresariais,2 35 | 2178,"Estabelecimento, no Brasil, de Sociedade Estrangeira",Procurador,17,Entidades Empresariais,2 36 | 2194,"Estabelecimento, no Brasil, de Empresa Binacional Argentino-Brasileira",Procurador,17,Entidades Empresariais,2 37 | 2216,Empresa Domiciliada no Exterior,Procurador,17,Entidades Empresariais,2 38 | 2224,Clube/Fundo de Investimento,Responsável,43,Entidades Empresariais,2 39 | 2232,Sociedade Simples Pura,Administrador ou Sócio-Administrador,05|49,Entidades Empresariais,2 40 | 2240,Sociedade Simples Limitada,Administrador ou Sócio-Administrador,05|49,Entidades Empresariais,2 41 | 2259,Sociedade Simples em Nome Coletivo,Sócio-Administrador,49,Entidades Empresariais,2 42 | 2267,Sociedade Simples em Comandita Simples,Sócio Comanditado,24,Entidades Empresariais,2 43 | 2275,Empresa Binacional,Diretor,10,Entidades Empresariais,2 44 | 2283,Consórcio de Empregadores,Administrador,05,Entidades Empresariais,2 45 | 2291,Consórcio Simples,Administrador,05,Entidades Empresariais,2 46 | 2305,Empresa Individual de Responsabilidade Limitada (de Natureza Empresária),"Administrador, Procurador ou Titular Pessoa Física Residente ou Domiciliado no Brasil",05|17|65,Entidades Empresariais,2 47 | 2313,Empresa Individual de Responsabilidade Limitada (de Natureza Simples),"Administrador, Procurador ou Titular Pessoa Física Residente ou Domiciliado no Brasil",05|17|65,Entidades Empresariais,2 48 | 3034,Serviço Notarial e Registral (Cartório),Tabelião ou Oficial de Registro,32|42,Entidades Sem Fins Lucrativos,3 49 | 3069,Fundação Privada,"Administrador, Diretor, Presidente ou Fundador",05|10|16|54,Entidades Sem Fins Lucrativos,3 50 | 3077,Serviço Social Autônomo,Administrador,05,Entidades Sem Fins Lucrativos,3 51 | 3085,Condomínio Edilício,Administrador ou Síndico (Condomínio),05|19,Entidades Sem Fins Lucrativos,3 52 | 3107,Comissão de Conciliação Prévia,Administrador,05,Entidades Sem Fins Lucrativos,3 53 | 3115,Entidade de Mediação e Arbitragem,Administrador,05,Entidades Sem Fins Lucrativos,3 54 | 3123,Partido Político,Administrador ou Presidente,05|16,Entidades Sem Fins Lucrativos,3 55 | 3131,Entidade Sindical,Administrador ou Presidente,05|16,Entidades Sem Fins Lucrativos,3 56 | 3204,"Estabelecimento, no Brasil, de Fundação ou Associação Estrangeiras",Procurador,17,Entidades Sem Fins Lucrativos,3 57 | 3212,Fundação ou Associação domiciliada no exterior,Procurador,17,Entidades Sem Fins Lucrativos,3 58 | 3220,Organização Religiosa,"Administrador, Diretor ou Presidente",05|10|16,Entidades Sem Fins Lucrativos,3 59 | 3239,Comunidade Indígena,Responsável Indígena,61,Entidades Sem Fins Lucrativos,3 60 | 3247,Fundo Privado,Administrador,05,Entidades Sem Fins Lucrativos,3 61 | 3999,Associação Privada,"Administrador, Diretor ou Presidente",05|10|16,Entidades Sem Fins Lucrativos,3 62 | 4014,Empresa Individual Imobiliária,Titular,34,Pessoas Físicas,4 63 | 4081,Contribuinte Individual,Produtor Rural,59,Pessoas Físicas,4 64 | 4090,Candidato a Cargo Político Eletivo,Candidato a Cargo Político Eletivo,51,Pessoas Físicas,4 65 | 5010,Organização Internacional,Representante de Organização Internacional,41,Instituições Extraterritoriais,5 66 | 5029,Representação Diplomática Estrangeira,"Diplomata, Cônsul, Ministro de Estado das Relações Exteriores ou Cônsul Honorário",39|40|46|60,Instituições Extraterritoriais,5 67 | 5037,Outras Instituições Extraterritoriais,Representante da Instituição Extraterritorial,62,Instituições Extraterritoriais,5 68 | -------------------------------------------------------------------------------- /cnae.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urljoin 2 | 3 | import scrapy 4 | from lxml.html import document_fromstring 5 | 6 | 7 | def get_text(lines): 8 | """Return text from "text()" XPath result as a string, removing whitespaces 9 | 10 | >>> get_text(["\\t", " teste ", "\\n", "123 "]) 11 | 'teste 123' 12 | """ 13 | 14 | return " ".join([line.strip() for line in lines if line.strip()]) 15 | 16 | 17 | class CNAESpider(scrapy.Spider): 18 | name = "cnae" 19 | versoes = { 20 | "1.0": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=1.0.1&versao_subclasse=2.1.1", 21 | "1.1": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=3.0.1&versao_subclasse=4.1.1", 22 | "2.0": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=5.0.1&versao_subclasse=6.1.1", 23 | "2.1": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=7.0.0&versao_subclasse=8.1.1", 24 | "2.2": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=7.0.0&versao_subclasse=9.1.1", 25 | "2.3": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=7.0.0&versao_subclasse=10.1.0", 26 | } 27 | parsers = { 28 | "root": { 29 | "xpath_items": "//table[@id = 'tbEstrutura']/tbody/tr", 30 | "xpath_id": ".//td[1]/a/text()", 31 | "xpath_description": ".//td[3]/text()", 32 | "xpath_url": ".//td[1]/a/@href", 33 | "next": "secao", 34 | }, 35 | "secao": { 36 | "id_length": 1, 37 | "xpath_items": "//table[@class = 'tabela-hierarquia']//td[a[contains(@href, 'divisao=')]]", 38 | "xpath_id": ".//a/text()", 39 | "xpath_description": ".//text()", 40 | "xpath_url": ".//a/@href", 41 | "next": "divisao", 42 | }, 43 | "divisao": { 44 | "id_length": 2, 45 | "xpath_items": "//table[@class = 'tabela-hierarquia']//td[a[contains(@href, 'grupo=')]]", 46 | "xpath_id": ".//a/text()", 47 | "xpath_description": ".//text()", 48 | "xpath_url": ".//a/@href", 49 | "next": "grupo", 50 | }, 51 | "grupo": { 52 | "id_length": 4, 53 | "xpath_items": "//table[@class = 'tabela-hierarquia']//td[a[contains(@href, 'classe=')]]", 54 | "xpath_id": ".//a/text()", 55 | "xpath_description": ".//text()", 56 | "xpath_url": ".//a/@href", 57 | "next": "classe", 58 | }, 59 | "classe": { 60 | "id_length": 7, 61 | "xpath_items": "//table[@class = 'tabela-hierarquia']//td[a[contains(@href, 'subclasse=')]]", 62 | "xpath_id": ".//a/text()", 63 | "xpath_description": ".//text()", 64 | "xpath_url": ".//a/@href", 65 | "next": "subclasse", 66 | }, 67 | } 68 | 69 | def __init__(self, versao=None, *args, **kwargs): 70 | super().__init__(*args, **kwargs) 71 | if not versao: 72 | raise ValueError("O parâmetro 'versao' é obrigatório") 73 | self.versao = versao 74 | 75 | def start_requests(self): 76 | return [scrapy.Request(url=self.versoes[self.versao], callback=self.parse)] 77 | 78 | def parse(self, response): 79 | yield { 80 | "id_subclasse": "8888-8/88", 81 | "descricao_subclasse": "Não identificada", 82 | "id_classe": "88.88-8", 83 | "descricao_classe": "Não identificada", 84 | "id_grupo": "88.8", 85 | "descricao_grupo": "Não identificado", 86 | "id_divisao": "88", 87 | "descricao_divisao": "Não identificada", 88 | "id_secao": "8", 89 | "descricao_secao": "Não identificada", 90 | "notas_explicativas": "", 91 | "url": "", 92 | "id": 8888888, 93 | "versao": self.versao, 94 | } 95 | yield from self.parse_items(response, root_name="root") 96 | 97 | def parse_items(self, response, root_name=None): 98 | """Recursively get data/make requests for all parser hierarchical levels""" 99 | 100 | data = response.request.meta.get("data", {}) 101 | root_name = root_name or response.request.meta["root_name"] 102 | metadata = self.parsers[root_name] 103 | xpath_id = metadata["xpath_id"] 104 | xpath_description = metadata["xpath_description"] 105 | xpath_url = metadata["xpath_url"] 106 | item_name = metadata["next"] 107 | for item in response.xpath(metadata["xpath_items"]): 108 | tree = document_fromstring(item.extract()) 109 | url = urljoin("https://cnae.ibge.gov.br/", tree.xpath(xpath_url)[0]) 110 | item_id = get_text(tree.xpath(xpath_id)) 111 | item_description = get_text(tree.xpath(xpath_description)) 112 | item_data = {} 113 | if item_name == "subclasse" or len(item_id) == self.parsers[item_name]["id_length"]: 114 | next_root_name = item_name 115 | else: 116 | descricao = response.xpath("//span[@class = 'destaque']//text()").extract()[0] 117 | item_data[f"id_{item_name}"] = descricao.split()[0] 118 | item_data[f"descricao_{item_name}"] = descricao 119 | next_root_name = self.parsers[item_name]["next"] 120 | item_data.update( 121 | {f"id_{next_root_name}": item_id.strip(), f"descricao_{next_root_name}": item_description.strip(),} 122 | ) 123 | item_data.update(data) 124 | 125 | callback = self.parse_items if next_root_name != "subclasse" else self.parse_subclasse 126 | yield scrapy.Request( 127 | url=url, meta={"data": item_data, "root_name": next_root_name}, callback=callback, 128 | ) 129 | 130 | def parse_subclasse(self, response): 131 | """Yield the subclass item (last mile of the recursive strategy)""" 132 | data = response.request.meta["data"] 133 | tree = document_fromstring(response.body) 134 | data["notas_explicativas"] = "\n".join( 135 | [line.strip() for line in tree.xpath('//div[@id = "notas-explicativas"]//text()') if line.strip()] 136 | ) 137 | data["url"] = response.request.url 138 | data["id"] = int(data["id_subclasse"].replace("/", "").replace("-", "")) 139 | data["versao"] = self.versao 140 | yield data 141 | -------------------------------------------------------------------------------- /test_parser.py: -------------------------------------------------------------------------------- 1 | from extract_dump import parse_row, read_header 2 | 3 | headers = { 4 | "1": read_header("headers/empresa.csv"), 5 | "2": read_header("headers/socio.csv"), 6 | "6": read_header("headers/cnae_secundaria.csv"), 7 | } 8 | 9 | 10 | def test_empresa(): 11 | data = "1F 000000000001911BANCO DO BRASIL SA DIRECAO GERAL 022005110300 2038196608016422100QUADRA SAUN QUADRA 5 LOTE B TORRES I, II E III SN ANDAR 1 A 16 SALA 101 A 1601 ANDAR 1 A 16 SALA 101 A 1601 ANDAR 1 A 16 SALA 101 A 1601 ASA NORTE 70040912DF9701BRASILIA 61 34939002 61 34931040SECEX@BB.COM.BR 10060000000000000500000000000000000N F\n" 12 | header = headers[data[0]] 13 | result = parse_row(header, data) 14 | expected = { 15 | "bairro": "ASA NORTE", 16 | "capital_social": 6000000000000, 17 | "cep": 70040912, 18 | "cnae_fiscal": 6422100, 19 | "cnpj": "00000000000191", 20 | "codigo_municipio": 9701, 21 | "codigo_natureza_juridica": 2038, 22 | "codigo_pais": "", 23 | "complemento": "ANDAR 1 A 16 SALA 101 A 1601 ANDAR 1 A 16 SALA 101 A 1601 ANDAR 1 A 16 SALA 101 A 1601", 24 | "correio_eletronico": "SECEX@BB.COM.BR", 25 | "data_exclusao_do_simples": "", 26 | "data_inicio_atividade": "1966-08-01", 27 | "data_opcao_pelo_simples": "", 28 | "data_situacao_cadastral": "2005-11-03", 29 | "data_situacao_especial": None, 30 | "ddd_fax": "61 34931040", 31 | "ddd_telefone_1": "61 34939002", 32 | "ddd_telefone_2": "", 33 | "descricao_tipo_logradouro": "QUADRA", 34 | "identificador_matriz_filial": 1, 35 | "logradouro": "SAUN QUADRA 5 LOTE B TORRES I, II E III", 36 | "motivo_situacao_cadastral": 0, 37 | "municipio": "BRASILIA", 38 | "nome_cidade_exterior": "", 39 | "nome_fantasia": "DIRECAO GERAL", 40 | "nome_pais": "", 41 | "numero": "SN", 42 | "opcao_pelo_mei": "N", 43 | "opcao_pelo_simples": "0", 44 | "porte": "05", 45 | "qualificacao_do_responsavel": 10, 46 | "razao_social": "BANCO DO BRASIL SA", 47 | "situacao_cadastral": 2, 48 | "situacao_especial": "", 49 | "uf": "DF", 50 | } 51 | assert result == expected 52 | 53 | 54 | def test_socio(): 55 | data = "2F 000000000001912MARCIO HAMILTON FERREIRA 000***923641**100000020101117249ESTADOS UNIDOS ***000000**CPF INVALIDO 00 F\n" 56 | header = headers[data[0]] 57 | result = parse_row(header, data) 58 | expected = { 59 | "campo_desconhecido": "", 60 | "cnpj": "00000000000191", 61 | "cnpj_cpf_do_socio": "000***923641**", 62 | "codigo_pais": "249", 63 | "codigo_qualificacao_representante_legal": "00", 64 | "codigo_qualificacao_socio": "10", 65 | "cpf_representante_legal": "***000000**", 66 | "data_entrada_sociedade": "2010-11-17", 67 | "identificador_de_socio": 2, 68 | "nome_pais_socio": "ESTADOS UNIDOS", 69 | "nome_representante_legal": "CPF INVALIDO", 70 | "nome_socio": "MARCIO HAMILTON FERREIRA", 71 | "percentual_capital_social": 0, 72 | } 73 | assert result == expected 74 | 75 | 76 | def test_cnae_secundaria(): 77 | data = "6F 00000000000191649999900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 F\n" 78 | header = headers[data[0]] 79 | result = parse_row(header, data) 80 | expected = { 81 | "cnae": "649999900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 82 | "cnpj": "00000000000191", 83 | } 84 | 85 | assert result == expected 86 | -------------------------------------------------------------------------------- /sql/04-create-old-views.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW view_socios AS 2 | SELECT 3 | s.cnpj AS cnpj, 4 | e.razao_social AS razao_social, 5 | s.cnpj_cpf_do_socio AS cpf_cnpj_socio, 6 | s.nome_socio AS nome_socio, 7 | s.codigo_qualificacao_socio AS codigo_qualificacao_socio, 8 | s.identificador_de_socio AS codigo_tipo_socio, 9 | CASE 10 | WHEN s.identificador_de_socio = 1 THEN 'Pessoa Jurídica' 11 | WHEN s.identificador_de_socio = 2 THEN 'Pessoa Física' 12 | WHEN s.identificador_de_socio = 3 THEN 'Nome Exterior' 13 | ELSE NULL 14 | END AS tipo_socio, 15 | CASE 16 | WHEN s.codigo_qualificacao_socio = 1 THEN 'Acionista' 17 | WHEN s.codigo_qualificacao_socio = 2 THEN 'Acionista Controlador' 18 | WHEN s.codigo_qualificacao_socio = 3 THEN 'Acionista Diretor' 19 | WHEN s.codigo_qualificacao_socio = 4 THEN 'Acionista Presidente' 20 | WHEN s.codigo_qualificacao_socio = 5 THEN 'Administrador' 21 | WHEN s.codigo_qualificacao_socio = 6 THEN 'Administradora de consórcio de Empresas ou Grupo de Empresas' 22 | WHEN s.codigo_qualificacao_socio = 7 THEN 'Comissário' 23 | WHEN s.codigo_qualificacao_socio = 8 THEN 'Conselheiro de Administração' 24 | WHEN s.codigo_qualificacao_socio = 9 THEN 'Curador' 25 | WHEN s.codigo_qualificacao_socio = 10 THEN 'Diretor' 26 | WHEN s.codigo_qualificacao_socio = 11 THEN 'Interventor' 27 | WHEN s.codigo_qualificacao_socio = 12 THEN 'Inventariante' 28 | WHEN s.codigo_qualificacao_socio = 13 THEN 'Liquidante' 29 | WHEN s.codigo_qualificacao_socio = 14 THEN 'Mãe' 30 | WHEN s.codigo_qualificacao_socio = 15 THEN 'Pai' 31 | WHEN s.codigo_qualificacao_socio = 16 THEN 'Presidente' 32 | WHEN s.codigo_qualificacao_socio = 17 THEN 'Procurador' 33 | WHEN s.codigo_qualificacao_socio = 18 THEN 'Secretário' 34 | WHEN s.codigo_qualificacao_socio = 19 THEN 'Síndico (Condomínio)' 35 | WHEN s.codigo_qualificacao_socio = 20 THEN 'Sociedade Consorciada' 36 | WHEN s.codigo_qualificacao_socio = 21 THEN 'Sociedade Filiada' 37 | WHEN s.codigo_qualificacao_socio = 22 THEN 'Sócio' 38 | WHEN s.codigo_qualificacao_socio = 23 THEN 'Sócio Capitalista' 39 | WHEN s.codigo_qualificacao_socio = 24 THEN 'Sócio Comanditado' 40 | WHEN s.codigo_qualificacao_socio = 25 THEN 'Sócio Comanditário' 41 | WHEN s.codigo_qualificacao_socio = 26 THEN 'Sócio de Indústria' 42 | WHEN s.codigo_qualificacao_socio = 27 THEN 'Sócio Residente ou Domiciliado no Exterior' 43 | WHEN s.codigo_qualificacao_socio = 28 THEN 'Sócio-Gerente' 44 | WHEN s.codigo_qualificacao_socio = 29 THEN 'Sócio ou Acionista Incapaz ou Relativamente Incapaz (exceto menor)' 45 | WHEN s.codigo_qualificacao_socio = 30 THEN 'Sócio ou Acionista Menor (Assistido/Representado)' 46 | WHEN s.codigo_qualificacao_socio = 31 THEN 'Sócio Ostensivo' 47 | WHEN s.codigo_qualificacao_socio = 32 THEN 'Tabelião' 48 | WHEN s.codigo_qualificacao_socio = 33 THEN 'Tesoureiro' 49 | WHEN s.codigo_qualificacao_socio = 34 THEN 'Titular de Empresa Individual Imobiliária' 50 | WHEN s.codigo_qualificacao_socio = 35 THEN 'Tutor' 51 | WHEN s.codigo_qualificacao_socio = 36 THEN 'Gerente-Delegado' 52 | WHEN s.codigo_qualificacao_socio = 37 THEN 'Sócio Pessoa Jurídica Domiciliado no Exterior' 53 | WHEN s.codigo_qualificacao_socio = 38 THEN 'Sócio Pessoa Física Residente ou Domiciliado no Exterior' 54 | WHEN s.codigo_qualificacao_socio = 39 THEN 'Diplomata' 55 | WHEN s.codigo_qualificacao_socio = 40 THEN 'Cônsul' 56 | WHEN s.codigo_qualificacao_socio = 41 THEN 'Representante de Organização Internacional' 57 | WHEN s.codigo_qualificacao_socio = 42 THEN 'Oficial de Registro' 58 | WHEN s.codigo_qualificacao_socio = 43 THEN 'Responsável' 59 | WHEN s.codigo_qualificacao_socio = 44 THEN 'Sócio Participante' 60 | WHEN s.codigo_qualificacao_socio = 45 THEN 'Sócio Investidor' 61 | WHEN s.codigo_qualificacao_socio = 46 THEN 'Ministro de Estado das Relações Exteriores' 62 | WHEN s.codigo_qualificacao_socio = 47 THEN 'Sócio Pessoa Física Residente no Brasil' 63 | WHEN s.codigo_qualificacao_socio = 48 THEN 'Sócio Pessoa Jurídica Domiciliado no Brasil' 64 | WHEN s.codigo_qualificacao_socio = 49 THEN 'Sócio-Administrador' 65 | WHEN s.codigo_qualificacao_socio = 50 THEN 'Empresário' 66 | WHEN s.codigo_qualificacao_socio = 51 THEN 'Candidato a Cargo Político Eletivo' 67 | WHEN s.codigo_qualificacao_socio = 52 THEN 'Sócio com Capital' 68 | WHEN s.codigo_qualificacao_socio = 53 THEN 'Sócio sem Capital' 69 | WHEN s.codigo_qualificacao_socio = 54 THEN 'Fundador' 70 | WHEN s.codigo_qualificacao_socio = 55 THEN 'Sócio Comanditado Residente no Exterior' 71 | WHEN s.codigo_qualificacao_socio = 56 THEN 'Sócio Comanditário Pessoa Física Residente no Exterior' 72 | WHEN s.codigo_qualificacao_socio = 57 THEN 'Sócio Comanditário Pessoa Jurídica Domiciliado no Exterior' 73 | WHEN s.codigo_qualificacao_socio = 58 THEN 'Sócio Comanditário Incapaz' 74 | WHEN s.codigo_qualificacao_socio = 59 THEN 'Produtor Rural' 75 | WHEN s.codigo_qualificacao_socio = 60 THEN 'Cônsul Honorário' 76 | WHEN s.codigo_qualificacao_socio = 61 THEN 'Responsável Indigena' 77 | WHEN s.codigo_qualificacao_socio = 62 THEN 'Representante das Instituições Extraterritoriais' 78 | WHEN s.codigo_qualificacao_socio = 63 THEN 'Cotas em Tesouraria' 79 | WHEN s.codigo_qualificacao_socio = 64 THEN 'Administrador Judicial' 80 | WHEN s.codigo_qualificacao_socio = 65 THEN 'Titular Pessoa Física Residente ou Domiciliado no Brasil' 81 | WHEN s.codigo_qualificacao_socio = 66 THEN 'Titular Pessoa Física Residente ou Domiciliado no Exterior' 82 | WHEN s.codigo_qualificacao_socio = 67 THEN 'Titular Pessoa Física Incapaz ou Relativamente Incapaz (exceto menor)' 83 | WHEN s.codigo_qualificacao_socio = 68 THEN 'Titular Pessoa Física Menor (Assistido/Representado)' 84 | WHEN s.codigo_qualificacao_socio = 69 THEN 'Beneficiário Final' 85 | WHEN s.codigo_qualificacao_socio = 70 THEN 'Administrador Residente ou Domiciliado no Exterior' 86 | WHEN s.codigo_qualificacao_socio = 71 THEN 'Conselheiro de Administração Residente ou Domiciliado no Exterior' 87 | WHEN s.codigo_qualificacao_socio = 72 THEN 'Diretor Residente ou Domiciliado no Exterior' 88 | WHEN s.codigo_qualificacao_socio = 73 THEN 'Presidente Residente ou Domiciliado no Exterior' 89 | WHEN s.codigo_qualificacao_socio = 74 THEN 'Sócio-Administrador Residente ou Domiciliado no Exterior' 90 | WHEN s.codigo_qualificacao_socio = 75 THEN 'Fundador Residente ou Domiciliado no Exterior' 91 | WHEN s.codigo_qualificacao_socio = 76 THEN 'Protetor' 92 | WHEN s.codigo_qualificacao_socio = 77 THEN 'Vice-Presidente' 93 | WHEN s.codigo_qualificacao_socio = 78 THEN 'Titular Pessoa Jurídica Domiciliada no Brasil' 94 | WHEN s.codigo_qualificacao_socio = 79 THEN 'Titular Pessoa Jurídica Domiciliada no Exterior' 95 | END AS qualificacao_socio 96 | FROM socio AS s 97 | LEFT JOIN empresa AS e 98 | ON s.cnpj = e.cnpj; 99 | 100 | 101 | CREATE VIEW view_holdings AS 102 | SELECT 103 | h.cnpj AS cnpj, 104 | h.razao_social AS razao_social, 105 | h.holding_cnpj AS cnpj_socia, 106 | h.qualificacao_socia AS qualificacao_socia, 107 | h.holding_razao_social AS razao_social_socia 108 | FROM holding AS h; 109 | 110 | 111 | CREATE VIEW view_empresas AS 112 | SELECT 113 | e.cnpj AS cnpj, 114 | e.razao_social AS razao_social, 115 | e.uf AS uf 116 | FROM empresa AS e; 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sócios de Empresas Brasileiras 2 | 3 | Script que baixa todos os dados de sócios das empresas brasileiras [disponíveis 4 | no site da Receita 5 | Federal](https://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/dados-publicos-cnpj), 6 | extrai, limpa e converte para CSV. Para entender melhor sobre quais dados estão 7 | disponíveis, consulte a [história desse dataset](historia-do-dataset.md). 8 | 9 | 10 | ## Licença 11 | 12 | A licença do código é [LGPL3](https://www.gnu.org/licenses/lgpl-3.0.en.html) e 13 | dos dados convertidos [Creative Commons Attribution 14 | ShareAlike](https://creativecommons.org/licenses/by-sa/4.0/). Caso utilize os 15 | dados, **cite a fonte original e quem tratou os dados**, como: **Fonte: Receita 16 | Federal do Brasil, dados tratados por Álvaro 17 | Justen/[Brasil.IO](https://brasil.io/)**. Caso compartilhe os dados, **utilize 18 | a mesma licença**. 19 | 20 | 21 | ## Dados 22 | 23 | ### Entrada 24 | 25 | Os dados publicados pela Receita Federal do Brasil contemplam as seguintes 26 | tabelas: 27 | 28 | - Cadastro das empresas, incluindo CNPJ, razão social, nome fantasia, endereço, 29 | CNAE fiscal e outros; 30 | - Cadastro de sócios, contendo CNPJ da empresa, documento do sócio, nome do 31 | sócio e outros; 32 | - CNAEs secundários para cada CNPJ. 33 | 34 | Os dados originalmente estão em um formato [fixed-width 35 | file](http://www.softinterface.com/Convert-XLS/Features/Fixed-Width-Text-File-Definition.htm) 36 | e cada linha possui um tipo diferente de registro (empresa, sócio, CNAE 37 | secundária, header ou trailler), que dificulta qualquer tipo de análise, sendo 38 | necessária a conversão para formatos mais amigáveis. 39 | 40 | O campo de qualificação do sócio foi definido com base [na tabela 41 | disponibilizada pela Receita 42 | Federal](http://idg.receita.fazenda.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/Qualificacao_socio.pdf) 43 | e está disponível no arquivo 44 | [`qualificacao-socio.csv`](qualificacao-socio.csv). Em breve também teremos 45 | arquivos com os nomes dos CNAEs e situação cadastral ([veja mais detalhes 46 | aqui](https://github.com/turicas/socios-brasil/issues/20)). 47 | 48 | 49 | ### Saída 50 | 51 | Além de extrair os dados do arquivo origingal, o script gera uma nova tabela 52 | contendo as empresas que são sócias de outras empresas (para facilitar buscas 53 | de *holdings*). 54 | 55 | Caso você não queira/possa rodar o script, **[acesse diretamente os dados 56 | convertidos no Brasil.IO](https://brasil.io/dataset/socios-brasil)**. 57 | 58 | Se esse programa e/ou os dados resultantes foram úteis a você ou à sua empresa, 59 | considere [fazer uma doação ao projeto Brasil.IO](https://brasil.io/doe), que é 60 | mantido voluntariamente. 61 | 62 | Como resultado temos os seguintes arquivos: 63 | 64 | - `empresa.csv.gz`: cadastro das empresas; 65 | - `socio.csv.gz`: cadastro dos sócios; 66 | - `cnae-secundaria.csv.gz`: lista de CNAEs secundárias; 67 | - `holding.csv.gz`: cadastro das empresas que são sócias de outras 68 | empresas (é o arquivo `socio.csv.gz` filtrado por sócios do tipo PJ). 69 | 70 | Além disso, os arquivos contidos nas pastas [schema](schema/) e 71 | [schema-full](schema-full/) podem te ajudar a importar os dados para um banco 72 | de dados (veja comandos para [SQLite](#sqlite) e [PostgreSQL](#postgresql) 73 | abaixo). 74 | 75 | > Nota 1: a extensão `.gz` quer dizer que o arquivo foi compactado usando gzip. 76 | > Para descompactá-lo execute o comando `gunzip arquivo.gz` (**não é necessário 77 | > descompactá-los** caso você siga as instruções de importação em 78 | > [SQLite](#sqlite) e [PostgreSQL](#postgresql)). 79 | 80 | > Nota 2: a codificação de caracteres original é ISO-8859-15, mas o script gera 81 | > os arquivos CSV em UTF-8. 82 | 83 | > Nota 3: se você estava usando os dados no formato anterior, veja como 84 | > converter os novos para o padrão antigo no arquivo 85 | > `sql/04-create-old-views.sql`. 86 | 87 | 88 | ### Privacidade 89 | 90 | Para garantir a privacidade, evitar SPAM e publicar apenas dados corretos, o 91 | script deleta/limpa algumas colunas com informações sensíveis ou incorretas. 92 | Essa é a forma padrão de funcionamento para não facilitar a exposição desses 93 | dados. Os dados censurados são: 94 | 95 | - Na tabela `empresa`: 96 | - Deletadas as colunas `codigo_pais` e `nome_pais`, pois os dados contidos 97 | nelas estão incorretos; 98 | - Deletada a coluna `correio_eletronico`, para evitar SPAM; 99 | - Na tabela `socio`: 100 | - Deletadas as colunas `codigo_pais` e `nome_pais`, pois os dados contidos 101 | nelas estão incorretos; 102 | - As colunas `complemento`, `ddd_fax`, `ddd_telefone_1`, `ddd_telefone_2`, 103 | `descricao_tipo_logradouro`, `logradouro`, `numero` terão seus dados 104 | deletados (ficarão em branco) para empresas que são empreendedores 105 | individuais (MEI, EI, EIRELI etc.) e, provavelmente, correspondem aos dados 106 | do sócio (endereço residencial, por exemplo); 107 | - Para os casos de empresas individuais que constarem o CPF na razão social 108 | (como é comum no caso de MEIs), o CPF será deletado. 109 | 110 | Caso queira rodar o script sem o modo censura, altere o script `run.sh` e 111 | adicione a opção `--no_censorship` para o script `extract_dump.py`. 112 | 113 | 114 | ### Dados auxiliares 115 | 116 | - Cadastro Nacional de Atividades Empresariais (CNAE): existe um spider que 117 | baixa os metadados das [atividades empresariais (CNAEs) do site do 118 | IBGE](https://cnae.ibge.gov.br). Veja a função `extract_cnae` no arquivo 119 | `run.sh`, ela baixará os dados para as versões 1.0, 1.1, 2.0, 2.1, 2.2 e 2.3 120 | e salvará em `data/output`. **Nota**: esse script será melhorado/alterado, 121 | veja a [issue #36](https://github.com/turicas/socios-brasil/issues/36). 122 | - Natureza jurídica: o arquivo `data/natureza-juridica.csv` contém o cadsatro 123 | de naturezas jurídicas das empresas (coluna `codigo_natureza_juridica` da 124 | tabela `empresa`). Esse arquivo é gerado pelo script `natureza_juridica.py`, 125 | que baixa os [dados do site da Receita 126 | Federal](https://www.receita.fazenda.gov.br/pessoajuridica/cnpj/tabelas/natjurqualificaresponsavel.htm). 127 | 128 | 129 | ## Rodando 130 | 131 | ### Instalando as Dependências 132 | 133 | Esse script depende de Python 3.7, de algumas bibliotecas e do software 134 | [aria2](https://aria2.github.io/). Depois de instalar o Python 3.7 e o aria2, 135 | instale as bibliotecas executando: 136 | 137 | ```bash 138 | pip install -r requirements.txt 139 | ``` 140 | 141 | ### Executando 142 | 143 | Então basta executar o script `run.sh` para baixar os arquivos necessários e 144 | fazer as conversões: 145 | 146 | ```bash 147 | ./run.sh 148 | ``` 149 | 150 | Você poderá rodar etapas separadamente também (leia o script [run.sh](run.sh) 151 | para mais detalhes). 152 | 153 | #### Agilizando o Download 154 | 155 | [O servidor da Receita Federal onde os dados estão hospedados é **muito 156 | lento**](https://twitter.com/turicas/status/1114185311372873729) e, por isso, o 157 | [Brasil.IO](https://brasil.io/) disponibiliza um *mirror* de onde o download 158 | pode ser feito mais rapidamente. Para executar o script baixando os dados do 159 | *mirror*, execute: 160 | 161 | ```bash 162 | ./run.sh --use-mirror 163 | ``` 164 | 165 | > Nota: os *mirrors* do Brasil.IO ainda estão em fase de testes e não é 166 | > garantido que estejam sempre atualizados. 167 | 168 | 169 | ## Importando em Bancos de Dados 170 | 171 | Depois de executar o script ou baixar os dados já convertidos, o ideal é 172 | importá-los em um banco de dados para facilitar consultas. Com a [interface de 173 | linha de comando da rows](http://turicas.info/rows/cli/) é possível importá-los 174 | rapidamente em bancos SQLite e PostgreSQL. 175 | 176 | > Nota 1: depois de importar os dados em um banco de dados é recomendável a 177 | > criação de índices para agilizar as consultas. Um índice bem comum é na 178 | > coluna `cnpj` (de todas as tabelas), para facilitar encontrar uma determinada 179 | > empresa, seus sócios e CNAEs secundários através do CNPJ. Exemplo: 180 | > `CREATE INDEX IF NOT EXISTS idx_empresa_cnpj ON empresa (cnpj);`. Veja o 181 | > arquivo [sql/create-indexes.sql](sql/create-indexes.sql) para uma lista de 182 | > índices sugeridos; veja também os outros arquivos da pasta `sql/` para 183 | > criação de tabelas auxiliares, chaves primárias e estrangeiras e o arquivo 184 | > `import-postgresql.sh` para automatizar o processo de importação e criação 185 | > dos índices. 186 | 187 | > Nota 2: caso utilize a opção `--no_censorship`, utilize os arquivos da pasta 188 | > `schema-full` em vez da pasta `schema`, pois a versão "sem censura" possui 189 | > mais colunas. 190 | 191 | ### SQLite 192 | 193 | Instale a CLI da rows e a versão de desenvolvimento da biblioteca rodando 194 | (requer Python 3.7+): 195 | 196 | ```bash 197 | pip install rows[cli] 198 | pip install -U https://github.com/turicas/rows/archive/develop.zip 199 | ``` 200 | 201 | Agora, com os arquivos na pasta `data/output` basta executar os seguintes 202 | comandos: 203 | 204 | ```bash 205 | DB_NAME="data/output/socios-brasil.sqlite" 206 | rows csv2sqlite --schemas=schema/empresa.csv data/output/empresa.csv.gz "$DB_NAME" 207 | rows csv2sqlite --schemas=schema/holding.csv data/output/holding.csv.gz "$DB_NAME" 208 | rows csv2sqlite --schemas=schema/socio.csv data/output/socio.csv.gz "$DB_NAME" 209 | rows csv2sqlite --schemas=schema/cnae-secundaria.csv data/output/cnae-secundaria.csv.gz "$DB_NAME" 210 | ``` 211 | 212 | Pegue um café, aguarde alguns minutos e depois desfrute do banco de dados em 213 | `data/output/socios-brasil.sqlite`. :) 214 | 215 | 216 | ### PostgreSQL 217 | 218 | Instale a CLI da rows, as dependências do PostgreSQL e a versão de 219 | desenvolvimento da biblioteca rodando (requer Python 3.7+): 220 | 221 | ```bash 222 | pip install rows[cli] 223 | pip install rows[postgresql] 224 | pip install -U https://github.com/turicas/rows/archive/develop.zip 225 | ``` 226 | 227 | Agora, com os arquivos na pasta `data/output` basta executar os seguintes 228 | comandos (não esqueça de preencher a variável `POSTGRESQL_URI` corretamente): 229 | 230 | ```bash 231 | POSTGRESQL_URI="postgres://:@:/" # PREENCHA! 232 | rows pgimport --schema=schema/empresa.csv data/output/empresa.csv.gz $POSTGRESQL_URI empresa 233 | rows pgimport --schema=schema/socio.csv data/output/empresa-socia.csv.gz $POSTGRESQL_URI empresa_socia 234 | rows pgimport --schema=schema/socio.csv data/output/socio.csv.gz $POSTGRESQL_URI socio 235 | rows pgimport --schema=schema/cnae-secundaria.csv data/output/cnae-secundaria.csv.gz $POSTGRESQL_URI cnae_secundaria 236 | ``` 237 | 238 | Pegue um café, aguarde alguns minutos e depois desfrute do banco de dados em 239 | `$POSTGRESQL_URI`. :) 240 | 241 | 242 | ## Outras Implementações 243 | 244 | Em R: 245 | 246 | - [qsacnpj](https://github.com/georgevbsantiago/qsacnpj/) 247 | - [RFBCNPJ](http://curso-r.com/blog/2018/05/13/2018-05-13-rfbcnpj/) 248 | 249 | Em Python: 250 | 251 | - [CNPJ-full](https://github.com/fabioserpa/CNPJ-full) 252 | -------------------------------------------------------------------------------- /extract_dump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Extrai os dados do dump do QSA da Receita Federal 4 | 5 | Dentro dos arquivos ZIP existe apenas um arquivo do tipo fixed-width file, 6 | contendo registros de diversas tabelas diferentes. O script descompacta sob 7 | demanda o ZIP e, conforme vai lendo os registros contidos, cria os arquivos de 8 | saída, em formato CSV. Você deve especificar o arquivo de entrada e o diretório 9 | onde ficarão os CSVs de saída (que por padrão ficam compactados também, em 10 | gzip, para diminuir o tempo de escrita e economizar espaço em disco). 11 | """ 12 | 13 | from argparse import ArgumentParser 14 | from decimal import Decimal 15 | from io import TextIOWrapper 16 | from pathlib import Path 17 | from zipfile import ZipFile 18 | 19 | import rows 20 | from rows.fields import slug 21 | from rows.plugins.utils import ipartition 22 | from rows.utils import CsvLazyDictWriter, open_compressed 23 | from tqdm import tqdm 24 | 25 | # Fields to delete/clean in some cases so we don't expose personal information 26 | FIELDS_TO_DELETE_COMPANY = ("codigo_pais", "correio_eletronico", "nome_pais") 27 | FIELDS_TO_DELETE_PARTNER = ("codigo_pais", "nome_pais") 28 | FIELDS_TO_CLEAR_INDIVIDUAL_COMPANY = ( 29 | "complemento", 30 | "ddd_fax", 31 | "ddd_telefone_1", 32 | "ddd_telefone_2", 33 | "descricao_tipo_logradouro", 34 | "logradouro", 35 | "numero", 36 | ) 37 | ONE_CENT = Decimal("0.01") 38 | INDIVIDUAL_COMPANIES = tuple( 39 | row.codigo 40 | for row in rows.import_from_csv("data/natureza-juridica.csv") 41 | if "individual" in row.natureza_juridica.lower() 42 | ) 43 | 44 | 45 | def clear_company_name(words): 46 | """Remove CPF from company name (useful to remove sensitive data from MEI) 47 | 48 | >>> clear_company_name(['FULANO', 'DE', 'TAL', '12345678901']) 49 | 'FULANO DE TAL' 50 | >>> clear_company_name(['FULANO', 'DE', 'TAL', 'CPF', '12345678901']) 51 | 'FULANO DE TAL' 52 | >>> clear_company_name(['FULANO', 'DE', 'TAL', '-', 'CPF', '12345678901']) 53 | 'FULANO DE TAL' 54 | >>> clear_company_name(['123456']) 55 | '123456' 56 | """ 57 | if len(words) == 1 and words[0].isdigit(): # Weird name, but doesn't have a CPF 58 | return words[0] 59 | 60 | last_word = words[-1] 61 | if last_word.isdigit() and len(last_word) == 11: # Remove CPF (numbers) 62 | words.pop() 63 | 64 | if words[-1].upper() == "CPF": # Remove CPF (word) 65 | words.pop() 66 | 67 | if words[-1] == "-": 68 | words.pop() 69 | 70 | return " ".join(words).strip() 71 | 72 | 73 | class ParsingError(ValueError): 74 | def __init__(self, line, error): 75 | super().__init__() 76 | self.line = line 77 | self.error = error 78 | 79 | 80 | def clear_email(email): 81 | """ 82 | >>> clear_email('-') is None 83 | True 84 | >>> clear_email('.') is None 85 | True 86 | >>> clear_email('0') is None 87 | True 88 | >>> clear_email('0000000000000000000000000000000000000000') is None 89 | True 90 | >>> clear_email('N/TEM') is None 91 | True 92 | >>> clear_email('NAO POSSUI') is None 93 | True 94 | >>> clear_email('NAO TEM') is None 95 | True 96 | >>> clear_email('NT') is None 97 | True 98 | >>> clear_email('S/N') is None 99 | True 100 | >>> clear_email('XXXXXXXX') is None 101 | True 102 | >>> clear_email('________________________________________') is None 103 | True 104 | >>> clear_email('n/t') is None 105 | True 106 | >>> clear_email('nao tem') is None 107 | True 108 | """ 109 | 110 | clean = email.lower().replace("/", "").replace("_", "") 111 | if len(set(clean)) < 3 or clean in ("nao tem", "n tem", "ntem", "nao possui", "nt"): 112 | return None 113 | return email 114 | 115 | 116 | def read_header(filename): 117 | """Read a CSV file which describes a fixed-width file 118 | 119 | The CSV must have the following columns: 120 | 121 | - name (final field name) 122 | - size (fixed size of the field, in bytes) 123 | - start_column (column in the fwf file where the fields starts) 124 | - type ("A" for text, "N" for int) 125 | """ 126 | 127 | table = rows.import_from_csv(filename) 128 | table.order_by("start_column") 129 | header = [] 130 | for row in table: 131 | row = dict(row._asdict()) 132 | row["field_name"] = slug(row["name"]) 133 | row["start_index"] = row["start_column"] - 1 134 | row["end_index"] = row["start_index"] + row["size"] 135 | header.append(row) 136 | return header 137 | 138 | 139 | def transform_empresa(row, censor): 140 | """Transform row of type company""" 141 | 142 | if censor: 143 | for field_name in FIELDS_TO_DELETE_COMPANY: 144 | del row[field_name] 145 | 146 | if row["codigo_natureza_juridica"] in INDIVIDUAL_COMPANIES: # "eupresa" 147 | for field_name in FIELDS_TO_CLEAR_INDIVIDUAL_COMPANY: 148 | row[field_name] = "" 149 | for field_name in ("razao_social", "nome_fantasia"): 150 | words = row[field_name].split() 151 | if words and words[-1].isdigit(): 152 | row[field_name] = clear_company_name(words) 153 | 154 | if "correio_eletronico" in row: # Could be deleted by censorship 155 | row["correio_eletronico"] = clear_email(row["correio_eletronico"]) 156 | 157 | if row["opcao_pelo_simples"] in ("", "0", "6", "8"): 158 | row["opcao_pelo_simples"] = "0" 159 | elif row["opcao_pelo_simples"] in ("5", "7"): 160 | row["opcao_pelo_simples"] = "1" 161 | else: 162 | raise ValueError(f"Opção pelo Simples inválida: {row['opcao_pelo_simples']} (CNPJ: {row['cnpj']})") 163 | 164 | if row["opcao_pelo_mei"] in ("N", ""): 165 | row["opcao_pelo_mei"] = "0" 166 | elif row["opcao_pelo_mei"] == "S": 167 | row["opcao_pelo_mei"] = "1" 168 | else: 169 | raise ValueError(f"Opção pelo MEI inválida: {row['opcao_pelo_mei']} (CNPJ: {row['cnpj']})") 170 | 171 | if set(row["nome_fantasia"]) == set(["0"]): 172 | row["nome_fantasia"] = "" 173 | 174 | if row["capital_social"] is not None: 175 | row["capital_social"] = Decimal(row["capital_social"]) * ONE_CENT 176 | 177 | return [row] 178 | 179 | 180 | def transform_socio(row, censor): 181 | """Transform row of type partner""" 182 | 183 | if row["campo_desconhecido"] != "": 184 | raise ValueError(f"Campo desconhecido preenchido - checar: {row}") 185 | del row["campo_desconhecido"] 186 | 187 | if row["nome_representante_legal"] == "CPF INVALIDO": 188 | row["cpf_representante_legal"] = None 189 | row["nome_representante_legal"] = None 190 | row["codigo_qualificacao_representante_legal"] = None 191 | 192 | if row["cnpj_cpf_do_socio"] == "000***000000**": 193 | row["cnpj_cpf_do_socio"] = "" 194 | 195 | if row["identificador_de_socio"] == 2: # Pessoa Física 196 | row["cnpj_cpf_do_socio"] = row["cnpj_cpf_do_socio"][-11:] 197 | 198 | # TODO: convert percentual_capital_social 199 | 200 | # Delete some fields if needed 201 | if censor: 202 | for field_name in FIELDS_TO_DELETE_PARTNER: 203 | del row[field_name] 204 | 205 | return [row] 206 | 207 | 208 | def transform_cnae_secundaria(row, censor): 209 | """Transform row of type CNAE""" 210 | 211 | cnaes = ["".join(digits) for digits in ipartition(row.pop("cnae"), 7) if set(digits) != set(["0"])] 212 | data = [] 213 | for cnae in cnaes: 214 | new_row = row.copy() 215 | new_row["cnae"] = cnae 216 | data.append(new_row) 217 | 218 | return data 219 | 220 | 221 | def parse_row(header, line): 222 | """Parse a fixed-width file line and returns a dict, based on metadata 223 | 224 | The `header` parameter is the return from `read_header`. 225 | Notes: 226 | 1- There's no check whether all fields are parsed (this function trusts 227 | the `header` was created in the correct way). 228 | 2- `line` is already decoded and since the input encoding is `latin1`, one 229 | character equals to one byte. If the input encoding does not have this 230 | characteristic then this function needs to be changed. 231 | """ 232 | line = line.replace("\x00", " ").replace("\x02", " ") 233 | row = {} 234 | for field in header: 235 | field_name = field["field_name"] 236 | value = line[field["start_index"] : field["end_index"]].strip() 237 | 238 | if field_name == "filler": 239 | if set(value) not in (set(), {"9"}): 240 | raise ParsingError(line=line, error="Wrong filler") 241 | continue # Do not save `filler` 242 | elif field_name == "tipo_de_registro": 243 | continue # Do not save row type (will be saved in separate files) 244 | elif field_name == "fim": 245 | if value.strip() != "F": 246 | raise ParsingError(line=line, error="Wrong end") 247 | continue # Do not save row end mark 248 | elif field_name in ("indicador_full_diario", "tipo_de_atualizacao"): 249 | continue # These fields are usually useless 250 | 251 | if field_name.startswith("data_") and value: 252 | if len(str(value)) > 8: 253 | raise ParsingError(line=line, error="Wrong date size") 254 | value = f"{value[:4]}-{value[4:6]}-{value[6:8]}" 255 | if value == "0000-00-00": 256 | value = "" 257 | elif field["type"] == "N" and "*" not in value: 258 | try: 259 | value = int(value) if value else None 260 | except ValueError: 261 | raise ParsingError(line=line, error=f"Cannot convert {repr(value)} to int") 262 | 263 | row[field_name] = value 264 | 265 | return row 266 | 267 | 268 | def extract_files( 269 | filenames, 270 | header_definitions, 271 | transform_functions, 272 | output_writers, 273 | error_filename, 274 | input_encoding="latin1", 275 | censorship=True, 276 | ): 277 | """Extract files from a fixed-width file containing more than one row type 278 | 279 | `filenames` is expected to be a list of ZIP files having only one file 280 | inside each. The file is read and metadata inside `fobjs` is used to parse 281 | it and save the output files. 282 | """ 283 | error_fobj = open_compressed(error_filename, mode="w", encoding="latin1") 284 | error_writer = CsvLazyDictWriter(error_fobj) 285 | 286 | for filename in filenames: 287 | # TODO: use another strategy to open this file (like using rows' 288 | # open_compressed when archive support is implemented) 289 | zf = ZipFile(filename) 290 | inner_filenames = zf.filelist 291 | assert len(inner_filenames) == 1, f"Only one file inside the zip is expected (got {len(inner_filenames)})" 292 | # XXX: The current approach of decoding here and then extracting 293 | # fixed-width-file data will work only for encodings where 1 character is 294 | # represented by 1 byte, such as latin1. If the encoding can represent one 295 | # character using more than 1 byte (like UTF-8), this approach will make 296 | # incorrect results. 297 | fobj = TextIOWrapper(zf.open(inner_filenames[0]), encoding=input_encoding) 298 | for line in tqdm(fobj, desc=f"Extracting {filename}"): 299 | row_type = line[0] 300 | try: 301 | row = parse_row(header_definitions[row_type], line) 302 | except ParsingError as exception: 303 | error_writer.writerow({"error": exception.error, "line": exception.line}) 304 | continue 305 | data = transform_functions[row_type](row, censorship) 306 | for row in data: 307 | output_writers[row_type].writerow(row) 308 | 309 | fobj.close() 310 | zf.close() 311 | 312 | error_fobj.close() 313 | 314 | 315 | def main(): 316 | base_path = Path(__file__).parent 317 | output_path = base_path / "data" / "output" 318 | error_filename = output_path / "errors.csv" 319 | 320 | parser = ArgumentParser() 321 | parser.add_argument("output_path", default=str(output_path)) 322 | parser.add_argument("input_filenames", nargs="+") 323 | parser.add_argument("--no_censorship", action="store_true") 324 | args = parser.parse_args() 325 | 326 | input_encoding = "latin1" 327 | input_filenames = args.input_filenames 328 | output_path = Path(args.output_path) 329 | if not output_path.exists(): 330 | output_path.mkdir(parents=True) 331 | error_filename = output_path / "error.csv.gz" 332 | censorship = not args.no_censorship 333 | 334 | row_types = { 335 | "0": { 336 | "header_filename": "headers/header.csv", 337 | "output_filename": output_path / "header.csv.gz", 338 | "transform_function": lambda row, censor: [row], 339 | }, 340 | "1": { 341 | "header_filename": "headers/empresa.csv", 342 | "output_filename": output_path / "empresa.csv.gz", 343 | "transform_function": transform_empresa, 344 | }, 345 | "2": { 346 | "header_filename": "headers/socio.csv", 347 | "output_filename": output_path / "socio.csv.gz", 348 | "transform_function": transform_socio, 349 | }, 350 | "6": { 351 | "header_filename": "headers/cnae_secundaria.csv", 352 | "output_filename": output_path / "cnae_secundaria.csv.gz", 353 | "transform_function": transform_cnae_secundaria, 354 | }, 355 | "9": { 356 | "header_filename": "headers/trailler.csv", 357 | "output_filename": output_path / "trailler.csv.gz", 358 | "transform_function": lambda row, censor: [row], 359 | }, 360 | } 361 | header_definitions, output_writers, transform_functions = {}, {}, {} 362 | for row_type, data in row_types.items(): 363 | header_definitions[row_type] = read_header(data["header_filename"]) 364 | output_writers[row_type] = CsvLazyDictWriter(data["output_filename"]) 365 | transform_functions[row_type] = data["transform_function"] 366 | extract_files( 367 | filenames=input_filenames, 368 | header_definitions=header_definitions, 369 | transform_functions=transform_functions, 370 | output_writers=output_writers, 371 | error_filename=error_filename, 372 | input_encoding=input_encoding, 373 | censorship=censorship, 374 | ) 375 | 376 | 377 | if __name__ == "__main__": 378 | main() 379 | --------------------------------------------------------------------------------