jupiterweb-scraper 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.
- jupiterweb_scraper-0.1.0/LICENSE +21 -0
- jupiterweb_scraper-0.1.0/PKG-INFO +96 -0
- jupiterweb_scraper-0.1.0/README.md +51 -0
- jupiterweb_scraper-0.1.0/jupiterweb/__init__.py +19 -0
- jupiterweb_scraper-0.1.0/jupiterweb/data/institutos.json +382 -0
- jupiterweb_scraper-0.1.0/jupiterweb/disciplina.py +373 -0
- jupiterweb_scraper-0.1.0/jupiterweb/instituto.py +61 -0
- jupiterweb_scraper-0.1.0/jupiterweb/jupiterweb.py +15 -0
- jupiterweb_scraper-0.1.0/jupiterweb/paths.py +8 -0
- jupiterweb_scraper-0.1.0/jupiterweb/urls.py +10 -0
- jupiterweb_scraper-0.1.0/jupiterweb/utils.py +25 -0
- jupiterweb_scraper-0.1.0/jupiterweb_scraper.egg-info/PKG-INFO +96 -0
- jupiterweb_scraper-0.1.0/jupiterweb_scraper.egg-info/SOURCES.txt +16 -0
- jupiterweb_scraper-0.1.0/jupiterweb_scraper.egg-info/dependency_links.txt +1 -0
- jupiterweb_scraper-0.1.0/jupiterweb_scraper.egg-info/requires.txt +2 -0
- jupiterweb_scraper-0.1.0/jupiterweb_scraper.egg-info/top_level.txt +1 -0
- jupiterweb_scraper-0.1.0/pyproject.toml +42 -0
- jupiterweb_scraper-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 IME Jr
|
|
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.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jupiterweb-scraper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Extração de informações sobre disciplinas da Universidade de São Paulo a partir do Jupiterweb
|
|
5
|
+
Author: Davi Golebiovski, Isaque Nascimento, Lucas Kevin Silva Muniz
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 IME Jr
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Repository, https://github.com/davigole/jupiterweb-scraper
|
|
29
|
+
Project-URL: Issues, https://github.com/davigole/jupiterweb-scraper/issues
|
|
30
|
+
Keywords: usp,jupiterweb,scraper,universidade,web,university
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Requires-Python: >=3.9
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
License-File: LICENSE
|
|
42
|
+
Requires-Dist: requests>=2.34.2
|
|
43
|
+
Requires-Dist: beautifulsoup4>=4.15.0
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# Jupiterweb Scraper
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+

