├── .coverage ├── .coveragerc ├── .gitignore ├── README.md ├── download.py ├── integrate_data.py ├── leis_brasileiras ├── __init__.py ├── commons.py ├── leis.py ├── urls.py └── utils.py ├── requirements.txt └── tests ├── __init__.py ├── fixtures.py └── test_utils.py /.coverage: -------------------------------------------------------------------------------- 1 | !coverage.py: This is a private format, don't read it directly!{"lines":{"/home/bernardocordeiro/Documents/leis-brasileiras/leis_brasileiras/__init__.py":[1],"/home/bernardocordeiro/Documents/leis-brasileiras/leis_brasileiras/utils.py":[1,2,3,4,6,37,42,50,43,44,45,46,47,48,51,62,63,52,53,54,55,56,57,58,59,60,61,64,65,7,8,9,10,11,13,31,32,33,34,14,15,16,18,19,20,21,23,24,25,26,28,35,30,38,39,40],"/home/bernardocordeiro/Documents/leis-brasileiras/leis_brasileiras/commons.py":[],"/home/bernardocordeiro/Documents/leis-brasileiras/leis_brasileiras/leis.py":[]}} -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | leis_brasileiras/urls.py, 4 | leis_brasileiras/integrate_data.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | geckodriver 3 | geckodriver.log 4 | __pycache__ 5 | settings.ini 6 | .env 7 | *.ipynb 8 | .ipynb_checkpoints/ 9 | output/ 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leis Brasileiras 2 | 3 | Os scripts desde repositório buscam automatizar o processo de busca e download das leis e projetos de leis 4 | hospedados em sites do Governo. Até o momento os seguintes documentos são baixados: 5 | 6 | ## Planalto 7 | http://www4.planalto.gov.br/legislacao/ 8 | 9 | - Decretos 10 | - Decretos Leis 11 | - Leis Ordinárias 12 | - Leis Complementares 13 | - Leis Delegadas 14 | - Medidas Provisórias 15 | 16 | ## Casa Civil 17 | http://www.casacivil.gov.br/Secretaria-Executiva/Diretoria%20de%20Assuntos%20Legislativos/projetos-de-lei 18 | 19 | - Projetos de Lei 20 | - Projetos de Lei Complementar 21 | - Projetos de Lei Congresso Nacional 22 | 23 | 24 | ## Alerj 25 | http://www3.alerj.rj.gov.br/lotus_notes/default.asp?id=144 26 | 27 | - Decretos 28 | - Leis Ordinárias 29 | - Leis Complementares 30 | 31 | ## Câmara Municipal do Rio de Janeiro 32 | http://www.camara.rj.gov.br/ 33 | 34 | - Decretos 35 | - Leis Ordinárias 36 | - Leis Complementares 37 | - Emendas à Lei Orgânica 38 | - Projetos de Leis Ordinárias 39 | - Projetos de Leis Complementares 40 | - Projetos de Decretos 41 | - Projetos de Emendas à Lei Orgânica 42 | 43 | 44 | As leis e projetos são salvos em um arquivo .CSV com separação das colunas por '';'' e com nome definido no momento da consulta. 45 | 46 | Além disso, são fornecidos os seguintes scripts: 47 | - download.py : Baixa todas as leis, decretos e emendas à lei orgânica, assim como seus projetos, da Câmara Municipal do Rio de Janeiro. 48 | - integrate_data.py : Dados os arquivos de leis e projetos de lei, junta eles em uma única tabela. 49 | 50 | ### Exemplo de Uso 51 | ##### Baixando os Decretos do site do Planalto: 52 | ```python 53 | from leis_brasileiras.leis import DecretosPlanalto 54 | 55 | planalto = DecretosPlanalto('/caminho/para/arquivo.csv') 56 | planalto.download() 57 | ``` 58 | 59 | ### Instalando dependências 60 | 61 | ```bash 62 | pip install -r requirements.txt 63 | ``` 64 | 65 | ##### Obeservação: 66 | Para baixar os documentos dos site é necessário utilizar o programa 67 | [Selenium](https://www.seleniumhq.org/) e utilizar o driver do navegador **Firefox**. O executável funciona em qualquer plataforma (Windows, Mac e Linux) e pode ser baixado a partir deste [link](https://github.com/mozilla/geckodriver/releases). 68 | 69 | ### Configuração do Ambiente 70 | É necessário criar um arquivo **settings.ini** com a variável que irá apontar para o 71 | **geckodriver** apresentado anteriormente. 72 | 73 | ``` 74 | [settings] 75 | DRIVER_PATH=/caminho/completo/para/geckodriver 76 | ``` 77 | 78 | #### Estrutura do Projeto: 79 | ```bash 80 | . 81 | ├── leis_brasileiras 82 | ├── __init__.py 83 | ├── commons.py 84 | ├── leis.py 85 | ├── urls.py 86 | └── utils.py 87 | ├── tests 88 | ├── __init__.py 89 | ├── fixtures.py 90 | └── test_utils.py 91 | ├── download.py 92 | ├── geckodriver 93 | ├── integrate_data.py 94 | ├── README.md 95 | ├── requirements.txt 96 | └── settings.ini 97 | ``` 98 | -------------------------------------------------------------------------------- /download.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | 4 | from leis_brasileiras.leis import ( 5 | DecretosCamaraMunicipalRJ, 6 | ProjetosDeDecretoCamaraMunicipalRJ1720, 7 | ProjetosDeDecretoCamaraMunicipalRJ1316, 8 | ProjetosDeDecretoCamaraMunicipalRJ0912, 9 | EmendasLeiOrganicaCamaraMunicipalRJ, 10 | ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ1720, 11 | ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ1316, 12 | ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ0912, 13 | LeisOrdinariasCamaraMunicipalRJ, 14 | ProjetosDeLeiCamaraMunicipalRJ0912, 15 | ProjetosDeLeiCamaraMunicipalRJ1316, 16 | ProjetosDeLeiCamaraMunicipalRJ1720, 17 | LeisComplementaresCamaraMunicipalRJ, 18 | ProjetosDeLeiComplementarCamaraMunicipalRJ1720, 19 | ProjetosDeLeiComplementarCamaraMunicipalRJ1316, 20 | ProjetosDeLeiComplementarCamaraMunicipalRJ0912 21 | 22 | ) 23 | 24 | 25 | OUTPUT_FOLDER = 'output' 26 | with contextlib.suppress(FileExistsError): 27 | os.mkdir(OUTPUT_FOLDER) 28 | 29 | docs = [ 30 | DecretosCamaraMunicipalRJ( 31 | f'{OUTPUT_FOLDER}/decretos.csv'), 32 | ProjetosDeDecretoCamaraMunicipalRJ1720( 33 | f'{OUTPUT_FOLDER}/projetos_decreto_1720.csv'), 34 | ProjetosDeDecretoCamaraMunicipalRJ1316( 35 | f'{OUTPUT_FOLDER}/projetos_decreto_1316.csv'), 36 | ProjetosDeDecretoCamaraMunicipalRJ0912( 37 | f'{OUTPUT_FOLDER}/projetos_decreto_0912.csv'), 38 | EmendasLeiOrganicaCamaraMunicipalRJ( 39 | f'{OUTPUT_FOLDER}/emendas_lei_organica.csv'), 40 | ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ1720( 41 | f'{OUTPUT_FOLDER}/projetos_emenda_lei_organica_1720.csv'), 42 | ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ1316( 43 | f'{OUTPUT_FOLDER}/projetos_emenda_lei_organica_1316.csv'), 44 | ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ0912( 45 | f'{OUTPUT_FOLDER}/projetos_emenda_lei_organica_0912.csv'), 46 | LeisOrdinariasCamaraMunicipalRJ( 47 | f'{OUTPUT_FOLDER}/leis_ordinarias.csv'), 48 | ProjetosDeLeiCamaraMunicipalRJ0912( 49 | f'{OUTPUT_FOLDER}/projetos_lei_0912.csv'), 50 | ProjetosDeLeiCamaraMunicipalRJ1316( 51 | f'{OUTPUT_FOLDER}/projetos_lei_1316.csv'), 52 | ProjetosDeLeiCamaraMunicipalRJ1720( 53 | f'{OUTPUT_FOLDER}/projetos_lei_1720.csv'), 54 | LeisComplementaresCamaraMunicipalRJ( 55 | f'{OUTPUT_FOLDER}/leis_complementares.csv'), 56 | ProjetosDeLeiComplementarCamaraMunicipalRJ1720( 57 | f'{OUTPUT_FOLDER}/projetos_lei_complementares_1720.csv'), 58 | ProjetosDeLeiComplementarCamaraMunicipalRJ1316( 59 | f'{OUTPUT_FOLDER}/projetos_lei_complementares_1316.csv'), 60 | ProjetosDeLeiComplementarCamaraMunicipalRJ0912( 61 | f'{OUTPUT_FOLDER}/projetos_lei_complementares_0912.csv'), 62 | ] 63 | 64 | for doc in docs: 65 | doc.download() 66 | -------------------------------------------------------------------------------- /integrate_data.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pandas as pd 4 | import tqdm 5 | 6 | from leis_brasileiras.utils import extract_projeto 7 | 8 | 9 | USAGE_STRING = """ 10 | usage: python integrate_data.py TYPE PROJETOS_FILES LEI_FILES OUTPUT_NAME 11 | 12 | TYPE needs to be 'lei', 'lei_comp', 'decreto' or 'emenda' 13 | 14 | PROJETOS_FILES and LEI_FILES may be comprised of several CSVs, 15 | separated by commas: 16 | 17 | python integrate_data.py lei projetos1.csv,projetos2.csv leis.csv output.csv 18 | """ 19 | 20 | if len(sys.argv) < 4: 21 | print(USAGE_STRING) 22 | sys.exit(1) 23 | 24 | SUPPORTED_TYPES = ['lei', 'lei_comp', 'decreto', 'emenda'] 25 | START_YEAR = 2009 26 | TYPE = sys.argv[1] 27 | PROJETOS_FILES = sys.argv[2].split(',') 28 | LEI_FILES = sys.argv[3].split(',') 29 | OUTPUT_FILE = sys.argv[4] 30 | 31 | if TYPE not in SUPPORTED_TYPES: 32 | raise RuntimeError('{} not supported! Supported types are:\n' 33 | '{}'.format(TYPE, SUPPORTED_TYPES)) 34 | 35 | # Get projetos de lei 36 | projetos = [] 37 | for pf in tqdm.tqdm(PROJETOS_FILES, 'Integrando arquivos com projetos de lei'): 38 | projetos.append(pd.read_csv(pf, ';')) 39 | projetos = pd.concat(projetos) 40 | 41 | projetos = projetos.rename({'lei': 'projeto'}, axis=1) 42 | 43 | projetos.dropna(subset=['ementa', 'autor'], inplace=True) 44 | projetos.drop_duplicates(subset=['projeto', 'data_publicacao'], inplace=True) 45 | 46 | projetos['nr_projeto'] = projetos['projeto'].apply(lambda x: x.split('/')[0]) 47 | projetos['ano'] = projetos['projeto'].apply(lambda x: x.split('/')[1]) 48 | 49 | projetos[projetos['ano'].astype(int) >= START_YEAR] 50 | 51 | projetos.sort_values(['ano', 'nr_projeto'], ascending=False, inplace=True) 52 | projetos.drop(['nr_projeto', 'ano'], axis=1, inplace=True) 53 | 54 | # Get leis 55 | leis = [] 56 | for lf in tqdm.tqdm(LEI_FILES, 'Integrando arquivos com leis'): 57 | leis.append(pd.read_csv(lf, ';')) 58 | leis = pd.concat(leis) 59 | 60 | leis['nr_projeto'] = leis['inteiro_teor'].apply( 61 | extract_projeto, law_type=TYPE) 62 | 63 | # Merge leis with their respective projetos 64 | dfm = projetos.merge( 65 | leis[['lei', 'ano', 'status', 'nr_projeto']].astype(str), 66 | how='left', 67 | left_on='projeto', 68 | right_on='nr_projeto') 69 | dfm['status'] = dfm['status'].fillna('Não se aplica') 70 | dfm['cod_municipio'] = 330455 71 | dfm['nm_municipio'] = 'RIO DE JANEIRO' 72 | dfm = dfm.drop(['nr_projeto'], axis=1) 73 | 74 | print('Escrevendo arquivo final...', end=' ') 75 | dfm.to_csv(OUTPUT_FILE, ';', index=False) 76 | print('\033[92mPronto!') 77 | -------------------------------------------------------------------------------- /leis_brasileiras/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/leis-brasileiras/a851cbdcec74389bc5c3b7ceeb56da9e7e88bf1c/leis_brasileiras/__init__.py -------------------------------------------------------------------------------- /leis_brasileiras/commons.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def striphtml(html): 5 | p = re.compile(r'<.*?>') 6 | return p.sub('', html) 7 | -------------------------------------------------------------------------------- /leis_brasileiras/leis.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import re 3 | import requests as req 4 | 5 | from abc import ABCMeta, abstractmethod 6 | 7 | from requests.exceptions import MissingSchema 8 | 9 | from bs4 import BeautifulSoup 10 | from decouple import config 11 | from selenium.common.exceptions import NoSuchElementException 12 | from selenium.webdriver import Firefox 13 | from selenium.webdriver.firefox.options import Options 14 | from selenium.webdriver.support import expected_conditions as EC 15 | from selenium.webdriver.support.ui import WebDriverWait 16 | from selenium.webdriver.common.by import By 17 | from tqdm import tqdm 18 | 19 | from leis_brasileiras.commons import striphtml 20 | from leis_brasileiras.urls import ( 21 | urls_decretos_planalto, 22 | urls_leis_ordinarias_planalto, 23 | urls_medidas_provisorias, 24 | urls_projetos_leis_casa_civil, 25 | urls_projetos_leis_complementares_casa_civil, 26 | urls_projetos_leis_congresso_casa_civil) 27 | 28 | 29 | class Planalto(metaclass=ABCMeta): 30 | base_url = "http://www4.planalto.gov.br/legislacao/portal-legis/"\ 31 | "legislacao-1/" 32 | origin = 'Planalto' 33 | 34 | @abstractmethod 35 | def __init__(self): 36 | options = Options() 37 | options.headless = True 38 | print("Iniciando navegador Firefox em modo headless") 39 | self.driver = Firefox( 40 | options=options, 41 | executable_path=config('DRIVER_PATH') 42 | ) 43 | 44 | def get_content(self, link): 45 | headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X' 46 | '10_10_1) Appl eWebKit/537.36 (KHTML, like Gecko)' 47 | 'Chrome/39.0.2171.95Safari/537.36' 48 | } 49 | resp = req.get(link, headers=headers) 50 | content = resp.content.decode('latin-1') 51 | return BeautifulSoup(content, features='lxml').find('body').text 52 | 53 | def get_row_info(self, tds, year): 54 | try: 55 | link = tds[0].find_element_by_tag_name('a').get_attribute('href') 56 | link = link.replace('https', 'http') 57 | inteiro_teor = striphtml(self.get_content(link)) 58 | except (NoSuchElementException, MissingSchema): 59 | inteiro_teor = '' 60 | 61 | info = {k: v.text for k, v in zip(('lei', 'ementa'), tds)} 62 | info['ano'] = year 63 | info['inteiro_teor'] = inteiro_teor 64 | return info 65 | 66 | def _wait_table(self): 67 | wait = WebDriverWait(self.driver, 20) 68 | wait.until(EC.presence_of_element_located( 69 | (By.TAG_NAME, 'table') 70 | ) 71 | ) 72 | 73 | def extract_info(self, year, url, writer): 74 | download_desc = 'Baixando {tipo} {origin} ({ano})'.format( 75 | tipo=self.tipo_lei, 76 | origin=self.origin, 77 | ano=year 78 | ) 79 | 80 | self.driver.get(self.base_url + url) 81 | 82 | self._wait_table() 83 | table = self.driver.find_element_by_tag_name('table') 84 | rows = table.find_elements_by_tag_name('tr') 85 | 86 | # rows[1:] to skip table header 87 | for row in tqdm(rows[1:], desc=download_desc): 88 | tds = row.find_elements_by_tag_name('td') 89 | row_info = self.get_row_info(tds, year) 90 | writer.writerow(row_info) 91 | 92 | def download(self): 93 | with open(self.file_destination, 'w', newline='') as csvfile: 94 | writer = csv.DictWriter( 95 | csvfile, 96 | fieldnames=self.header, 97 | delimiter=";", 98 | quotechar='"' 99 | ) 100 | writer.writeheader() 101 | 102 | for year, url in self.urls.items(): 103 | self.extract_info(year, url, writer) 104 | 105 | print('Fechando Navegador Firefox') 106 | self.driver.close() 107 | 108 | 109 | class DecretosPlanalto(Planalto): 110 | def __init__(self, file_destination): 111 | super().__init__() 112 | self.file_destination = file_destination 113 | self.tipo_lei = 'decretos' 114 | self.urls = urls_decretos_planalto 115 | self.header = ['lei', 'ementa', 'ano', 'inteiro_teor'] 116 | 117 | 118 | class LeisOrdinariasPlanalto(Planalto): 119 | def __init__(self, file_destination): 120 | super().__init__() 121 | self.file_destination = file_destination 122 | self.tipo_lei = 'leis ordinárias' 123 | self.urls = urls_leis_ordinarias_planalto 124 | self.header = ['lei', 'ementa', 'ano', 'inteiro_teor'] 125 | 126 | 127 | class LeisComplementaresPlanalto(Planalto): 128 | def __init__(self, file_destination): 129 | super().__init__() 130 | self.file_destination = file_destination 131 | self.tipo_lei = 'leis complementares' 132 | self.urls = { 133 | 'todos-os-anos': 'leis-complementares-1/' 134 | 'todas-as-leis-complementares-1' 135 | } 136 | self.header = ['lei', 'ementa', 'ano', 'inteiro_teor'] 137 | 138 | 139 | class LeisDelegadasPlanalto(Planalto): 140 | def __init__(self, file_destination): 141 | super().__init__() 142 | self.file_destination = file_destination 143 | self.tipo_lei = 'leis delegadas' 144 | self.header = ['lei', 'ementa', 'ano', 'inteiro_teor'] 145 | 146 | 147 | class MedidasProvisoriasPlanalto(Planalto): 148 | def __init__(self, file_destination): 149 | super().__init__() 150 | self.file_destination = file_destination 151 | self.tipo_lei = 'medidas provisórias' 152 | self.urls = urls_medidas_provisorias 153 | self.header = ['lei', 'ementa', 'ano', 'inteiro_teor'] 154 | 155 | 156 | class DecretosLeisPlanalto(Planalto): 157 | def __init__(self, file_destination): 158 | super().__init__() 159 | self.file_destination = file_destination 160 | self.tipo_lei = 'decretos-leis' 161 | self.urls = {'1937-1946': 'decretos-leis/1937-a-1946-decretos-leis-1', 162 | '1965-1988': 'decretos-leis/1965-a-1988-decretos-leis'} 163 | self.header = ['lei', 'ementa', 'ano', 'inteiro_teor'] 164 | 165 | 166 | class PlanaltoProjetosReader(metaclass=ABCMeta): 167 | @abstractmethod 168 | def __init__(self): 169 | pass 170 | 171 | def get_row_info(self, tds, year): 172 | inteiro_teor = '' 173 | motivacao = '' 174 | try: 175 | links = tds[0].find_elements_by_tag_name('a') 176 | if len(links) >= 1: 177 | link_inteiro_teor = links[0].get_attribute('href') 178 | link_inteiro_teor = link_inteiro_teor.replace('https', 'http') 179 | inteiro_teor = striphtml(self.get_content(link_inteiro_teor)) 180 | if len(links) == 2: 181 | link_motivacao = links[1].get_attribute('href') 182 | link_motivacao = link_motivacao.replace('https', 'http') 183 | motivacao = striphtml(self.get_content(link_motivacao)) 184 | 185 | except (NoSuchElementException, MissingSchema): 186 | pass 187 | 188 | numero_lei = re.sub(r'\s+', '', tds[0].text) 189 | numero_lei = re.search(r'(\d\.|\d+)?\d+\/\d+', numero_lei) 190 | if numero_lei: 191 | numero_lei = numero_lei[0] 192 | info = {'lei': numero_lei} 193 | info['ementa'] = tds[1].text 194 | info['ano'] = year 195 | info['inteiro_teor'] = inteiro_teor 196 | info['motivacao'] = motivacao 197 | 198 | if len(tds) == 3: 199 | info['situacao'] = tds[2].text 200 | 201 | return info 202 | 203 | 204 | class ProjetosPlanalto(PlanaltoProjetosReader, Planalto): 205 | def __init__(self, file_destination): 206 | super().__init__() 207 | self.file_destination = file_destination 208 | self.tipo_lei = 'projetos-lei' 209 | self.urls = urls_projetos_leis_casa_civil 210 | self.header = [ 211 | 'lei', 212 | 'ementa', 213 | 'ano', 214 | 'inteiro_teor', 215 | 'situacao', 216 | 'motivacao' 217 | ] 218 | 219 | 220 | class ProjetosLeisComplementaresPlanalto(PlanaltoProjetosReader, Planalto): 221 | def __init__(self, file_destination): 222 | super().__init__() 223 | self.file_destination = file_destination 224 | self.tipo_lei = 'projetos-lei-complementar' 225 | self.urls = urls_projetos_leis_complementares_casa_civil 226 | self.header = [ 227 | 'lei', 228 | 'ementa', 229 | 'ano', 230 | 'inteiro_teor', 231 | 'situacao', 232 | 'motivacao' 233 | ] 234 | 235 | 236 | class ProjetosLeisCongressoPlanalto(PlanaltoProjetosReader, Planalto): 237 | def __init__(self, file_destination): 238 | super().__init__() 239 | self.file_destination = file_destination 240 | self.tipo_lei = 'projetos-lei-congresso-nacional' 241 | self.urls = urls_projetos_leis_congresso_casa_civil 242 | self.header = [ 243 | 'lei', 244 | 'ementa', 245 | 'ano', 246 | 'inteiro_teor', 247 | 'situacao', 248 | 'motivacao' 249 | ] 250 | 251 | 252 | class Alerj(metaclass=ABCMeta): 253 | 254 | dns = "http://alerjln1.alerj.rj.gov.br" 255 | base_url = dns + "/contlei.nsf/{tipo}?OpenForm&Start={start}&Count=1000" 256 | header = ['lei', 'ano', 'autor', 'ementa'] 257 | 258 | @abstractmethod 259 | def __init__(self, file_destination): 260 | self.file_destination = file_destination 261 | 262 | def visit_url(self, start): 263 | url = self.base_url.format(tipo=self.tipo, start=start) 264 | common_page = req.get(url) 265 | soup = BeautifulSoup(common_page.content, features='lxml') 266 | return soup.find_all('tr') 267 | 268 | def parse_metadata(self, row): 269 | columns = row.find_all('td') 270 | return dict( 271 | zip( 272 | self.header, 273 | [c.text for c in columns if c.text and c.text != '*'] 274 | ) 275 | ) 276 | 277 | def parse_full_content(self, row): 278 | # There may be links pointing to the form, which are not wanted 279 | links = [l for l in row.find_all('a') if 'OpenDocument' in l['href']] 280 | full_content_link = self.dns + links[0]['href'] 281 | resp = req.get(full_content_link) 282 | soup = BeautifulSoup(resp.content, features='lxml') 283 | body = soup.find('body') 284 | strip_body = striphtml(body.text) 285 | 286 | # Documents come with noise at the end, this removes that 287 | end_doc_i = strip_body.find('HTML5 Canvas') 288 | return strip_body[:end_doc_i] 289 | 290 | def download(self): 291 | with open(self.file_destination, 'w', newline='') as csvfile: 292 | writer = csv.DictWriter( 293 | csvfile, 294 | fieldnames=self.header + ['inteiro_teor'], 295 | delimiter=";", 296 | quotechar='"' 297 | ) 298 | writer.writeheader() 299 | 300 | page = 1 301 | rows = self.visit_url(start=page) 302 | while len(rows) > 1: 303 | download_desc = 'Baixando {tipo} {orgao} - página: {page}'\ 304 | .format(tipo=self.tipo_lei, orgao=self.orgao, page=page) 305 | 306 | # Skip header 307 | for row in tqdm(rows[1:], desc=download_desc): 308 | if not row.find_all('td'): 309 | continue 310 | metadata = self.parse_metadata(row) 311 | metadata['inteiro_teor'] = self.parse_full_content(row) 312 | writer.writerow(metadata) 313 | 314 | start = page * 1000 + 1 315 | page += 1 316 | rows = self.visit_url(start) 317 | 318 | 319 | class DecretosAlerj(Alerj): 320 | orgao = 'Alerj' 321 | tipo = 'DecretoInt' 322 | tipo_lei = 'decretos' 323 | 324 | 325 | class LeisOrdinariasAlerj(Alerj): 326 | orgao = 'Alerj' 327 | tipo = 'LeiOrdInt' 328 | tipo_lei = 'leis ordinárias' 329 | 330 | 331 | class LeisComplementaresAlerj(Alerj): 332 | orgao = 'Alerj' 333 | tipo = 'LeiCompInt' 334 | tipo_lei = 'leis complementares' 335 | 336 | 337 | class ProjetosDeLeiAlerj1923(Alerj): 338 | orgao = 'Alerj' 339 | dns = "http://alerjln1.alerj.rj.gov.br" 340 | base_url = dns + "/scpro1923.nsf/{tipo}?OpenView&Start={start}&Count=1000" 341 | 342 | tipo = 'VLeiInt' 343 | tipo_lei = 'leis ordinárias' 344 | header = ['projeto', 'ementa', 'data_publicacao', 'autor'] 345 | 346 | 347 | class ProjetosDeLeiComplementarAlerj1923(Alerj): 348 | orgao = 'Alerj' 349 | dns = "http://alerjln1.alerj.rj.gov.br" 350 | base_url = dns + "/scpro1923.nsf/{tipo}?OpenView&Start={start}&Count=1000" 351 | 352 | tipo = 'VLeiCompInt' 353 | tipo_lei = 'leis ordinárias' 354 | header = ['projeto', 'ementa', 'data_publicacao', 'autor'] 355 | 356 | 357 | class ProjetosDeDecretosAlerj1923(Alerj): 358 | orgao = 'Alerj' 359 | dns = "http://alerjln1.alerj.rj.gov.br" 360 | base_url = dns + "/scpro1923.nsf/{tipo}?OpenView&Start={start}&Count=1000" 361 | 362 | tipo = 'VDecretoInt' 363 | tipo_lei = 'leis ordinárias' 364 | header = ['projeto', 'ementa', 'data_publicacao', 'autor'] 365 | 366 | 367 | class EmendasLeiOrganicaCamaraMunicipalRJ(Alerj): 368 | orgao = 'Camara Municipal' 369 | dns = 'http://mail.camara.rj.gov.br' 370 | base_url = dns + '/APL/Legislativos/contlei.nsf/'\ 371 | '{tipo}?OpenForm&Start={start}&Count=1000' 372 | 373 | tipo = 'EmendaInt' 374 | tipo_lei = 'emendas a lei organica' 375 | header = ['lei', 'ano', 'status', 'ementa', 'autor'] 376 | 377 | 378 | class DecretosCamaraMunicipalRJ(Alerj): 379 | orgao = 'Camara Municipal' 380 | dns = 'http://mail.camara.rj.gov.br' 381 | base_url = dns + '/APL/Legislativos/contlei.nsf/'\ 382 | '{tipo}?OpenForm&Start={start}&Count=1000' 383 | 384 | tipo = 'DecretoInt' 385 | tipo_lei = 'decretos' 386 | header = ['lei', 'ano', 'ementa', 'autor', 'status'] 387 | 388 | 389 | class LeisOrdinariasCamaraMunicipalRJ(Alerj): 390 | orgao = 'Camara Municipal' 391 | dns = 'http://mail.camara.rj.gov.br' 392 | base_url = dns + '/APL/Legislativos/contlei.nsf/'\ 393 | '{tipo}?OpenForm&Start={start}&Count=1000' 394 | 395 | tipo = 'LeiOrdInt' 396 | tipo_lei = 'leis ordinárias' 397 | header = ['lei', 'ano', 'status', 'ementa', 'autor'] 398 | 399 | 400 | class LeisComplementaresCamaraMunicipalRJ(Alerj): 401 | orgao = 'Camara Municipal' 402 | dns = 'http://mail.camara.rj.gov.br' 403 | base_url = dns + '/APL/Legislativos/contlei.nsf/'\ 404 | '{tipo}?OpenForm&Start={start}&Count=1000' 405 | 406 | tipo = 'LeiCompInt' 407 | tipo_lei = 'leis ordinárias' 408 | header = ['lei', 'ano', 'status', 'ementa', 'autor'] 409 | 410 | 411 | # Projetos de Lei 2017-2020 CamaraRJ 412 | class ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ1720(Alerj): 413 | orgao = 'Camara Municipal' 414 | dns = 'http://mail.camara.rj.gov.br' 415 | base_url = dns + '/APL/Legislativos/scpro1720.nsf/Internet/'\ 416 | '{tipo}?OpenForm&Start={start}&Count=1000' 417 | 418 | tipo = 'EmendaInt' 419 | tipo_lei = 'projetos de emenda a lei organica' 420 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 421 | 422 | 423 | class ProjetosDeLeiCamaraMunicipalRJ1720(Alerj): 424 | orgao = 'Camara Municipal' 425 | dns = 'http://mail.camara.rj.gov.br' 426 | base_url = dns + '/APL/Legislativos/scpro1720.nsf/Internet/'\ 427 | '{tipo}?OpenForm&Start={start}&Count=1000' 428 | 429 | tipo = 'LeiInt' 430 | tipo_lei = 'projetos de lei' 431 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 432 | 433 | 434 | class ProjetosDeLeiComplementarCamaraMunicipalRJ1720(Alerj): 435 | orgao = 'Camara Municipal' 436 | dns = 'http://mail.camara.rj.gov.br' 437 | base_url = dns + '/APL/Legislativos/scpro1720.nsf/Internet/'\ 438 | '{tipo}?OpenForm&Start={start}&Count=1000' 439 | 440 | tipo = 'LeiCompInt' 441 | tipo_lei = 'projetos de lei complementar' 442 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 443 | 444 | 445 | class ProjetosDeDecretoCamaraMunicipalRJ1720(Alerj): 446 | orgao = 'Camara Municipal' 447 | dns = 'http://mail.camara.rj.gov.br' 448 | base_url = dns + '/APL/Legislativos/scpro1720.nsf/Internet/'\ 449 | '{tipo}?OpenForm&Start={start}&Count=1000' 450 | 451 | tipo = 'DecretoInt' 452 | tipo_lei = 'projetos de decreto' 453 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 454 | 455 | 456 | # Projetos de Lei Camara 2013-2016 457 | class ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ1316(Alerj): 458 | orgao = 'Camara Municipal' 459 | dns = 'http://mail.camara.rj.gov.br' 460 | base_url = dns + '/APL/Legislativos/scpro1316.nsf/Internet/'\ 461 | '{tipo}?OpenForm&Start={start}&Count=1000' 462 | 463 | tipo = 'EmendaInt' 464 | tipo_lei = 'projetos de emenda a lei organica' 465 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 466 | 467 | 468 | class ProjetosDeLeiCamaraMunicipalRJ1316(Alerj): 469 | orgao = 'Camara Municipal' 470 | dns = 'http://mail.camara.rj.gov.br' 471 | base_url = dns + '/APL/Legislativos/scpro1316.nsf/Internet/'\ 472 | '{tipo}?OpenForm&Start={start}&Count=1000' 473 | 474 | tipo = 'LeiInt' 475 | tipo_lei = 'projetos de lei' 476 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 477 | 478 | 479 | class ProjetosDeLeiComplementarCamaraMunicipalRJ1316(Alerj): 480 | orgao = 'Camara Municipal' 481 | dns = 'http://mail.camara.rj.gov.br' 482 | base_url = dns + '/APL/Legislativos/scpro1316.nsf/Internet/'\ 483 | '{tipo}?OpenForm&Start={start}&Count=1000' 484 | 485 | tipo = 'LeiCompInt' 486 | tipo_lei = 'projetos de lei complementar' 487 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 488 | 489 | 490 | class ProjetosDeDecretoCamaraMunicipalRJ1316(Alerj): 491 | orgao = 'Camara Municipal' 492 | dns = 'http://mail.camara.rj.gov.br' 493 | base_url = dns + '/APL/Legislativos/scpro1316.nsf/Internet/'\ 494 | '{tipo}?OpenForm&Start={start}&Count=1000' 495 | 496 | tipo = 'DecretoInt' 497 | tipo_lei = 'projetos de decreto' 498 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 499 | 500 | 501 | # Projetos de Lei Camara 2009-2012 502 | class ProjetosDeEmendasLeiOrganicaCamaraMunicipalRJ0912(Alerj): 503 | orgao = 'Camara Municipal' 504 | dns = 'http://mail.camara.rj.gov.br' 505 | base_url = dns + '/APL/Legislativos/scpro0711.nsf/Internet/'\ 506 | '{tipo}?OpenForm&Start={start}&Count=1000' 507 | 508 | tipo = 'EmendaInt' 509 | tipo_lei = 'projetos de emenda a lei organica' 510 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 511 | 512 | 513 | class ProjetosDeLeiCamaraMunicipalRJ0912(Alerj): 514 | orgao = 'Camara Municipal' 515 | dns = 'http://mail.camara.rj.gov.br' 516 | base_url = dns + '/APL/Legislativos/scpro0711.nsf/Internet/'\ 517 | '{tipo}?OpenForm&Start={start}&Count=1000' 518 | 519 | tipo = 'LeiInt' 520 | tipo_lei = 'projetos de lei' 521 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 522 | 523 | 524 | class ProjetosDeLeiComplementarCamaraMunicipalRJ0912(Alerj): 525 | orgao = 'Camara Municipal' 526 | dns = 'http://mail.camara.rj.gov.br' 527 | base_url = dns + '/APL/Legislativos/scpro0711.nsf/Internet/'\ 528 | '{tipo}?OpenForm&Start={start}&Count=1000' 529 | 530 | tipo = 'LeiCompInt' 531 | tipo_lei = 'projetos de lei complementar' 532 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 533 | 534 | 535 | class ProjetosDeDecretoCamaraMunicipalRJ0912(Alerj): 536 | orgao = 'Camara Municipal' 537 | dns = 'http://mail.camara.rj.gov.br' 538 | base_url = dns + '/APL/Legislativos/scpro0711.nsf/Internet/'\ 539 | '{tipo}?OpenForm&Start={start}&Count=1000' 540 | 541 | tipo = 'DecretoInt' 542 | tipo_lei = 'projetos de decreto' 543 | header = ['lei', 'ementa', 'data_publicacao', 'autor'] 544 | -------------------------------------------------------------------------------- /leis_brasileiras/urls.py: -------------------------------------------------------------------------------- 1 | urls_decretos_planalto = { 2 | '2020': 'decretos1/2020-decretos', 3 | '2019': 'decretos1/2019-decretos', 4 | '2018': 'decretos1/2018-decretos/2018-decretos', 5 | '2017': 'decretos1/2017-decretos', 6 | '2016': 'decretos1/2016-decretos', 7 | '2015': 'decretos1/2015-decretos-1', 8 | '2014': 'decretos1/2014-decretos-1', 9 | '2013': 'decretos1/2013-decretos-2', 10 | '2012': 'decretos1/2012-decretos-2', 11 | '2011': 'decretos1/2011-decretos-2', 12 | '2010': 'decretos1/2010-decretos-1', 13 | '2009': 'decretos1/2009-decretos', 14 | '2008': 'decretos1/2008-decretos', 15 | '2007': 'decretos1/2007-decretos', 16 | '2006': 'decretos1/2006-decretos', 17 | '2005': 'decretos1/2005-decretos-1', 18 | '2004': 'decretos1/2004-decretos-1', 19 | '2003': 'decretos1/2003-decretos', 20 | '2002': 'decretos1/2002-decretos-1', 21 | '2001': 'decretos1/2001-decretos', 22 | '2000': 'decretos1/2000-decretos-1', 23 | '1999': 'decretos1/1999-decretos-2', 24 | '1998': 'decretos1/1998-decretos-1', 25 | '1997': 'decretos1/1997-decretos', 26 | '1996': 'decretos1/1996-decretos', 27 | '1995': 'decretos1/1995-decretos', 28 | '1994': 'decretos1/1994-decretos-1', 29 | '1993': 'decretos1/1993-decretos-1', 30 | '1992': 'decretos1/1992-decretos', 31 | '1991': 'decretos1/1991-decretos', 32 | '1990': 'decretos1/1990-decretos-1', 33 | '1989': 'decretos1/1989-decretos', 34 | '1988': 'decretos1/1988-decretos-1', 35 | '1987': 'decretos1/1987-decretos-1', 36 | '1986': 'decretos1/1986-decretos-1', 37 | '1985': 'decretos1/1985-decretos-1', 38 | '1984': 'decretos1/1984-decretos-1', 39 | '1983': 'decretos1/1983-decretos-1', 40 | '1982': 'decretos1/1982-decretos', 41 | '1981': 'decretos1/1981-decretos-1', 42 | '1980': 'decretos1/1980-decretos-1', 43 | '1970-1979': 'decretos1/1979-a-1970-decretos-1', 44 | '1960-1969': 'decretos1/1969-a-1960-decretos-1', 45 | 'anterior-1960': 'decretos1/anteriores-a-1960-decretos' 46 | } 47 | 48 | urls_leis_ordinarias_planalto = { 49 | '2020': 'leis-ordinarias/2020-leis-ordinarias', 50 | '2019': 'leis-ordinarias/2019-leis-ordinarias', 51 | '2018': 'leis-ordinarias/2018-leis-ordinarias', 52 | '2017': 'leis-ordinarias/2017-leis-ordinarias', 53 | '2016': 'leis-ordinarias/leis-ordinarias-2016', 54 | '2015': 'leis-ordinarias/leis-ordinarias-2015', 55 | '2014': 'leis-ordinarias/2014-leis-ordinarias', 56 | '2013': 'leis-ordinarias/2013-leis-ordinarias', 57 | '2012': 'leis-ordinarias/2012-leis-ordinarias-2', 58 | '2011': 'leis-ordinarias/2011-leis-ordinarias', 59 | '2010': 'leis-ordinarias/2010-leis-ordinarias-1', 60 | '2009': 'leis-ordinarias/2009-leis-ordinarias-1', 61 | '2008': 'leis-ordinarias/2008-leis-ordinarias-1', 62 | '2007': 'leis-ordinarias/2007-leis-ordinarias-1', 63 | '2006': 'leis-ordinarias/2006-leis-ordinarias-1', 64 | '2005': 'leis-ordinarias/2005', 65 | '2004': 'leis-ordinarias/2004', 66 | '2003': 'leis-ordinarias/2003', 67 | '2002': 'leis-ordinarias/2002-leis-ordinarias-1', 68 | '2001': 'leis-ordinarias/2001-leis-ordinarias-1', 69 | '2000': 'leis-ordinarias/2000-leis-ordinarias-1', 70 | '1999': 'leis-ordinarias/1999-leis-ordinarias-1', 71 | '1998': 'leis-ordinarias/1998-leis-ordinarias', 72 | '1997': 'leis-ordinarias/1997-leis-ordinarias-1', 73 | '1996': 'leis-ordinarias/1996', 74 | '1995': 'leis-ordinarias/1995', 75 | '1994': 'leis-ordinarias/1994', 76 | '1993': 'leis-ordinarias/1993', 77 | '1992': 'leis-ordinarias/1992', 78 | '1991': 'leis-ordinarias/1991', 79 | '1990': 'leis-ordinarias/1990', 80 | '1989': 'leis-ordinarias/1989', 81 | '1988': 'leis-ordinarias/1988', 82 | '1981-1987': 'leis-ordinarias/1987-a-1981-leis-ordinarias', 83 | '1960-1980': 'leis-ordinarias/1980-a-1960-leis-ordinarias', 84 | 'anterior-1960': 'leis-ordinarias/anteriores-a-1960-leis-ordinarias' 85 | } 86 | 87 | urls_medidas_provisorias = { 88 | '2019-2022': 'medidas-provisorias/2019-a-2022', 89 | '2015-2018': 'medidas-provisorias/medidas-provisorias-2015-a-2018', 90 | '2011-2014': 'medidas-provisorias/2011-a-2014', 91 | '2007-2010': 'medidas-provisorias/2007-a-2010', 92 | '2003-2006': 'medidas-provisorias/2003-a-2006', 93 | '2001-2002': 'medidas-provisorias/2001-e-2002', 94 | '2000-2001': 'medidas-provisorias/2000-e-2001', 95 | '1996-1999': 'medidas-provisorias/1996-a-1999', 96 | '1992-1995': 'medidas-provisorias/1992-a-1995', 97 | '1988-1991': 'medidas-provisorias/1988-a-1991' 98 | } 99 | 100 | urls_projetos_leis_casa_civil = { 101 | **{2019: 'projetos-de-lei-m/copy_of_pl-2019'}, 102 | **{year: 'projetos-de-lei-m/pl-{0}'.format(year) for year in range(2018, 1993, -1)} 103 | } 104 | urls_projetos_leis_complementares_casa_civil = { 105 | **{2019: 'projetos-de-lei-m/plp-2019'}, 106 | **{year: 'projetos-de-lei-m/plp-{0}'.format(year) for year in range(2017, 1997, -1)} 107 | } 108 | urls_projetos_leis_congresso_casa_civil = { 109 | **{2019: 'projetos-de-lei-m/pln-2019', 2018: 'projetos-de-lei-m/pln-2018-1'}, 110 | **{year: 'projetos-de-lei-m/pln-{0}'.format(year) for year in range(2017, 2009, -1)} 111 | } 112 | -------------------------------------------------------------------------------- /leis_brasileiras/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def extract_projeto(s, law_type=None): 5 | clean_s = s.replace('.', '').replace("//", "/") 6 | clean_s = re.sub(r'\s', '', clean_s) 7 | if law_type == 'lei': 8 | m = re.search( 9 | r'(ProjetodeLei|ProjLei)' 10 | r'(nº)?(\d+)\-?\w?(/|,de|de)(\d+)', 11 | clean_s) 12 | elif law_type == 'lei_comp': 13 | m = re.search( 14 | r'(ProjetodeLei|ProjLei)Complementar' 15 | r'(nº)?(\d+)\-?\w?(/|,de|de|)(\d+)', 16 | clean_s) 17 | elif law_type == 'decreto': 18 | m = re.search( 19 | r'(ProjetodeDecretoLegislativo|ProjDecretoLegislativo)' 20 | r'(nº)?(\d+)\-?\w?(/|,de|de|)(\d+)', 21 | clean_s) 22 | elif law_type == 'emenda': 23 | m = re.search( 24 | r'(ProjetodeEmendaàLeiOrgânica|PropostadeEmenda)' 25 | r'(nº)?(\d+)\-?\w?(/|,de|de|)(\d+)', 26 | clean_s) 27 | else: 28 | m = None 29 | if m: 30 | nr = m.group(3).zfill(4) 31 | ano = m.group(5) 32 | return '{}/{}'.format(nr, ano) 33 | return '' 34 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.8.0 2 | certifi==2019.6.16 3 | chardet==3.0.4 4 | python-decouple==3.1 5 | idna==2.8 6 | lxml==4.3.4 7 | requests==2.22.0 8 | selenium==3.141.0 9 | soupsieve==1.9.2 10 | tqdm==4.32.2 11 | urllib3==1.25.3 12 | Unidecode==1.0.23 13 | SQLAlchemy==1.3.8 14 | pandas==0.23.4 15 | numpy==1.15.4 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/leis-brasileiras/a851cbdcec74389bc5c3b7ceeb56da9e7e88bf1c/tests/__init__.py -------------------------------------------------------------------------------- /tests/fixtures.py: -------------------------------------------------------------------------------- 1 | # Fixtures extract_projeto 2 | 3 | string_projeto_lei_1 = 'Projeto de Lei nº 231A/2019' 4 | string_projeto_lei_2 = 'Proj. Lei nº 231-A, de 2019' 5 | string_projeto_leicomp_1 = 'Projeto de Lei Complementar 231 de 2019' 6 | string_projeto_leicomp_2 = 'Proj. Lei Complementar 231/2019' 7 | string_projeto_decreto_1 = 'Projeto de Decreto Legislativo 231 de 2019' 8 | string_projeto_emenda_1 = 'Projeto de Emenda à Lei Orgânica 231 de 2019' 9 | string_projeto_emenda_2 = 'Proposta de Emenda nº 231, de 2019' 10 | string_no_projeto = 'Sem projeto' 11 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from leis_brasileiras.utils import ( 4 | extract_projeto 5 | ) 6 | from tests.fixtures import ( 7 | string_projeto_lei_1, string_projeto_lei_2, 8 | string_projeto_leicomp_1, string_projeto_leicomp_2, 9 | string_projeto_decreto_1, string_projeto_emenda_1, 10 | string_projeto_emenda_2, string_no_projeto 11 | ) 12 | 13 | 14 | class TestUtils(TestCase): 15 | def test_extract_projeto(self): 16 | expected = '0231/2019' 17 | 18 | output_1 = extract_projeto(string_projeto_lei_1, 'lei') 19 | output_2 = extract_projeto(string_projeto_lei_2, 'lei') 20 | output_3 = extract_projeto(string_projeto_leicomp_1, 'lei_comp') 21 | output_4 = extract_projeto(string_projeto_leicomp_2, 'lei_comp') 22 | output_5 = extract_projeto(string_projeto_decreto_1, 'decreto') 23 | output_6 = extract_projeto(string_projeto_emenda_1, 'emenda') 24 | output_7 = extract_projeto(string_projeto_emenda_2, 'emenda') 25 | output_8 = extract_projeto(string_no_projeto, 'lei') 26 | output_9 = extract_projeto(string_no_projeto, law_type=None) 27 | 28 | assert expected == output_1 29 | assert expected == output_2 30 | assert expected == output_3 31 | assert expected == output_4 32 | assert expected == output_5 33 | assert expected == output_6 34 | assert expected == output_7 35 | assert '' == output_8 36 | assert '' == output_9 37 | --------------------------------------------------------------------------------