pyFBDS 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfbds-0.1.0/LICENSE +21 -0
- pyfbds-0.1.0/PKG-INFO +55 -0
- pyfbds-0.1.0/README.md +37 -0
- pyfbds-0.1.0/pyFBDS.egg-info/PKG-INFO +55 -0
- pyfbds-0.1.0/pyFBDS.egg-info/SOURCES.txt +23 -0
- pyfbds-0.1.0/pyFBDS.egg-info/dependency_links.txt +1 -0
- pyfbds-0.1.0/pyFBDS.egg-info/requires.txt +9 -0
- pyfbds-0.1.0/pyFBDS.egg-info/top_level.txt +1 -0
- pyfbds-0.1.0/pyFDBS/__init__.py +4 -0
- pyfbds-0.1.0/pyFDBS/requests/__init__.py +0 -0
- pyfbds-0.1.0/pyFDBS/requests/cache.py +42 -0
- pyfbds-0.1.0/pyFDBS/requests/download.py +148 -0
- pyfbds-0.1.0/pyFDBS/requests/logger.py +168 -0
- pyfbds-0.1.0/pyFDBS/requests/teste.py +13 -0
- pyfbds-0.1.0/pyFDBS/requests/web.py +78 -0
- pyfbds-0.1.0/pyFDBS/scraper/__init__.py +1 -0
- pyfbds-0.1.0/pyFDBS/scraper/page/__init__.py +0 -0
- pyfbds-0.1.0/pyFDBS/scraper/page/sss.py +0 -0
- pyfbds-0.1.0/pyFDBS/scraper/webdriver/__init__.py +2 -0
- pyfbds-0.1.0/pyFDBS/scraper/webdriver/chrome.py +295 -0
- pyfbds-0.1.0/pyFDBS/scraper/webdriver/config.py +15 -0
- pyfbds-0.1.0/pyFDBS/scraper/webdriver/firefox.py +174 -0
- pyfbds-0.1.0/pyFDBS/scraper/webdriver/gecko.py +151 -0
- pyfbds-0.1.0/pyproject.toml +29 -0
- pyfbds-0.1.0/setup.cfg +4 -0
pyfbds-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Open Geodata
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyfbds-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyFBDS
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: requests>=2.32.5
|
|
9
|
+
Requires-Dist: requests-cache>=1.2.1
|
|
10
|
+
Requires-Dist: tqdm>=4.67.1
|
|
11
|
+
Requires-Dist: lxml>=6.0.0
|
|
12
|
+
Requires-Dist: ipywidgets>=8.1.8
|
|
13
|
+
Requires-Dist: aiohttp>=3.13.2
|
|
14
|
+
Requires-Dist: nest-asyncio>=1.6.0
|
|
15
|
+
Requires-Dist: pandas>=2.3.3
|
|
16
|
+
Requires-Dist: selenium>=4.38.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# pyFBDS
|
|
20
|
+
|
|
21
|
+
[](https://github.com/open-geodata/pyFBDS)
|
|
22
|
+
[](https://pypi.org/project/pyfbds/)<br>
|
|
23
|
+
[](https://pyFBDS.readthedocs.io/)
|
|
24
|
+
[](https://github.com/michelmetran/pyFBDS/actions/workflows/publish-to-pypipoetry.yml)
|
|
25
|
+
|
|
26
|
+
_Scripts_ para obter dados espaciais do [**repositório público de mapas e _shapefiles_ para _download_**](https://geo.fbds.org.br/). Veja mais na documentação:
|
|
27
|
+
|
|
28
|
+
> [https://pyFBDS.readthedocs.io/](https://pyFBDS.readthedocs.io/)
|
|
29
|
+
|
|
30
|
+
<br>
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Como Instalar?
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
pip3 install pyFBDS
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
<br>
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Como Usar?
|
|
45
|
+
|
|
46
|
+
```shell
|
|
47
|
+
# Importa pacote
|
|
48
|
+
import pyFBDS
|
|
49
|
+
|
|
50
|
+
# Instancia Objeto
|
|
51
|
+
fbds = pyFBDS.Repo(output_path='.')
|
|
52
|
+
|
|
53
|
+
# Chama o método
|
|
54
|
+
fbds.get_municipio(id_ibge=353243)
|
|
55
|
+
```
|
pyfbds-0.1.0/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# pyFBDS
|
|
2
|
+
|
|
3
|
+
[](https://github.com/open-geodata/pyFBDS)
|
|
4
|
+
[](https://pypi.org/project/pyfbds/)<br>
|
|
5
|
+
[](https://pyFBDS.readthedocs.io/)
|
|
6
|
+
[](https://github.com/michelmetran/pyFBDS/actions/workflows/publish-to-pypipoetry.yml)
|
|
7
|
+
|
|
8
|
+
_Scripts_ para obter dados espaciais do [**repositório público de mapas e _shapefiles_ para _download_**](https://geo.fbds.org.br/). Veja mais na documentação:
|
|
9
|
+
|
|
10
|
+
> [https://pyFBDS.readthedocs.io/](https://pyFBDS.readthedocs.io/)
|
|
11
|
+
|
|
12
|
+
<br>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Como Instalar?
|
|
17
|
+
|
|
18
|
+
```shell
|
|
19
|
+
pip3 install pyFBDS
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
<br>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Como Usar?
|
|
27
|
+
|
|
28
|
+
```shell
|
|
29
|
+
# Importa pacote
|
|
30
|
+
import pyFBDS
|
|
31
|
+
|
|
32
|
+
# Instancia Objeto
|
|
33
|
+
fbds = pyFBDS.Repo(output_path='.')
|
|
34
|
+
|
|
35
|
+
# Chama o método
|
|
36
|
+
fbds.get_municipio(id_ibge=353243)
|
|
37
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyFBDS
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: requests>=2.32.5
|
|
9
|
+
Requires-Dist: requests-cache>=1.2.1
|
|
10
|
+
Requires-Dist: tqdm>=4.67.1
|
|
11
|
+
Requires-Dist: lxml>=6.0.0
|
|
12
|
+
Requires-Dist: ipywidgets>=8.1.8
|
|
13
|
+
Requires-Dist: aiohttp>=3.13.2
|
|
14
|
+
Requires-Dist: nest-asyncio>=1.6.0
|
|
15
|
+
Requires-Dist: pandas>=2.3.3
|
|
16
|
+
Requires-Dist: selenium>=4.38.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# pyFBDS
|
|
20
|
+
|
|
21
|
+
[](https://github.com/open-geodata/pyFBDS)
|
|
22
|
+
[](https://pypi.org/project/pyfbds/)<br>
|
|
23
|
+
[](https://pyFBDS.readthedocs.io/)
|
|
24
|
+
[](https://github.com/michelmetran/pyFBDS/actions/workflows/publish-to-pypipoetry.yml)
|
|
25
|
+
|
|
26
|
+
_Scripts_ para obter dados espaciais do [**repositório público de mapas e _shapefiles_ para _download_**](https://geo.fbds.org.br/). Veja mais na documentação:
|
|
27
|
+
|
|
28
|
+
> [https://pyFBDS.readthedocs.io/](https://pyFBDS.readthedocs.io/)
|
|
29
|
+
|
|
30
|
+
<br>
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Como Instalar?
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
pip3 install pyFBDS
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
<br>
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Como Usar?
|
|
45
|
+
|
|
46
|
+
```shell
|
|
47
|
+
# Importa pacote
|
|
48
|
+
import pyFBDS
|
|
49
|
+
|
|
50
|
+
# Instancia Objeto
|
|
51
|
+
fbds = pyFBDS.Repo(output_path='.')
|
|
52
|
+
|
|
53
|
+
# Chama o método
|
|
54
|
+
fbds.get_municipio(id_ibge=353243)
|
|
55
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
pyFBDS.egg-info/PKG-INFO
|
|
5
|
+
pyFBDS.egg-info/SOURCES.txt
|
|
6
|
+
pyFBDS.egg-info/dependency_links.txt
|
|
7
|
+
pyFBDS.egg-info/requires.txt
|
|
8
|
+
pyFBDS.egg-info/top_level.txt
|
|
9
|
+
pyFDBS/__init__.py
|
|
10
|
+
pyFDBS/requests/__init__.py
|
|
11
|
+
pyFDBS/requests/cache.py
|
|
12
|
+
pyFDBS/requests/download.py
|
|
13
|
+
pyFDBS/requests/logger.py
|
|
14
|
+
pyFDBS/requests/teste.py
|
|
15
|
+
pyFDBS/requests/web.py
|
|
16
|
+
pyFDBS/scraper/__init__.py
|
|
17
|
+
pyFDBS/scraper/page/__init__.py
|
|
18
|
+
pyFDBS/scraper/page/sss.py
|
|
19
|
+
pyFDBS/scraper/webdriver/__init__.py
|
|
20
|
+
pyFDBS/scraper/webdriver/chrome.py
|
|
21
|
+
pyFDBS/scraper/webdriver/config.py
|
|
22
|
+
pyFDBS/scraper/webdriver/firefox.py
|
|
23
|
+
pyFDBS/scraper/webdriver/gecko.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyFDBS
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Para cache das requisições
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import timedelta
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
import requests_cache
|
|
9
|
+
|
|
10
|
+
# Configuração do cache
|
|
11
|
+
requests_cache.install_cache(
|
|
12
|
+
cache_name='fbds_cache', # Nome do arquivo de cache
|
|
13
|
+
backend='sqlite', # Backend para armazenamento (SQLite)
|
|
14
|
+
expire_after=timedelta(days=3), # Cache expira após 7 dias
|
|
15
|
+
allowable_methods=('GET', 'POST'), # Métodos HTTP permitidos
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_request(url):
|
|
20
|
+
"""
|
|
21
|
+
Faz uma requisição HTTP com suporte a cache
|
|
22
|
+
|
|
23
|
+
Parameters:
|
|
24
|
+
-----------
|
|
25
|
+
url : str
|
|
26
|
+
URL para fazer a requisição
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
--------
|
|
30
|
+
response : requests.Response
|
|
31
|
+
Resposta da requisição
|
|
32
|
+
is_cached : bool
|
|
33
|
+
Indica se a resposta veio do cache
|
|
34
|
+
"""
|
|
35
|
+
response = requests.get(url)
|
|
36
|
+
is_cached = getattr(response, 'from_cache', False)
|
|
37
|
+
|
|
38
|
+
# Informação sobre o cache
|
|
39
|
+
# cache_status = 'CACHE' if is_cached else 'NOVA REQUISIÇÃO'
|
|
40
|
+
# print(f"{cache_status}: {url}")
|
|
41
|
+
|
|
42
|
+
return response, is_cached
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Módulo para download dos dados usando asyncio
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import aiohttp
|
|
9
|
+
from tqdm.asyncio import tqdm_asyncio
|
|
10
|
+
from tqdm.notebook import tqdm
|
|
11
|
+
|
|
12
|
+
from .cache import make_request
|
|
13
|
+
from .logger import FBDSLogger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def download_file_async(session, url_info, output_dir):
|
|
17
|
+
"""
|
|
18
|
+
Download assíncrono de um único arquivo
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
-----------
|
|
22
|
+
session : aiohttp.ClientSession
|
|
23
|
+
Sessão HTTP assíncrona
|
|
24
|
+
url_info : dict
|
|
25
|
+
Dicionário com informações do arquivo (url, name, etc)
|
|
26
|
+
output_dir : str or Path
|
|
27
|
+
Diretório onde salvar o arquivo
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
url = url_info['url']
|
|
31
|
+
# Remove o base URL e usa o caminho relativo
|
|
32
|
+
relative_path = url.replace('https://geo.fbds.org.br/', '')
|
|
33
|
+
output_path = Path(output_dir) / relative_path
|
|
34
|
+
|
|
35
|
+
# Cria o diretório se não existir
|
|
36
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
# Faz o download
|
|
39
|
+
async with session.get(url) as response:
|
|
40
|
+
if response.status == 200:
|
|
41
|
+
content = await response.read()
|
|
42
|
+
|
|
43
|
+
# Salva o arquivo
|
|
44
|
+
with open(output_path, 'wb') as f:
|
|
45
|
+
f.write(content)
|
|
46
|
+
|
|
47
|
+
result = {
|
|
48
|
+
'nome': url_info['name'],
|
|
49
|
+
'status': 'sucesso',
|
|
50
|
+
'size': len(content),
|
|
51
|
+
}
|
|
52
|
+
else:
|
|
53
|
+
result = {
|
|
54
|
+
'nome': url_info['name'],
|
|
55
|
+
'status': 'erro',
|
|
56
|
+
'erro': f'Status code: {response.status}',
|
|
57
|
+
}
|
|
58
|
+
except Exception as e:
|
|
59
|
+
result = {'nome': url_info['name'], 'status': 'erro', 'erro': str(e)}
|
|
60
|
+
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def download_files_async(url_list, output_dir, max_concurrent=5):
|
|
65
|
+
"""
|
|
66
|
+
Download assíncrono de múltiplos arquivos
|
|
67
|
+
|
|
68
|
+
Parameters:
|
|
69
|
+
-----------
|
|
70
|
+
url_list : list
|
|
71
|
+
Lista de dicionários com informações dos arquivos
|
|
72
|
+
output_dir : str or Path
|
|
73
|
+
Diretório onde salvar os arquivos
|
|
74
|
+
max_concurrent : int
|
|
75
|
+
Número máximo de downloads simultâneos
|
|
76
|
+
"""
|
|
77
|
+
# Configura conexão com limite de conexões simultâneas
|
|
78
|
+
conn = aiohttp.TCPConnector(limit=max_concurrent)
|
|
79
|
+
|
|
80
|
+
async with aiohttp.ClientSession(connector=conn) as session:
|
|
81
|
+
# Cria a lista de tarefas
|
|
82
|
+
tasks = []
|
|
83
|
+
for url_info in url_list:
|
|
84
|
+
task = download_file_async(session, url_info, output_dir)
|
|
85
|
+
tasks.append(task)
|
|
86
|
+
|
|
87
|
+
# Executa as tasks com barra de progresso
|
|
88
|
+
results = await tqdm_asyncio.gather(
|
|
89
|
+
*tasks,
|
|
90
|
+
desc="Downloading files",
|
|
91
|
+
total=len(tasks),
|
|
92
|
+
ascii=True, # Melhor compatibilidade
|
|
93
|
+
mininterval=0.5, # Atualiza a cada 0.5 segundos
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return results
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def download_files_parallel(
|
|
100
|
+
url_list, output_dir, max_concurrent=5, logger=None
|
|
101
|
+
):
|
|
102
|
+
"""
|
|
103
|
+
Wrapper para executar o download assíncrono
|
|
104
|
+
|
|
105
|
+
Parameters:
|
|
106
|
+
-----------
|
|
107
|
+
url_list : list
|
|
108
|
+
Lista de dicionários com informações dos arquivos
|
|
109
|
+
output_dir : str or Path
|
|
110
|
+
Diretório onde salvar os arquivos
|
|
111
|
+
max_concurrent : int
|
|
112
|
+
Número máximo de downloads simultâneos
|
|
113
|
+
logger : FBDSLogger, optional
|
|
114
|
+
Logger existente para usar. Se None, cria um novo.
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
# Usa o logger fornecido ou cria um novo
|
|
118
|
+
if logger is None:
|
|
119
|
+
logger = FBDSLogger()
|
|
120
|
+
logger.start_download_session()
|
|
121
|
+
|
|
122
|
+
# Pega o loop de eventos atual ou cria um novo se não existir
|
|
123
|
+
try:
|
|
124
|
+
loop = asyncio.get_event_loop()
|
|
125
|
+
except RuntimeError:
|
|
126
|
+
loop = asyncio.new_event_loop()
|
|
127
|
+
asyncio.set_event_loop(loop)
|
|
128
|
+
|
|
129
|
+
# Se estamos em um notebook IPython, use o nest_asyncio
|
|
130
|
+
try:
|
|
131
|
+
import nest_asyncio
|
|
132
|
+
|
|
133
|
+
nest_asyncio.apply()
|
|
134
|
+
except ImportError:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
# Executa o download assíncrono
|
|
138
|
+
results = loop.run_until_complete(
|
|
139
|
+
download_files_async(url_list, output_dir, max_concurrent)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Analisa e registra os resultados
|
|
143
|
+
logger.analyze_results(results)
|
|
144
|
+
return results
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.logger.error(f"Erro durante o download: {str(e)}")
|
|
148
|
+
return []
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sistema de logs para a aplicação FBDS
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# Obtém o diretório raiz do projeto
|
|
11
|
+
PROJECT_ROOT = Path(__file__).parent.parent
|
|
12
|
+
DEFAULT_LOG_DIR = PROJECT_ROOT / 'logs'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FBDSLogger:
|
|
16
|
+
_instance = None
|
|
17
|
+
_initialized = False
|
|
18
|
+
|
|
19
|
+
def __new__(cls, log_dir=None, new_session=False):
|
|
20
|
+
# Verifica se já existe uma instância ou se foi pedida uma nova sessão
|
|
21
|
+
if cls._instance is None or new_session:
|
|
22
|
+
# Cria uma nova instância se não existir ou se new_session=True
|
|
23
|
+
cls._instance = super(FBDSLogger, cls).__new__(cls)
|
|
24
|
+
# Marca como não inicializado para forçar a execução do __init__
|
|
25
|
+
cls._initialized = False
|
|
26
|
+
# Retorna a instância (seja ela nova ou existente)
|
|
27
|
+
return cls._instance
|
|
28
|
+
|
|
29
|
+
def __init__(self, log_dir=None, new_session=False):
|
|
30
|
+
if not self._initialized or new_session:
|
|
31
|
+
# Usa o diretório fornecido ou o padrão
|
|
32
|
+
self.log_dir = Path(log_dir) if log_dir else DEFAULT_LOG_DIR
|
|
33
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
|
|
35
|
+
# Configura o logger principal
|
|
36
|
+
self.logger = logging.getLogger('FBDS')
|
|
37
|
+
self.logger.setLevel(logging.INFO)
|
|
38
|
+
|
|
39
|
+
# Remove handlers anteriores se existirem
|
|
40
|
+
for handler in self.logger.handlers[:]:
|
|
41
|
+
self.logger.removeHandler(handler)
|
|
42
|
+
|
|
43
|
+
# Cria handlers
|
|
44
|
+
self._setup_handlers()
|
|
45
|
+
|
|
46
|
+
# Dicionário para armazenar estatísticas
|
|
47
|
+
self.stats = {
|
|
48
|
+
'total': 0,
|
|
49
|
+
'success': 0,
|
|
50
|
+
'errors': 0,
|
|
51
|
+
'cached': 0,
|
|
52
|
+
'start_time': None,
|
|
53
|
+
'end_time': None,
|
|
54
|
+
'errors_list': [],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
self._initialized = True
|
|
58
|
+
|
|
59
|
+
def _setup_handlers(self):
|
|
60
|
+
# Handler para arquivo
|
|
61
|
+
# Usa apenas a data, não o timestamp completo
|
|
62
|
+
date_str = datetime.now().strftime('%Y%m%d')
|
|
63
|
+
self.log_file = self.log_dir / f'fbds_{date_str}.log'
|
|
64
|
+
# self.stats_file = self.log_dir / f'stats_{date_str}.json'
|
|
65
|
+
|
|
66
|
+
file_handler = logging.FileHandler(
|
|
67
|
+
self.log_file, encoding='utf-8', mode='a'
|
|
68
|
+
)
|
|
69
|
+
file_handler.setLevel(logging.INFO)
|
|
70
|
+
|
|
71
|
+
# Handler para console
|
|
72
|
+
console_handler = logging.StreamHandler()
|
|
73
|
+
console_handler.setLevel(logging.INFO)
|
|
74
|
+
|
|
75
|
+
# Formato do log
|
|
76
|
+
formatter = logging.Formatter(
|
|
77
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
78
|
+
datefmt='%Y-%m-%d %H:%M:%S',
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
file_handler.setFormatter(formatter)
|
|
82
|
+
console_handler.setFormatter(formatter)
|
|
83
|
+
|
|
84
|
+
# Adiciona handlers ao logger
|
|
85
|
+
self.logger.addHandler(file_handler)
|
|
86
|
+
self.logger.addHandler(console_handler)
|
|
87
|
+
|
|
88
|
+
def start_download_session(self):
|
|
89
|
+
"""Inicia uma nova sessão de download"""
|
|
90
|
+
self.stats = {
|
|
91
|
+
'total': 0,
|
|
92
|
+
'success': 0,
|
|
93
|
+
'errors': 0,
|
|
94
|
+
'cached': 0,
|
|
95
|
+
'start_time': datetime.now(),
|
|
96
|
+
'end_time': None,
|
|
97
|
+
'errors_list': [],
|
|
98
|
+
}
|
|
99
|
+
self.logger.info('Iniciando nova sessão de download')
|
|
100
|
+
|
|
101
|
+
def end_download_session(self):
|
|
102
|
+
"""Finaliza a sessão de download e gera relatório"""
|
|
103
|
+
self.stats['end_time'] = datetime.now()
|
|
104
|
+
duration = self.stats['end_time'] - self.stats['start_time']
|
|
105
|
+
|
|
106
|
+
# Log do resumo
|
|
107
|
+
self.logger.info(f"=== Resumo da Sessão de Download ===")
|
|
108
|
+
self.logger.info(
|
|
109
|
+
f"Downloads com sucesso: {self.stats['success']} de {self.stats['total']}"
|
|
110
|
+
)
|
|
111
|
+
if self.stats['errors'] > 0:
|
|
112
|
+
self.logger.error(
|
|
113
|
+
f"Erros: {self.stats['errors']} de {self.stats['total']}"
|
|
114
|
+
)
|
|
115
|
+
# self.logger.info(
|
|
116
|
+
# f"Arquivos do cache: {self.stats['cached']} de {self.stats['total']}"
|
|
117
|
+
# )
|
|
118
|
+
self.logger.info(f"Duração total: {duration}")
|
|
119
|
+
|
|
120
|
+
# Se houver erros, registra eles
|
|
121
|
+
if self.stats['errors_list']:
|
|
122
|
+
self.logger.error("Erros encontrados:")
|
|
123
|
+
for error in self.stats['errors_list']:
|
|
124
|
+
self.logger.error(f"- {error['nome']}: {error['erro']}")
|
|
125
|
+
|
|
126
|
+
# # Salva as estatísticas em JSON
|
|
127
|
+
# stats_file = (
|
|
128
|
+
# self.log_dir
|
|
129
|
+
# / f'stats_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json'
|
|
130
|
+
# )
|
|
131
|
+
# with open(stats_file, 'w', encoding='utf-8') as f:
|
|
132
|
+
# # Converte datetime para string
|
|
133
|
+
# stats_dict = self.stats.copy()
|
|
134
|
+
# stats_dict['start_time'] = self.stats['start_time'].isoformat()
|
|
135
|
+
# stats_dict['end_time'] = self.stats['end_time'].isoformat()
|
|
136
|
+
# json.dump(stats_dict, f, ensure_ascii=False, indent=4)
|
|
137
|
+
|
|
138
|
+
def log_result(self, result):
|
|
139
|
+
"""Registra o resultado de um download"""
|
|
140
|
+
self.stats['total'] += 1
|
|
141
|
+
|
|
142
|
+
if result.get('cached', False):
|
|
143
|
+
self.stats['cached'] += 1
|
|
144
|
+
self.logger.info(f"Arquivo em cache: {result['nome']}")
|
|
145
|
+
|
|
146
|
+
if result['status'] == 'sucesso':
|
|
147
|
+
self.stats['success'] += 1
|
|
148
|
+
self.logger.info(
|
|
149
|
+
f"Download concluído: {result['nome']} ({result['size']} bytes)"
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
self.stats['errors'] += 1
|
|
153
|
+
self.stats['errors_list'].append(result)
|
|
154
|
+
self.logger.error(
|
|
155
|
+
f"Erro no download de {result['nome']}: {result['erro']}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def analyze_results(self, results):
|
|
159
|
+
"""
|
|
160
|
+
Analisa os resultados dos downloads
|
|
161
|
+
"""
|
|
162
|
+
self.start_download_session()
|
|
163
|
+
|
|
164
|
+
for result in results:
|
|
165
|
+
self.log_result(result)
|
|
166
|
+
|
|
167
|
+
self.end_download_session()
|
|
168
|
+
return self.stats
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# """
|
|
2
|
+
# Sistema de logs para a aplicação FBDS
|
|
3
|
+
# """
|
|
4
|
+
|
|
5
|
+
# import json
|
|
6
|
+
# import logging
|
|
7
|
+
# from datetime import datetime
|
|
8
|
+
# from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# # Obtém o diretório raiz do projeto
|
|
11
|
+
# PROJECT_ROOT = Path(__file__).parent.parent
|
|
12
|
+
# DEFAULT_LOG_DIR = PROJECT_ROOT / 'logs'
|
|
13
|
+
# print(DEFAULT_LOG_DIR)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Summary
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from urllib.parse import urljoin
|
|
7
|
+
|
|
8
|
+
from lxml import html
|
|
9
|
+
|
|
10
|
+
from .cache import make_request
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FBDS:
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
self.url_base = "https://geo.fbds.org.br/"
|
|
16
|
+
|
|
17
|
+
def get_links(self, url, ignore_first):
|
|
18
|
+
# Usa a função make_request com cache
|
|
19
|
+
response, is_cached = make_request(url)
|
|
20
|
+
response.raise_for_status()
|
|
21
|
+
tree = html.fromstring(response.content)
|
|
22
|
+
|
|
23
|
+
list_folders = []
|
|
24
|
+
folders = tree.xpath("//tr")
|
|
25
|
+
|
|
26
|
+
# Ignora o primeiro tr, que é o cabeçalho
|
|
27
|
+
folders = folders[ignore_first:]
|
|
28
|
+
|
|
29
|
+
for folder in folders:
|
|
30
|
+
# Get Data
|
|
31
|
+
link = folder.xpath('.//td[@class="fb-n"]/a/@href')[0]
|
|
32
|
+
tipo = folder.xpath('.//td[@class="fb-i"]/img/@src')[0]
|
|
33
|
+
name = folder.xpath('.//td[@class="fb-n"]/a')[0].text.strip()
|
|
34
|
+
data = folder.xpath('.//td[@class="fb-d"]')[0].text.strip()
|
|
35
|
+
size = folder.xpath('.//td[@class="fb-s"]')[0].text.strip()
|
|
36
|
+
|
|
37
|
+
# Append to list
|
|
38
|
+
list_folders.append(
|
|
39
|
+
{
|
|
40
|
+
"url": urljoin(self.url_base, link),
|
|
41
|
+
"type": Path(tipo).stem,
|
|
42
|
+
"name": name,
|
|
43
|
+
"date": data,
|
|
44
|
+
"size": size,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
return list_folders
|
|
48
|
+
|
|
49
|
+
def get_states(self) -> list[dict]:
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
:return: _description_
|
|
54
|
+
:rtype: list[dict]
|
|
55
|
+
"""
|
|
56
|
+
return self.get_links(url=self.url_base, ignore_first=1)
|
|
57
|
+
|
|
58
|
+
def get_municipalities(self, uf):
|
|
59
|
+
state = self.get_state(uf=uf)
|
|
60
|
+
url = state["url"]
|
|
61
|
+
return self.get_links(url=url, ignore_first=2)
|
|
62
|
+
|
|
63
|
+
def get_layers(self, municipality, uf):
|
|
64
|
+
municipalitie = self.get_municipalitie(municipality=municipality, uf=uf)
|
|
65
|
+
url = municipalitie["url"]
|
|
66
|
+
return self.get_links(url=url, ignore_first=2)
|
|
67
|
+
|
|
68
|
+
def get_state(self, uf=None):
|
|
69
|
+
states = self.get_states()
|
|
70
|
+
return [x for x in states if x["name"] == uf][0]
|
|
71
|
+
|
|
72
|
+
def get_municipalitie(self, municipality=None, uf=None):
|
|
73
|
+
municipalities = self.get_municipalities(uf=uf)
|
|
74
|
+
return [x for x in municipalities if x["name"] == municipality][0]
|
|
75
|
+
|
|
76
|
+
def get_layer(self, municipality=None, uf=None, layer=None):
|
|
77
|
+
layers = self.get_layers(municipality=municipality, uf=uf)
|
|
78
|
+
return [x for x in layers if x["name"] == layer][0]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#from . import page, webdriver, search, outros, params
|
|
File without changes
|
|
File without changes
|