|
|
50
|
+
[](https://pypi.org/project/jupiterweb-scraper/)
|
|
51
|
+
|
|
52
|
+
Biblioteca para extração de informações sobre disciplinas da Universidade de São Paulo a partir do [Jupiterweb](https://uspdigital.usp.br/jupiterweb/).
|
|
53
|
+
|
|
54
|
+
## 📖 Sobre o projeto
|
|
55
|
+
|
|
56
|
+
O **Jupiterweb Scraper** é uma biblioteca Python que permite a extração de informações sobre disciplinas da Universidade de São Paulo a partir do [Jupiterweb](https://uspdigital.usp.br/jupiterweb/), o sistema oficial de gestão acadêmica da universidade.
|
|
57
|
+
|
|
58
|
+
A biblioteca foi desenvolvida por alunos do IME-USP, inicialmente para atender às demandas de um projeto interno da [IME Jr](https://imejr.com/) — a empresa júnior do instituto. No entanto, percebemos que a obtenção de dados do Jupiterweb é uma necessidade recorrente em projetos voltados à comunidade USP. Por isso, decidimos disponibilizar o scraper como projeto open-source, com o intuito de facilitar o desenvolvimento de novas ferramentas destinadas à universidade.
|
|
59
|
+
|
|
60
|
+
> ⚠️ **Aviso:** O Jupiterweb é um site antigo, com uma estrutura HTML complexa e por vezes inconsistente, o que torna o processo de scraping muito desafiador. É esperado que a biblioteca contenha erros que passaram despercebidos. Se você encontrar algum problema ou comportamento inesperado, pedimos que abra uma [Issue](https://github.com/davigole/jupiterweb-scraper/issues) descrevendo o ocorrido.
|
|
61
|
+
|
|
62
|
+
## 🚀 Instalação
|
|
63
|
+
|
|
64
|
+
Para instalar a biblioteca, utilize o comando
|
|
65
|
+
```bash
|
|
66
|
+
pip install jupiterweb-scraper
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Ou, para instalar a partir do repositório:
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/davigole/jupiterweb-scraper.git
|
|
72
|
+
cd jupiterweb-scraper
|
|
73
|
+
pip install -e .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 📚 Como usar
|
|
77
|
+
|
|
78
|
+
*A fazer*
|
|
79
|
+
|
|
80
|
+
## 🤝 Como contribuir
|
|
81
|
+
|
|
82
|
+
Caso queira contribuir mas não saiba por onde começar, aqui estão algumas melhorias e funcionalidades que ainda não foram implementadas:
|
|
83
|
+
|
|
84
|
+
- Buscar disciplinas por parte do nome, horário, vagas remanescentes, etc. (o Jupiterweb já tem essas funcionalidades)
|
|
85
|
+
- Obter os cursos oferecidos por cada unidade e informações sobre cada curso (descrição, objetivos, grade curricular, etc.), disponíveis na seção "Cursos de ingresso" do Jupiterweb
|
|
86
|
+
- Obter informações sobre docentes (nome, instituto, departamento, disciplinas que ministra/ministrou, etc.)
|
|
87
|
+
- Obter informações do calendário escolar, disponível em PDF no Jupiterweb
|
|
88
|
+
- Testes automáticos para verificar o funcionamento do scraping
|
|
89
|
+
- Documentação mais completa e exemplos de uso
|
|
90
|
+
- Qualquer alteração nas funções de scraping que torne a biblioteca mais robusta
|
|
91
|
+
|
|
92
|
+
Contribuições são muito bem-vindas!
|
|
93
|
+
|
|
94
|
+
## 📄 Licença
|
|
95
|
+
|
|
96
|
+
MIT © [IME Jr](https://imejr.com/)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Jupiterweb Scraper
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
[](https://pypi.org/project/jupiterweb-scraper/)
|
|
6
|
+
|
|
7
|
+
Biblioteca para extração de informações sobre disciplinas da Universidade de São Paulo a partir do [Jupiterweb](https://uspdigital.usp.br/jupiterweb/).
|
|
8
|
+
|
|
9
|
+
## 📖 Sobre o projeto
|
|
10
|
+
|
|
11
|
+
O **Jupiterweb Scraper** é uma biblioteca Python que permite a extração de informações sobre disciplinas da Universidade de São Paulo a partir do [Jupiterweb](https://uspdigital.usp.br/jupiterweb/), o sistema oficial de gestão acadêmica da universidade.
|
|
12
|
+
|
|
13
|
+
A biblioteca foi desenvolvida por alunos do IME-USP, inicialmente para atender às demandas de um projeto interno da [IME Jr](https://imejr.com/) — a empresa júnior do instituto. No entanto, percebemos que a obtenção de dados do Jupiterweb é uma necessidade recorrente em projetos voltados à comunidade USP. Por isso, decidimos disponibilizar o scraper como projeto open-source, com o intuito de facilitar o desenvolvimento de novas ferramentas destinadas à universidade.
|
|
14
|
+
|
|
15
|
+
> ⚠️ **Aviso:** O Jupiterweb é um site antigo, com uma estrutura HTML complexa e por vezes inconsistente, o que torna o processo de scraping muito desafiador. É esperado que a biblioteca contenha erros que passaram despercebidos. Se você encontrar algum problema ou comportamento inesperado, pedimos que abra uma [Issue](https://github.com/davigole/jupiterweb-scraper/issues) descrevendo o ocorrido.
|
|
16
|
+
|
|
17
|
+
## 🚀 Instalação
|
|
18
|
+
|
|
19
|
+
Para instalar a biblioteca, utilize o comando
|
|
20
|
+
```bash
|
|
21
|
+
pip install jupiterweb-scraper
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Ou, para instalar a partir do repositório:
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/davigole/jupiterweb-scraper.git
|
|
27
|
+
cd jupiterweb-scraper
|
|
28
|
+
pip install -e .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 📚 Como usar
|
|
32
|
+
|
|
33
|
+
*A fazer*
|
|
34
|
+
|
|
35
|
+
## 🤝 Como contribuir
|
|
36
|
+
|
|
37
|
+
Caso queira contribuir mas não saiba por onde começar, aqui estão algumas melhorias e funcionalidades que ainda não foram implementadas:
|
|
38
|
+
|
|
39
|
+
- Buscar disciplinas por parte do nome, horário, vagas remanescentes, etc. (o Jupiterweb já tem essas funcionalidades)
|
|
40
|
+
- Obter os cursos oferecidos por cada unidade e informações sobre cada curso (descrição, objetivos, grade curricular, etc.), disponíveis na seção "Cursos de ingresso" do Jupiterweb
|
|
41
|
+
- Obter informações sobre docentes (nome, instituto, departamento, disciplinas que ministra/ministrou, etc.)
|
|
42
|
+
- Obter informações do calendário escolar, disponível em PDF no Jupiterweb
|
|
43
|
+
- Testes automáticos para verificar o funcionamento do scraping
|
|
44
|
+
- Documentação mais completa e exemplos de uso
|
|
45
|
+
- Qualquer alteração nas funções de scraping que torne a biblioteca mais robusta
|
|
46
|
+
|
|
47
|
+
Contribuições são muito bem-vindas!
|
|
48
|
+
|
|
49
|
+
## 📄 Licença
|
|
50
|
+
|
|
51
|
+
MIT © [IME Jr](https://imejr.com/)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
|
|
3
|
+
from .disciplina import Disciplina, HorarioAula, Oferecimento, Requisito
|
|
4
|
+
from .instituto import Instituto
|
|
5
|
+
from .jupiterweb import obter_institutos
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = importlib.metadata.version("your_package")
|
|
9
|
+
except importlib.metadata.PackageNotFoundError:
|
|
10
|
+
__version__ = "0.0.0"
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Disciplina",
|
|
14
|
+
"HorarioAula",
|
|
15
|
+
"Oferecimento",
|
|
16
|
+
"Requisito",
|
|
17
|
+
"Instituto",
|
|
18
|
+
"obter_institutos",
|
|
19
|
+
]
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
{
|
|
2
|
+
"1": {
|
|
3
|
+
"nome": "Pró-Reitoria de Graduação - Cursos Interunidades",
|
|
4
|
+
"campus": "Butantã",
|
|
5
|
+
"abrev": "PRG"
|
|
6
|
+
},
|
|
7
|
+
"2": {
|
|
8
|
+
"nome": "Faculdade de Direito",
|
|
9
|
+
"campus": "Quadrilátero",
|
|
10
|
+
"abrev": "FD"
|
|
11
|
+
},
|
|
12
|
+
"3": {
|
|
13
|
+
"nome": "Escola Politécnica",
|
|
14
|
+
"campus": "Butantã",
|
|
15
|
+
"abrev": "POLI"
|
|
16
|
+
},
|
|
17
|
+
"4": {
|
|
18
|
+
"nome": "Instituto de Energia e Ambiente",
|
|
19
|
+
"campus": "Butantã",
|
|
20
|
+
"abrev": "IEE"
|
|
21
|
+
},
|
|
22
|
+
"5": {
|
|
23
|
+
"nome": "Faculdade de Medicina",
|
|
24
|
+
"campus": "Quadrilátero",
|
|
25
|
+
"abrev": "FM"
|
|
26
|
+
},
|
|
27
|
+
"6": {
|
|
28
|
+
"nome": "Faculdade de Saúde Pública",
|
|
29
|
+
"campus": "Quadrilátero",
|
|
30
|
+
"abrev": "FSP"
|
|
31
|
+
},
|
|
32
|
+
"7": {
|
|
33
|
+
"nome": "Escola de Enfermagem",
|
|
34
|
+
"campus": "Quadrilátero",
|
|
35
|
+
"abrev": "EE"
|
|
36
|
+
},
|
|
37
|
+
"8": {
|
|
38
|
+
"nome": "Faculdade de Filosofia, Letras e Ciências Humanas",
|
|
39
|
+
"campus": "Butantã",
|
|
40
|
+
"abrev": "FFLCH"
|
|
41
|
+
},
|
|
42
|
+
"9": {
|
|
43
|
+
"nome": "Faculdade de Ciências Farmacêuticas",
|
|
44
|
+
"campus": "Butantã",
|
|
45
|
+
"abrev": "FCF"
|
|
46
|
+
},
|
|
47
|
+
"10": {
|
|
48
|
+
"nome": "Faculdade de Medicina Veterinária e Zootecnia",
|
|
49
|
+
"campus": "Butantã",
|
|
50
|
+
"abrev": "FMVZ"
|
|
51
|
+
},
|
|
52
|
+
"11": {
|
|
53
|
+
"nome": "Escola Superior de Agricultura \"Luiz de Queiroz\"",
|
|
54
|
+
"campus": "Piracicaba",
|
|
55
|
+
"abrev": "ESALQ"
|
|
56
|
+
},
|
|
57
|
+
"12": {
|
|
58
|
+
"nome": "Faculdade de Economia, Administração, Contabilidade e Atuária",
|
|
59
|
+
"campus": "Butantã",
|
|
60
|
+
"abrev": "FEA"
|
|
61
|
+
},
|
|
62
|
+
"13": {
|
|
63
|
+
"nome": "Escola Politécnica e Faculdade de Medicina",
|
|
64
|
+
"campus": "Butantã",
|
|
65
|
+
"abrev": "POLI/FM"
|
|
66
|
+
},
|
|
67
|
+
"14": {
|
|
68
|
+
"nome": "Instituto de Astronomia, Geofísica e Ciências Atmosféricas",
|
|
69
|
+
"campus": "Butantã",
|
|
70
|
+
"abrev": "IAG"
|
|
71
|
+
},
|
|
72
|
+
"15": {
|
|
73
|
+
"nome": "Faculdade de Odontologia, Instituto de Ciências Biomédicas, Instituto de Química e Instituto de Biociências",
|
|
74
|
+
"campus": "Butantã",
|
|
75
|
+
"abrev": "FO/ICB/IQ/IB"
|
|
76
|
+
},
|
|
77
|
+
"16": {
|
|
78
|
+
"nome": "Faculdade de Arquitetura e Urbanismo e de Design",
|
|
79
|
+
"campus": "Butantã",
|
|
80
|
+
"abrev": "FAU"
|
|
81
|
+
},
|
|
82
|
+
"17": {
|
|
83
|
+
"nome": "Faculdade de Medicina de Ribeirão Preto",
|
|
84
|
+
"campus": "Ribeirão Preto",
|
|
85
|
+
"abrev": "FMRP"
|
|
86
|
+
},
|
|
87
|
+
"18": {
|
|
88
|
+
"nome": "Escola de Engenharia de São Carlos",
|
|
89
|
+
"campus": "São Carlos",
|
|
90
|
+
"abrev": "EESC"
|
|
91
|
+
},
|
|
92
|
+
"20": {
|
|
93
|
+
"nome": "Escola Politécnica, Instituto de Matemática, Estatística e Ciência da Computação, Instituto de Física",
|
|
94
|
+
"campus": "Butantã",
|
|
95
|
+
"abrev": "POLI/IME/IF"
|
|
96
|
+
},
|
|
97
|
+
"21": {
|
|
98
|
+
"nome": "Instituto Oceanográfico",
|
|
99
|
+
"campus": "Butantã",
|
|
100
|
+
"abrev": "IO"
|
|
101
|
+
},
|
|
102
|
+
"22": {
|
|
103
|
+
"nome": "Escola de Enfermagem de Ribeirão Preto",
|
|
104
|
+
"campus": "Ribeirão Preto",
|
|
105
|
+
"abrev": "EERP"
|
|
106
|
+
},
|
|
107
|
+
"23": {
|
|
108
|
+
"nome": "Faculdade de Odontologia",
|
|
109
|
+
"campus": "Butantã",
|
|
110
|
+
"abrev": "FO"
|
|
111
|
+
},
|
|
112
|
+
"24": {
|
|
113
|
+
"nome": "Faculdade de Medicina de Bauru",
|
|
114
|
+
"campus": "Bauru",
|
|
115
|
+
"abrev": "FMBRU"
|
|
116
|
+
},
|
|
117
|
+
"25": {
|
|
118
|
+
"nome": "Faculdade de Odontologia de Bauru",
|
|
119
|
+
"campus": "Bauru",
|
|
120
|
+
"abrev": "FOB"
|
|
121
|
+
},
|
|
122
|
+
"26": {
|
|
123
|
+
"nome": "Instituto de Astronomia, Geofísica e Ciências Atmosféricas e Instituto de Geociências",
|
|
124
|
+
"campus": "Butantã",
|
|
125
|
+
"abrev": "IAG/IGc"
|
|
126
|
+
},
|
|
127
|
+
"27": {
|
|
128
|
+
"nome": "Escola de Comunicações e Artes",
|
|
129
|
+
"campus": "Butantã",
|
|
130
|
+
"abrev": "ECA"
|
|
131
|
+
},
|
|
132
|
+
"30": {
|
|
133
|
+
"nome": "Centro de Biologia Marinha",
|
|
134
|
+
"campus": "São Sebastião",
|
|
135
|
+
"abrev": "CEBIMar"
|
|
136
|
+
},
|
|
137
|
+
"31": {
|
|
138
|
+
"nome": "Instituto de Estudos Brasileiros",
|
|
139
|
+
"campus": "Butantã",
|
|
140
|
+
"abrev": "IEB"
|
|
141
|
+
},
|
|
142
|
+
"32": {
|
|
143
|
+
"nome": "Museu de Arte Contemporânea",
|
|
144
|
+
"campus": "Butantã",
|
|
145
|
+
"abrev": "MAC"
|
|
146
|
+
},
|
|
147
|
+
"33": {
|
|
148
|
+
"nome": "Museu Paulista",
|
|
149
|
+
"campus": "Ipiranga",
|
|
150
|
+
"abrev": "MP"
|
|
151
|
+
},
|
|
152
|
+
"37": {
|
|
153
|
+
"nome": "Instituto de Estudos Avançados",
|
|
154
|
+
"campus": "Butantã",
|
|
155
|
+
"abrev": "IEA"
|
|
156
|
+
},
|
|
157
|
+
"38": {
|
|
158
|
+
"nome": "Museu de Zoologia",
|
|
159
|
+
"campus": "Ipiranga",
|
|
160
|
+
"abrev": "MZUSP"
|
|
161
|
+
},
|
|
162
|
+
"39": {
|
|
163
|
+
"nome": "Escola de Educação Física e Esporte",
|
|
164
|
+
"campus": "Butantã",
|
|
165
|
+
"abrev": "EEFE"
|
|
166
|
+
},
|
|
167
|
+
"41": {
|
|
168
|
+
"nome": "Instituto de Biociências",
|
|
169
|
+
"campus": "Butantã",
|
|
170
|
+
"abrev": "IB"
|
|
171
|
+
},
|
|
172
|
+
"42": {
|
|
173
|
+
"nome": "Instituto de Ciências Biomédicas",
|
|
174
|
+
"campus": "Butantã",
|
|
175
|
+
"abrev": "ICB"
|
|
176
|
+
},
|
|
177
|
+
"43": {
|
|
178
|
+
"nome": "Instituto de Física",
|
|
179
|
+
"campus": "Butantã",
|
|
180
|
+
"abrev": "IF"
|
|
181
|
+
},
|
|
182
|
+
"44": {
|
|
183
|
+
"nome": "Instituto de Geociências",
|
|
184
|
+
"campus": "Butantã",
|
|
185
|
+
"abrev": "IGc"
|
|
186
|
+
},
|
|
187
|
+
"45": {
|
|
188
|
+
"nome": "Instituto de Matemática, Estatística e Ciência da Computação",
|
|
189
|
+
"campus": "Butantã",
|
|
190
|
+
"abrev": "IME"
|
|
191
|
+
},
|
|
192
|
+
"46": {
|
|
193
|
+
"nome": "Instituto de Química",
|
|
194
|
+
"campus": "Butantã",
|
|
195
|
+
"abrev": "IQ"
|
|
196
|
+
},
|
|
197
|
+
"47": {
|
|
198
|
+
"nome": "Instituto de Psicologia",
|
|
199
|
+
"campus": "Butantã",
|
|
200
|
+
"abrev": "IP"
|
|
201
|
+
},
|
|
202
|
+
"48": {
|
|
203
|
+
"nome": "Faculdade de Educação",
|
|
204
|
+
"campus": "Butantã",
|
|
205
|
+
"abrev": "FE"
|
|
206
|
+
},
|
|
207
|
+
"55": {
|
|
208
|
+
"nome": "Instituto de Ciências Matemáticas e de Computação",
|
|
209
|
+
"campus": "São Carlos",
|
|
210
|
+
"abrev": "ICMC"
|
|
211
|
+
},
|
|
212
|
+
"58": {
|
|
213
|
+
"nome": "Faculdade de Odontologia de Ribeirão Preto",
|
|
214
|
+
"campus": "Ribeirão Preto",
|
|
215
|
+
"abrev": "FORP"
|
|
216
|
+
},
|
|
217
|
+
"59": {
|
|
218
|
+
"nome": "Faculdade de Filosofia, Ciências e Letras de Ribeirão Preto",
|
|
219
|
+
"campus": "Ribeirão Preto",
|
|
220
|
+
"abrev": "FFCLRP"
|
|
221
|
+
},
|
|
222
|
+
"60": {
|
|
223
|
+
"nome": "Faculdade de Ciências Farmacêuticas de Ribeirão Preto",
|
|
224
|
+
"campus": "Ribeirão Preto",
|
|
225
|
+
"abrev": "FCFRP"
|
|
226
|
+
},
|
|
227
|
+
"61": {
|
|
228
|
+
"nome": "Hospital de Reabilitação de Anomalias Craniofaciais",
|
|
229
|
+
"campus": "Bauru",
|
|
230
|
+
"abrev": "HRAC"
|
|
231
|
+
},
|
|
232
|
+
"64": {
|
|
233
|
+
"nome": "Centro de Energia Nuclear na Agricultura",
|
|
234
|
+
"campus": "Piracicaba",
|
|
235
|
+
"abrev": "CENA"
|
|
236
|
+
},
|
|
237
|
+
"65": {
|
|
238
|
+
"nome": "Pró-Reitoria de Graduação - Licenciatura em Ciências - Semipresencial",
|
|
239
|
+
"campus": "Butantã",
|
|
240
|
+
"abrev": "PRG-LC"
|
|
241
|
+
},
|
|
242
|
+
"66": {
|
|
243
|
+
"nome": "Física Médica - Instituto de Física e Faculdade de Medicina",
|
|
244
|
+
"campus": "Butantã",
|
|
245
|
+
"abrev": "IF/FM"
|
|
246
|
+
},
|
|
247
|
+
"67": {
|
|
248
|
+
"nome": "Faculdade de Medicina, Instituto de Ciências Biomédicas, Instituto de Química e Instituto de Biociências",
|
|
249
|
+
"campus": "Quadrilátero",
|
|
250
|
+
"abrev": "FM/ICB/IQ/IB"
|
|
251
|
+
},
|
|
252
|
+
"68": {
|
|
253
|
+
"nome": "Faculdade de Odontologia e Instituto de Ciências Biomédicas",
|
|
254
|
+
"campus": "Butantã",
|
|
255
|
+
"abrev": "FO/ICB"
|
|
256
|
+
},
|
|
257
|
+
"69": {
|
|
258
|
+
"nome": "Instituto de Biociências e Faculdade de Arquitetura e Urbanismo",
|
|
259
|
+
"campus": "Butantã",
|
|
260
|
+
"abrev": "IB/FAU"
|
|
261
|
+
},
|
|
262
|
+
"71": {
|
|
263
|
+
"nome": "Museu de Arqueologia e Etnologia",
|
|
264
|
+
"campus": "Butantã",
|
|
265
|
+
"abrev": "MAE"
|
|
266
|
+
},
|
|
267
|
+
"74": {
|
|
268
|
+
"nome": "Faculdade de Zootecnia e Engenharia de Alimentos",
|
|
269
|
+
"campus": "Pirassununga",
|
|
270
|
+
"abrev": "FZEA"
|
|
271
|
+
},
|
|
272
|
+
"75": {
|
|
273
|
+
"nome": "Instituto de Química de São Carlos",
|
|
274
|
+
"campus": "São Carlos",
|
|
275
|
+
"abrev": "IQSC"
|
|
276
|
+
},
|
|
277
|
+
"76": {
|
|
278
|
+
"nome": "Instituto de Física de São Carlos",
|
|
279
|
+
"campus": "São Carlos",
|
|
280
|
+
"abrev": "IFSC"
|
|
281
|
+
},
|
|
282
|
+
"81": {
|
|
283
|
+
"nome": "Faculdade de Economia, Administração e Contabilidade de Ribeirão Preto",
|
|
284
|
+
"campus": "Ribeirão Preto",
|
|
285
|
+
"abrev": "FEARP"
|
|
286
|
+
},
|
|
287
|
+
"83": {
|
|
288
|
+
"nome": "Instituto de Medicina Tropical de São Paulo",
|
|
289
|
+
"campus": "Quadrilátero",
|
|
290
|
+
"abrev": "IMT"
|
|
291
|
+
},
|
|
292
|
+
"85": {
|
|
293
|
+
"nome": "Instituto de Pesquisas Energéticas e Nucleares",
|
|
294
|
+
"campus": "Butantã",
|
|
295
|
+
"abrev": "IPEN"
|
|
296
|
+
},
|
|
297
|
+
"86": {
|
|
298
|
+
"nome": "Escola de Artes, Ciências e Humanidades",
|
|
299
|
+
"campus": "Leste",
|
|
300
|
+
"abrev": "EACH"
|
|
301
|
+
},
|
|
302
|
+
"87": {
|
|
303
|
+
"nome": "Instituto de Relações Internacionais",
|
|
304
|
+
"campus": "Butantã",
|
|
305
|
+
"abrev": "IRI"
|
|
306
|
+
},
|
|
307
|
+
"88": {
|
|
308
|
+
"nome": "Escola de Engenharia de Lorena",
|
|
309
|
+
"campus": "Lorena",
|
|
310
|
+
"abrev": "EEL"
|
|
311
|
+
},
|
|
312
|
+
"89": {
|
|
313
|
+
"nome": "Faculdade de Direito de Ribeirão Preto",
|
|
314
|
+
"campus": "Ribeirão Preto",
|
|
315
|
+
"abrev": "FDRP"
|
|
316
|
+
},
|
|
317
|
+
"90": {
|
|
318
|
+
"nome": "Licenciatura em Ciências Exatas - São Carlos",
|
|
319
|
+
"campus": "São Carlos",
|
|
320
|
+
"abrev": "LCE"
|
|
321
|
+
},
|
|
322
|
+
"91": {
|
|
323
|
+
"nome": "Faculdade de Filosofia, Ciências e Letras de Ribeirão Preto e Faculdade de Medicina de Ribeirão Preto",
|
|
324
|
+
"campus": "Ribeirão Preto",
|
|
325
|
+
"abrev": "FFCLRP/FMRP"
|
|
326
|
+
},
|
|
327
|
+
"92": {
|
|
328
|
+
"nome": "Instituto de Biociências e Centro de Biologia Marinha",
|
|
329
|
+
"campus": "Butantã",
|
|
330
|
+
"abrev": "IB/CEBIMar"
|
|
331
|
+
},
|
|
332
|
+
"93": {
|
|
333
|
+
"nome": "Instituto de Astronomia, Geofísica e Ciências Atmosféricas e Instituto Oceanográfico",
|
|
334
|
+
"campus": "Butantã",
|
|
335
|
+
"abrev": "IAG/IO"
|
|
336
|
+
},
|
|
337
|
+
"94": {
|
|
338
|
+
"nome": "Escola de Enfermagem de Ribeirão Preto e Faculdade de Economia, Administração e Contabilidade de Ribeirão Preto",
|
|
339
|
+
"campus": "Ribeirão Preto",
|
|
340
|
+
"abrev": "EERP/FEARP"
|
|
341
|
+
},
|
|
342
|
+
"95": {
|
|
343
|
+
"nome": "Faculdade de Odontologia de Ribeirão Preto e Faculdade de Ciências Farmacêuticas de Ribeirão Preto",
|
|
344
|
+
"campus": "Ribeirão Preto",
|
|
345
|
+
"abrev": "FORP/FCFRP"
|
|
346
|
+
},
|
|
347
|
+
"96": {
|
|
348
|
+
"nome": "Faculdade de Filosofia, Ciências e Letras de Ribeirão Preto e Faculdade de Economia, Administração e Contabilidade de Ribeirão Preto",
|
|
349
|
+
"campus": "Ribeirão Preto",
|
|
350
|
+
"abrev": "FFCLRP/FEARP"
|
|
351
|
+
},
|
|
352
|
+
"97": {
|
|
353
|
+
"nome": "Escola de Engenharia de São Carlos e Instituto de Ciências Matemáticas e de Computação",
|
|
354
|
+
"campus": "São Carlos",
|
|
355
|
+
"abrev": "EESC/ICMC"
|
|
356
|
+
},
|
|
357
|
+
"98": {
|
|
358
|
+
"nome": "Escola de Educação Física e Esporte de Ribeirão Preto",
|
|
359
|
+
"campus": "Ribeirão Preto",
|
|
360
|
+
"abrev": "EEFERP"
|
|
361
|
+
},
|
|
362
|
+
"99": {
|
|
363
|
+
"nome": "Instituto de Arquitetura e Urbanismo de São Carlos",
|
|
364
|
+
"campus": "São Carlos",
|
|
365
|
+
"abrev": "IAU"
|
|
366
|
+
},
|
|
367
|
+
"100": {
|
|
368
|
+
"nome": "Faculdade de Ciências Farmacêuticas e Instituto de Ciências Biomédicas",
|
|
369
|
+
"campus": "Butantã",
|
|
370
|
+
"abrev": "FCF/ICB"
|
|
371
|
+
},
|
|
372
|
+
"101": {
|
|
373
|
+
"nome": "Instituto de Química e Faculdade de Ciências Farmacêuticas",
|
|
374
|
+
"campus": "Butantã",
|
|
375
|
+
"abrev": "IQ/FCF"
|
|
376
|
+
},
|
|
377
|
+
"102": {
|
|
378
|
+
"nome": "Faculdade de Saúde Pública e Faculdade de Ciências Farmacêuticas",
|
|
379
|
+
"campus": "Quadrilátero",
|
|
380
|
+
"abrev": "FSP/FCF"
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import unicodedata
|
|
3
|
+
from typing import Any
|
|
4
|
+
from warnings import warn
|
|
5
|
+
|
|
6
|
+
from .urls import URLS
|
|
7
|
+
from .utils import obter_soup, truncate_string
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Disciplina:
|
|
11
|
+
"""
|
|
12
|
+
Disciplina cadastrada no Jupiterweb.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, sigla: str) -> None:
|
|
16
|
+
self.sigla = str(sigla).upper()
|
|
17
|
+
self._dados: dict[str, Any] = {}
|
|
18
|
+
self._carregado = False
|
|
19
|
+
|
|
20
|
+
def __repr__(self) -> str:
|
|
21
|
+
return f"Disciplina(sigla='{self.sigla}')"
|
|
22
|
+
|
|
23
|
+
def __str__(self) -> str:
|
|
24
|
+
return self.sigla
|
|
25
|
+
|
|
26
|
+
def __getitem__(self, key: str) -> Any:
|
|
27
|
+
return self.obter_dados()[key]
|
|
28
|
+
|
|
29
|
+
def obter_dados(self) -> dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Retorna dados da disciplina no Jupiterweb. Se disciplina ainda nao foi carregada,
|
|
32
|
+
faz o scraping antes de retornar.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
if not self._carregado:
|
|
36
|
+
self._carregar()
|
|
37
|
+
return self._dados
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def url_principal(self) -> str:
|
|
41
|
+
"""
|
|
42
|
+
URL da pagina principal da disciplina no Jupiterweb.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
return URLS["disciplina"].format(sigla=self.sigla)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def url_oferecimento(self) -> str:
|
|
49
|
+
"""
|
|
50
|
+
URL da pagina de oferecimentos da disciplina no Jupiterweb.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
return URLS["oferecimento"].format(sigla=self.sigla)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def url_requisitos(self) -> str:
|
|
57
|
+
"""
|
|
58
|
+
URL da pagina de requisitos da disciplina no Jupiterweb.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
return URLS["requisitos"].format(sigla=self.sigla)
|
|
62
|
+
|
|
63
|
+
def _normalizar_titulo(self, title: str) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Converte titulo ao formato padrao para chaves de dicionario.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
title = title.strip().rstrip(":")
|
|
69
|
+
title = unicodedata.normalize("NFKD", title)
|
|
70
|
+
title = title.encode("ascii", "ignore").decode()
|
|
71
|
+
title = title.lower()
|
|
72
|
+
title = re.sub(r" +", " ", title)
|
|
73
|
+
return title
|
|
74
|
+
|
|
75
|
+
def _carregar_principal(self) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Faz scraping da pagina principal da disciplina e armazena os dados obtidos.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
soup = obter_soup(self.url_principal)
|
|
81
|
+
|
|
82
|
+
table = soup.select_one("form[name='form1'] > table")
|
|
83
|
+
if not table:
|
|
84
|
+
warn(f"Nao foi possivel carregar pagina principal da disciplina {self.sigla}")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# ----- Texto centralizado -----
|
|
88
|
+
centered_text = [i.get_text(strip=True) for i in table.select("td[align='CENTER']")]
|
|
89
|
+
centered_text = [(centered_text[i] if len(centered_text) > i else "") for i in range(4)]
|
|
90
|
+
|
|
91
|
+
self._dados["instituto"] = centered_text[0]
|
|
92
|
+
self._dados["departamento"] = centered_text[1]
|
|
93
|
+
self._dados["nome"] = centered_text[2].removeprefix("Disciplina:").split("-", 1)[1].strip()
|
|
94
|
+
self._dados["nome ingles"] = centered_text[3]
|
|
95
|
+
|
|
96
|
+
# ----- Texto livre -----
|
|
97
|
+
span_text = table.select("span.txt_arial_8pt_gray, span.txt_arial_8pt_black")
|
|
98
|
+
|
|
99
|
+
title = "" # guardar texto em self._dados[title] (se subtitle == "")
|
|
100
|
+
subtitle = "" # guardar texto em self._dados[title][subtitle] (se subtitle != "")
|
|
101
|
+
subtitle_tab = None # tabela em que subtitle foi encontrado
|
|
102
|
+
added_text = False # se texto ja foi adicionado ao titulo atual (nao pode ter subtitulos)
|
|
103
|
+
|
|
104
|
+
for span in span_text:
|
|
105
|
+
text = span.get_text(strip=True, separator="\n")
|
|
106
|
+
|
|
107
|
+
if span.has_attr("class") and "txt_arial_8pt_black" in span["class"]: # titulo ou subtitulo
|
|
108
|
+
text = self._normalizar_titulo(text)
|
|
109
|
+
tab = span.find_parent("table")
|
|
110
|
+
|
|
111
|
+
if title and ((not subtitle and not added_text) or (subtitle and tab == subtitle_tab)): # subtitulo
|
|
112
|
+
subtitle = text
|
|
113
|
+
subtitle_tab = tab
|
|
114
|
+
|
|
115
|
+
if (title not in self._dados) or (not isinstance(self._dados[title], dict)):
|
|
116
|
+
self._dados[title] = {}
|
|
117
|
+
self._dados[title][subtitle] = ""
|
|
118
|
+
else: # titulo
|
|
119
|
+
title = text
|
|
120
|
+
subtitle = ""
|
|
121
|
+
self._dados[title] = ""
|
|
122
|
+
added_text = False
|
|
123
|
+
else: # texto
|
|
124
|
+
if subtitle:
|
|
125
|
+
self._dados[title][subtitle] += "\n" + text if self._dados[title][subtitle] and text else text
|
|
126
|
+
else:
|
|
127
|
+
self._dados[title] += "\n" + text if self._dados[title] and text else text
|
|
128
|
+
added_text = True # mesmo se text = "" o titulo nao pode ter subtitulos
|
|
129
|
+
|
|
130
|
+
def _carregar_requisitos(self) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Faz scraping da pagina de requisitos da disciplina e armazena os dados obtidos.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
# dados["requisitos"][curso] = [["x"], ["y", "z"]] significa que, para fazer a
|
|
136
|
+
# disciplina, alunos de 'curso' precisam ter feito a disciplina "x", ou ter
|
|
137
|
+
# feito ambas as disciplinas "y" e "z".
|
|
138
|
+
|
|
139
|
+
self._dados["requisitos"] = {}
|
|
140
|
+
self._dados["periodo ideal"] = {}
|
|
141
|
+
|
|
142
|
+
soup = obter_soup(self.url_requisitos)
|
|
143
|
+
table = soup.select_one("form[name='form1'] > table")
|
|
144
|
+
|
|
145
|
+
if not table:
|
|
146
|
+
return # sem requisitos
|
|
147
|
+
|
|
148
|
+
rows = table.select("tr.txt_verdana_8pt_gray")
|
|
149
|
+
curso = ""
|
|
150
|
+
index = 0 # adicionar em self._dados["requisitos"][curso][index]
|
|
151
|
+
|
|
152
|
+
for row in rows:
|
|
153
|
+
td = row.find_all("td")
|
|
154
|
+
if not td:
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
txt = " ".join(td[0].text.strip().split())
|
|
158
|
+
if not txt:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
if txt.startswith("Curso"):
|
|
162
|
+
sep = txt.removeprefix("Curso:").split(" - Período ideal:", 1)
|
|
163
|
+
|
|
164
|
+
curso = sep[0].strip()
|
|
165
|
+
index = 0
|
|
166
|
+
self._dados["requisitos"][curso] = [[]]
|
|
167
|
+
|
|
168
|
+
if len(sep) > 1:
|
|
169
|
+
self._dados["periodo ideal"][curso] = int(sep[1])
|
|
170
|
+
elif curso and txt.lower() == "ou":
|
|
171
|
+
index += 1
|
|
172
|
+
self._dados["requisitos"][curso].append([])
|
|
173
|
+
elif curso:
|
|
174
|
+
sigla = txt.split("-", 1)[0].strip().upper()
|
|
175
|
+
tipo = td[1].get_text(strip=True)
|
|
176
|
+
if not tipo:
|
|
177
|
+
tipo = "requisito"
|
|
178
|
+
|
|
179
|
+
req = Requisito(sigla, tipo)
|
|
180
|
+
self._dados["requisitos"][curso][index].append(req)
|
|
181
|
+
|
|
182
|
+
def _carregar_oferecimento(self) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Faz scraping da pagina de oferecimento da disciplina e armazena os dados obtidos.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
self._dados["oferecimento"] = []
|
|
188
|
+
|
|
189
|
+
soup = obter_soup(self.url_oferecimento)
|
|
190
|
+
table = soup.select_one("div#layout_principal > table:nth-of-type(4)")
|
|
191
|
+
|
|
192
|
+
if not table:
|
|
193
|
+
return # sem oferecimentos
|
|
194
|
+
|
|
195
|
+
boxes = table.select_one("td").find_all("div", recursive=False)
|
|
196
|
+
|
|
197
|
+
for box in boxes:
|
|
198
|
+
box_tables = box.find_all("table", recursive=False)
|
|
199
|
+
|
|
200
|
+
# ----- Informacao basica -----
|
|
201
|
+
info_text = [i.get_text(strip=True) for i in box_tables[0].select("span.txt_arial_8pt_gray")]
|
|
202
|
+
info_text = [(info_text[i] if len(info_text) > i else "") for i in range(5)]
|
|
203
|
+
|
|
204
|
+
oferecimento = Oferecimento(
|
|
205
|
+
codigo=info_text[0],
|
|
206
|
+
data_inicio=info_text[1],
|
|
207
|
+
data_fim=info_text[2],
|
|
208
|
+
tipo_turma=info_text[3],
|
|
209
|
+
observacoes=info_text[4],
|
|
210
|
+
sigla_disciplina=self.sigla,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# ----- Horarios -----
|
|
214
|
+
horarios_rows = box_tables[1].find_all("tr", recursive=False)[1:]
|
|
215
|
+
|
|
216
|
+
for row in horarios_rows:
|
|
217
|
+
row_text = [i.get_text(strip=True) for i in row.find_all("td", recursive=False)]
|
|
218
|
+
row_text = [(row_text[i] if len(row_text) > i else "") for i in range(4)]
|
|
219
|
+
|
|
220
|
+
oferecimento.adicionar_horario(row_text[0], row_text[1], row_text[2], row_text[3])
|
|
221
|
+
|
|
222
|
+
# ----- Vagas -----
|
|
223
|
+
vagas_rows = box_tables[2].find_all("tr", recursive=False)
|
|
224
|
+
vagas_labels = [i.get_text(strip=True).lower() for i in vagas_rows[0].find_all("td", recursive=False)][1:]
|
|
225
|
+
vagas_labels = [self._normalizar_titulo(i) for i in vagas_labels]
|
|
226
|
+
|
|
227
|
+
tipo_vaga = ""
|
|
228
|
+
|
|
229
|
+
for row in vagas_rows[1:]:
|
|
230
|
+
row_text = [i.get_text(strip=True) for i in row.find_all("td", recursive=False)]
|
|
231
|
+
|
|
232
|
+
istitle = row_text[0] != ""
|
|
233
|
+
if not istitle:
|
|
234
|
+
row_text = row_text[1:]
|
|
235
|
+
|
|
236
|
+
row_name = row_text[0]
|
|
237
|
+
row_vals = [(int(i) if i.isnumeric() else "-") for i in row_text[1:]]
|
|
238
|
+
row_vals = [(row_vals[i] if len(row_vals) > i else "-") for i in range(len(vagas_labels))]
|
|
239
|
+
row_items = {vagas_labels[i]: row_vals[i] for i in range(len(vagas_labels))}
|
|
240
|
+
|
|
241
|
+
if istitle: # novo tipo de vaga
|
|
242
|
+
tipo_vaga = self._normalizar_titulo(row_name)
|
|
243
|
+
oferecimento.vagas[tipo_vaga] = row_items
|
|
244
|
+
oferecimento.vagas[tipo_vaga]["cursos"] = {}
|
|
245
|
+
else:
|
|
246
|
+
oferecimento.vagas[tipo_vaga]["cursos"][row_name] = row_items
|
|
247
|
+
|
|
248
|
+
self._dados["oferecimento"].append(oferecimento)
|
|
249
|
+
|
|
250
|
+
def _carregar(self) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Faz scraping da disciplina e armazena os seus dados.
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
self._dados = {
|
|
256
|
+
"sigla": self.sigla,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
self._carregar_principal()
|
|
260
|
+
self._carregar_requisitos()
|
|
261
|
+
self._carregar_oferecimento()
|
|
262
|
+
self._carregado = True
|
|
263
|
+
|
|
264
|
+
def possui_oferecimento(self) -> bool:
|
|
265
|
+
"""
|
|
266
|
+
Verifica se disciplina tem algum oferecimento no semestre atual.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
return bool(self.obter_dados().get("oferecimento"))
|
|
270
|
+
|
|
271
|
+
def mostrar(self, trunc_str: bool = True) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Mostra dados da disciplina de forma legivel. Utilizada principalmente
|
|
274
|
+
para debug. Se trunc_str = True, strings longas serao truncadas.
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
LARGURA = 120
|
|
278
|
+
|
|
279
|
+
for key, val in self.obter_dados().items():
|
|
280
|
+
print(f"\n{key}{'─'*max(0, LARGURA-len(key))}")
|
|
281
|
+
|
|
282
|
+
if not val:
|
|
283
|
+
print(" (vazio)")
|
|
284
|
+
elif isinstance(val, dict):
|
|
285
|
+
for subkey, subval in val.items():
|
|
286
|
+
print(f" {subkey}{'─'*max(0, LARGURA-len(subkey)-1)}")
|
|
287
|
+
|
|
288
|
+
if isinstance(subval, str) and trunc_str:
|
|
289
|
+
subval = truncate_string(subval, LARGURA - 4)
|
|
290
|
+
print(f" {subval}")
|
|
291
|
+
else:
|
|
292
|
+
if isinstance(val, str) and trunc_str:
|
|
293
|
+
val = truncate_string(val, LARGURA - 2)
|
|
294
|
+
print(f" {val}")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class Requisito:
|
|
298
|
+
"""
|
|
299
|
+
Requisito de disciplina no Jupiterweb.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
def __init__(self, sigla: str, tipo: str = "requisito") -> None:
|
|
303
|
+
self.sigla = str(sigla)
|
|
304
|
+
self.tipo = str(tipo).lower() # requisito fraco, indicacao de conjunto, etc.
|
|
305
|
+
|
|
306
|
+
def __repr__(self) -> str:
|
|
307
|
+
return f"Requisito(sigla='{self.sigla}',tipo='{self.tipo}')"
|
|
308
|
+
|
|
309
|
+
def __str__(self) -> str:
|
|
310
|
+
return self.sigla
|
|
311
|
+
|
|
312
|
+
def obter_disciplina(self) -> Disciplina:
|
|
313
|
+
"""
|
|
314
|
+
Retorna objeto Disciplina correspondente ao requisito.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
return Disciplina(self.sigla)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class Oferecimento:
|
|
321
|
+
"""
|
|
322
|
+
Oferecimento de turma no Jupiterweb.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
def __init__(
|
|
326
|
+
self,
|
|
327
|
+
codigo: str,
|
|
328
|
+
data_inicio: str,
|
|
329
|
+
data_fim: str,
|
|
330
|
+
tipo_turma: str,
|
|
331
|
+
observacoes: str = "",
|
|
332
|
+
sigla_disciplina: str = "",
|
|
333
|
+
) -> None:
|
|
334
|
+
self.codigo = str(codigo).upper()
|
|
335
|
+
self.data_inicio = data_inicio
|
|
336
|
+
self.data_fim = data_fim
|
|
337
|
+
self.tipo_turma = str(tipo_turma).lower()
|
|
338
|
+
self.observacoes = observacoes
|
|
339
|
+
self.sigla_disciplina = str(sigla_disciplina).upper()
|
|
340
|
+
self.horarios: list[HorarioAula] = []
|
|
341
|
+
self.vagas = {}
|
|
342
|
+
|
|
343
|
+
def __repr__(self) -> str:
|
|
344
|
+
return f"Oferecimento(codigo='{self.codigo}',data_inicio='{self.data_inicio}',data_fim='{self.data_fim}',tipo_turma='{self.tipo_turma}',observacoes='{self.observacoes}',sigla_disciplina='{self.sigla_disciplina}')"
|
|
345
|
+
|
|
346
|
+
def __str__(self) -> str:
|
|
347
|
+
return f"Turma {self.codigo}"
|
|
348
|
+
|
|
349
|
+
def adicionar_horario(self, dia_semana: str, hora_inicio: str, hora_fim: str, professor: str) -> None:
|
|
350
|
+
"""
|
|
351
|
+
Adiciona horario de aula ao oferecimento.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
horario = HorarioAula(dia_semana, hora_inicio, hora_fim, professor)
|
|
355
|
+
self.horarios.append(horario)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class HorarioAula:
|
|
359
|
+
"""
|
|
360
|
+
Horario de aula no Jupiterweb.
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
def __init__(self, dia_semana: str, hora_inicio: str, hora_fim: str, professor: str) -> None:
|
|
364
|
+
self.dia_semana = str(dia_semana).lower()
|
|
365
|
+
self.hora_inicio = hora_inicio
|
|
366
|
+
self.hora_fim = hora_fim
|
|
367
|
+
self.professor = professor
|
|
368
|
+
|
|
369
|
+
def __repr__(self) -> str:
|
|
370
|
+
return f"HorarioAula(dia_semana='{self.dia_semana}',hora_inicio='{self.hora_inicio}',hora_fim='{self.hora_fim}',professor='{self.professor}')"
|
|
371
|
+
|
|
372
|
+
def __str__(self) -> str:
|
|
373
|
+
return f"{self.dia_semana} ({self.hora_inicio} - {self.hora_fim}) Prof(a). {self.professor}"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from .disciplina import Disciplina
|
|
2
|
+
from .urls import URLS
|
|
3
|
+
from .utils import obter_soup
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Instituto:
|
|
7
|
+
"""
|
|
8
|
+
Unidade de ensino cadastrada no Jupiterweb.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, codigo: str, nome: str, campus: str, abrev: str) -> None:
|
|
12
|
+
self.codigo = str(codigo)
|
|
13
|
+
self.nome = nome
|
|
14
|
+
self.campus = campus
|
|
15
|
+
self.abrev = abrev
|
|
16
|
+
self.disciplinas = []
|
|
17
|
+
self._carregado = False
|
|
18
|
+
|
|
19
|
+
def __repr__(self) -> str:
|
|
20
|
+
return f"Instituto(codigo='{self.codigo}',nome='{self.nome}',campus='{self.campus}',abrev='{self.abrev}')"
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
return self.nome
|
|
24
|
+
|
|
25
|
+
def _carregar(self) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Faz scraping da pagina com as disciplinas do instituto e armazena
|
|
28
|
+
os objetos do tipo Disciplina correspondentes (delega o scraping das
|
|
29
|
+
disciplinas, que é feito sob demanda).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
if self._carregado:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
soup = obter_soup(self.url_listagem)
|
|
36
|
+
disciplina_rows = soup.select("tr[bgcolor='#658CCF'] ~tr")
|
|
37
|
+
|
|
38
|
+
for row in disciplina_rows:
|
|
39
|
+
tds = row.find_all("td")
|
|
40
|
+
sigla = tds[0].find("span").get_text(strip=True)
|
|
41
|
+
self.disciplinas.append(Disciplina(sigla))
|
|
42
|
+
|
|
43
|
+
self._carregado = True
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def url_listagem(self) -> str:
|
|
47
|
+
"""
|
|
48
|
+
URL do Jupiterweb com todas as disciplinas oferecidas pela unidade de ensino.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
return URLS["listagem"].format(codigo=self.codigo)
|
|
52
|
+
|
|
53
|
+
def obter_disciplinas(self) -> list[Disciplina]:
|
|
54
|
+
"""
|
|
55
|
+
Retorna lista de disciplinas oferecidas no instituto.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
if not self._carregado:
|
|
59
|
+
self._carregar()
|
|
60
|
+
|
|
61
|
+
return self.disciplinas
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from .instituto import Instituto
|
|
4
|
+
from .paths import PATHS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def obter_institutos() -> list[Instituto]:
|
|
8
|
+
"""
|
|
9
|
+
Retorna lista com todas as unidades de ensino cadastradas no Jupiterweb (delega
|
|
10
|
+
o scraping da pagina da unidade e de suas disciplinas, que é feito sob demanda).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
with open(PATHS["institutos"], "r", encoding="utf-8") as f:
|
|
14
|
+
data = json.load(f)
|
|
15
|
+
return [Instituto(codigo, data[codigo]["nome"], data[codigo]["campus"], data[codigo]["abrev"]) for codigo in data]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from urllib.parse import urljoin
|
|
2
|
+
|
|
3
|
+
URL_BASE = "https://uspdigital.usp.br/jupiterweb/"
|
|
4
|
+
URLS: dict[str, str] = {
|
|
5
|
+
"listagem": urljoin(URL_BASE, "jupDisciplinaLista?codcg={codigo}&letra=0-Z&tipo=D"),
|
|
6
|
+
"disciplina": urljoin(URL_BASE, "obterDisciplina?sgldis={sigla}"),
|
|
7
|
+
"oferecimento": urljoin(URL_BASE, "obterTurma?sgldis={sigla}"),
|
|
8
|
+
"requisitos": urljoin(URL_BASE, "listarCursosRequisitos?coddis={sigla}"),
|
|
9
|
+
"institutos": urljoin(URL_BASE, "jupColegiadoLista?tipo=D"),
|
|
10
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from bs4 import BeautifulSoup
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def obter_soup(url: str) -> BeautifulSoup:
|
|
6
|
+
"""
|
|
7
|
+
Faz request para URL e retorna objeto BeautifulSoup com o texto da resposta.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
response = requests.get(url, timeout=10)
|
|
11
|
+
response.raise_for_status()
|
|
12
|
+
response.encoding = "iso-8859-1"
|
|
13
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
|
14
|
+
|
|
15
|
+
return soup
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def truncate_string(s: str, max_length: int) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Trunca string para um comprimento máximo, adicionando "..." no final se necessário.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
if len(s) <= max_length:
|
|
24
|
+
return s
|
|
25
|
+
return s[: max(max_length - 3, 0)] + "..."
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jupiterweb-scraper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Extração de informações sobre disciplinas da Universidade de São Paulo a partir do Jupiterweb
|
|
5
|
+
Author: Davi Golebiovski, Isaque Nascimento, Lucas Kevin Silva Muniz
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 IME Jr
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Repository, https://github.com/davigole/jupiterweb-scraper
|
|
29
|
+
Project-URL: Issues, https://github.com/davigole/jupiterweb-scraper/issues
|
|
30
|
+
Keywords: usp,jupiterweb,scraper,universidade,web,university
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Requires-Python: >=3.9
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
License-File: LICENSE
|
|
42
|
+
Requires-Dist: requests>=2.34.2
|
|
43
|
+
Requires-Dist: beautifulsoup4>=4.15.0
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# Jupiterweb Scraper
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+

|
|
50
|
+
[](https://pypi.org/project/jupiterweb-scraper/)
|
|
51
|
+
|
|
52
|
+
Biblioteca para extração de informações sobre disciplinas da Universidade de São Paulo a partir do [Jupiterweb](https://uspdigital.usp.br/jupiterweb/).
|
|
53
|
+
|
|
54
|
+
## 📖 Sobre o projeto
|
|
55
|
+
|
|
56
|
+
O **Jupiterweb Scraper** é uma biblioteca Python que permite a extração de informações sobre disciplinas da Universidade de São Paulo a partir do [Jupiterweb](https://uspdigital.usp.br/jupiterweb/), o sistema oficial de gestão acadêmica da universidade.
|
|
57
|
+
|
|
58
|
+
A biblioteca foi desenvolvida por alunos do IME-USP, inicialmente para atender às demandas de um projeto interno da [IME Jr](https://imejr.com/) — a empresa júnior do instituto. No entanto, percebemos que a obtenção de dados do Jupiterweb é uma necessidade recorrente em projetos voltados à comunidade USP. Por isso, decidimos disponibilizar o scraper como projeto open-source, com o intuito de facilitar o desenvolvimento de novas ferramentas destinadas à universidade.
|
|
59
|
+
|
|
60
|
+
> ⚠️ **Aviso:** O Jupiterweb é um site antigo, com uma estrutura HTML complexa e por vezes inconsistente, o que torna o processo de scraping muito desafiador. É esperado que a biblioteca contenha erros que passaram despercebidos. Se você encontrar algum problema ou comportamento inesperado, pedimos que abra uma [Issue](https://github.com/davigole/jupiterweb-scraper/issues) descrevendo o ocorrido.
|
|
61
|
+
|
|
62
|
+
## 🚀 Instalação
|
|
63
|
+
|
|
64
|
+
Para instalar a biblioteca, utilize o comando
|
|
65
|
+
```bash
|
|
66
|
+
pip install jupiterweb-scraper
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Ou, para instalar a partir do repositório:
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/davigole/jupiterweb-scraper.git
|
|
72
|
+
cd jupiterweb-scraper
|
|
73
|
+
pip install -e .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 📚 Como usar
|
|
77
|
+
|
|
78
|
+
*A fazer*
|
|
79
|
+
|
|
80
|
+
## 🤝 Como contribuir
|
|
81
|
+
|
|
82
|
+
Caso queira contribuir mas não saiba por onde começar, aqui estão algumas melhorias e funcionalidades que ainda não foram implementadas:
|
|
83
|
+
|
|
84
|
+
- Buscar disciplinas por parte do nome, horário, vagas remanescentes, etc. (o Jupiterweb já tem essas funcionalidades)
|
|
85
|
+
- Obter os cursos oferecidos por cada unidade e informações sobre cada curso (descrição, objetivos, grade curricular, etc.), disponíveis na seção "Cursos de ingresso" do Jupiterweb
|
|
86
|
+
- Obter informações sobre docentes (nome, instituto, departamento, disciplinas que ministra/ministrou, etc.)
|
|
87
|
+
- Obter informações do calendário escolar, disponível em PDF no Jupiterweb
|
|
88
|
+
- Testes automáticos para verificar o funcionamento do scraping
|
|
89
|
+
- Documentação mais completa e exemplos de uso
|
|
90
|
+
- Qualquer alteração nas funções de scraping que torne a biblioteca mais robusta
|
|
91
|
+
|
|
92
|
+
Contribuições são muito bem-vindas!
|
|
93
|
+
|
|
94
|
+
## 📄 Licença
|
|
95
|
+
|
|
96
|
+
MIT © [IME Jr](https://imejr.com/)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
jupiterweb/__init__.py
|
|
5
|
+
jupiterweb/disciplina.py
|
|
6
|
+
jupiterweb/instituto.py
|
|
7
|
+
jupiterweb/jupiterweb.py
|
|
8
|
+
jupiterweb/paths.py
|
|
9
|
+
jupiterweb/urls.py
|
|
10
|
+
jupiterweb/utils.py
|
|
11
|
+
jupiterweb/data/institutos.json
|
|
12
|
+
jupiterweb_scraper.egg-info/PKG-INFO
|
|
13
|
+
jupiterweb_scraper.egg-info/SOURCES.txt
|
|
14
|
+
jupiterweb_scraper.egg-info/dependency_links.txt
|
|
15
|
+
jupiterweb_scraper.egg-info/requires.txt
|
|
16
|
+
jupiterweb_scraper.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jupiterweb
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jupiterweb-scraper"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Extração de informações sobre disciplinas da Universidade de São Paulo a partir do Jupiterweb"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Davi Golebiovski" },
|
|
14
|
+
{ name = "Isaque Nascimento" },
|
|
15
|
+
{ name = "Lucas Kevin Silva Muniz" },
|
|
16
|
+
]
|
|
17
|
+
keywords = ["usp", "jupiterweb", "scraper", "universidade", "web", "university"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"requests>=2.34.2",
|
|
30
|
+
"beautifulsoup4>=4.15.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Repository = "https://github.com/davigole/jupiterweb-scraper"
|
|
35
|
+
Issues = "https://github.com/davigole/jupiterweb-scraper/issues"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["."]
|
|
39
|
+
include = ["jupiterweb*"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.package-data]
|
|
42
|
+
jupiterweb = ["data/*"]
|