Extração de informações sobre disciplinas da Universidade de São Paulo a partir do Jupiterweb
Project description
Jupiterweb Scraper
Biblioteca para extração de informações sobre disciplinas da Universidade de São Paulo a partir do Jupiterweb.
📖 Sobre o projeto
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, o sistema oficial de gestão acadêmica da universidade.
A biblioteca foi desenvolvida por alunos do IME-USP, inicialmente para atender às demandas de um projeto interno da IME Jr — 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.
⚠️ 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 descrevendo o ocorrido.
🚀 Instalação
Para instalar a biblioteca, utilize o comando
pip install jupiterweb-scraper
Ou, para instalar a partir do repositório:
git clone https://github.com/davigole/jupiterweb-scraper.git
cd jupiterweb-scraper
pip install -e .
📚 Como usar
>>> import jupiterweb
A função obter_institutos() retorna todos os institutos da USP na forma de objetos da classe Instituto:
>>> institutos_usp = jupiterweb.obter_institutos()
>>> instituto = institutos_usp[37]
>>> instituto
Instituto(codigo='45',nome='Instituto de Matemática, Estatística e Ciência da Computação',campus='Butantã',abrev='IME')
A partir de um instituto, é possível obter as suas disciplinas:
>>> disciplinas_instituto = instituto.obter_disciplinas()
>>> disciplina = disciplinas_instituto[314]
>>> disciplina
Disciplina(sigla='MAC0520')
E a partir de uma disciplina, podemos obter os seus dados completos:
>>> dados = disciplina.obter_dados()
>>> dados["nome"]
'Teoria dos Grafos'
>>> dados["departamento"]
'Ciência da Computação'
>>> dados["carga horaria total"]
'60 h'
>>> dados["ementa"]
'Conexidade. Emparelhamentos. Coloração de vértices. Problemas extremais. Grafos planares. Fluxos e colorações. Grafos menores.\nConnectivity. Matchings. Vertex colouring. Extremal problems. Planar graphs. Flows and colouring. Minors.'
>>> dados["requisitos"]
{'45052 Bacharelado em Ciência da Computação (integral)': [[Requisito(sigla='MAC0320',tipo='requisito')]]}
Obter instituto pelo código
Caso já conheça o código do instituto, é possível instanciá-lo diretamente:
>>> from jupiterweb import Instituto
>>> instituto = Instituto("27", "Escola de Comunicações e Artes", "Butantã", "ECA")
>>> disciplinas_instituto = instituto.obter_disciplinas()
>>> disciplinas_instituto[30]
Disciplina(sigla='CAP0260')
Obter disciplina pela sigla
Da mesma forma, é possível instanciar uma disciplina diretamente por sua sigla:
>>> from jupiterweb import Disciplina
>>> disciplina = Disciplina("MAT0112")
>>> dados = disciplina.obter_dados()
>>> dados["nome"]
'Vetores e Geometria'
>>> dados["departamento"]
'Matemática'
🔍 Detalhes de implementação
No geral, o uso da biblioteca é simples. Porém, alguns detalhes merecem atenção.
Lazy loading
Como o processo de scraping pode ser demorado, os dados da classe Instituto e Disciplina são carregados sob demanda.
Um objeto Instituto recém-criado não contém disciplinas.
Elas são carregadas apenas na primeira chamada de Instituto.obter_disciplinas().
A partir daí, o resultado fica armazenado no objeto, e as chamadas subsequentes retornam o cache sem realizar scraping novamente.
O mesmo vale para objetos Disciplina — os dados são obtidos somente na primeira chamada de Disciplina.obter_dados(), e ficam armazenados no objeto a partir daí.
>>> from jupiterweb import Disciplina
>>> disciplina = Disciplina("FLT0123") # vazia (sem dados)
>>> disciplina.obter_dados() # faz scraping (devagar)
{'sigla': 'FLT0123',
'instituto': 'Faculdade de Filosofia, Letras e Ciências Humanas',
... }
>>> disciplina.obter_dados() # retorna cache (rápido)
{'sigla': 'FLT0123',
'instituto': 'Faculdade de Filosofia, Letras e Ciências Humanas',
... }
Dados da disciplina
A função Disciplina.obter_dados() retorna um dicionário com as informações obtidas no scraping da disciplina, como a seguir
>>> from jupiterweb import Disciplina
>>> disciplina = Disciplina("CBM0190")
>>> disciplina.obter_dados()
{
'sigla': 'CBM0190',
'instituto': 'Centro de Biologia Marinha',
'departamento': 'Centro de Biologia Marinha',
'nome': 'Conservação Marinha',
...
'ementa': 'Serão apresentadas informações sobre:\n1) o histórico da di'...,
'objetivos': 'A disciplina tem como objetivos:\n1) apresentar os princ'...,
'conteudo programatico': '- Histórico da conservação marinha no mundo '...,
'viagem didatica': {
'e estruturante?': 'Sim',
'atividades a serem desenvolvidas': 'As atividades práticas desta '...,
},
'instrumentos e criterios de avaliacao': {
'metodo de avaliacao': 'Uma prova escrita, atividades teórico-prát'...,
'criterio de avaliacao': 'Prova escrita 30%\nAtividades teórico-pr'...,
'norma de recuperacao': 'Sem recuperação.',
},
...
'requisitos': {},
'periodo ideal': {},
'oferecimento': [],
}
É importante notar que as chaves do dicionário de dados variam de disciplina para disciplina, dependendo das informações disponíveis no Jupiterweb. Enquanto algumas chaves são esperadas em todas as disciplinas (como "sigla", "nome", "requisitos", etc.), outras podem ser exclusivas de algumas disciplinas (como "viagem didatica" acima).
O processo de scraping da página principal da disciplina no Jupiterweb percorre todos os campos disponíveis ("Ementa", "Instrumentos e Critérios de Avaliação", "Créditos Aula", etc.) e converte os seus títulos para minúsculo e sem acentos ("ementa", "instrumentos e criterios de avaliacao", "creditos aula", etc.) para usar como chaves no dicionário. Já outras informações, como requisitos, período ideal e oferecimento, são obtidas a partir de páginas específicas do Jupiterweb, e por isso ficam em chaves fixas ("requisitos", "periodo ideal" e "oferecimento", respectivamente).
Algumas seções da página principal da disciplina possuem subseções, como "Viagem Didática" e "Instrumentos e Critérios de Avaliação" acima. Nesses casos, os dados são organizados em um dicionário aninhado, onde as chaves do dicionário interno correspondem aos títulos das subseções ("e estruturante?", "atividades a serem desenvolvidas", "método de avaliação", "critério de avaliação", etc.).
Requisitos da disciplina
No Jupiterweb, os requisitos de uma disciplina são organizados em cursos. Para cada curso, os requisitos são organizados em conjuntos de alternativas: o aluno daquele curso deve satisfazer um conjunto ou outro. Ou seja, os requisitos podem ser da forma
"Para fazer a disciplina X, o aluno do curso 123 precisa ter cursado as disciplinas A e B, ou ter cursado as disciplinas C e D, ou ter cursado a disciplina E."
A imagem abaixo mostra que, para fazer a disciplina "MAE0227 - Probabilidade II", alunos do curso "45031 Matemática - Bacharelado (integral)" precisam ter cursado as disciplinas "MAE0127" e "MAT2453", ou ter cursado a disciplina "MAE0121":
Uma mesma disciplina pode estar em dois grupos de alternativas. Por exemplo, para fazer "MAT0334 - Análise Funcional", alunos do curso "45031 Matemática - Bacharelado" devem ter cursado "MAT0222" e "MAT0311", ou ter cursado "MAT0222" e "MAT0317" (então "MAT0222" é obrigatória):
Para representar essa estrutura, os requisitos de uma disciplina ficam armazenados em um dicionário, cujas chaves são cursos, e os valores são as listas de alternativas correspondentes. Cada alternativa é uma lista de requisitos. Por exemplo, disciplina.obter_dados()["requisitos"] = {'CURSO': [[x, y], [w, z]]} mostra que, para cursar a disciplina,alunos de "CURSO" devem ter cumprido os requisitos x e y, ou os requisitos w e z.
O código abaixo elucida essa ideia:
>>> from jupiterweb import Disciplina
>>> disciplina = Disciplina("MAE0227")
>>> dados = disciplina.obter_dados()
>>> dados["requisitos"]
{
'45031 Matemática - Bacharelado (integral)': [
[Requisito(sigla='MAE0127',tipo='requisito'), Requisito(sigla='MAT2453',tipo='requisito')],
[Requisito(sigla='MAE0121',tipo='requisito')]
],
'45062 Estatística - Bacharelado (integral)': [
[Requisito(sigla='MAE0127',tipo='requisito'), Requisito(sigla='MAT2453',tipo='requisito')]
]
}
Cada requisito é representado como um objeto da classe Requisito, que armazena a sigla da disciplina que exige, e o tipo do requisito (em letras minúsculas).
No Jupiterweb, alguns requisitos possuem tipos especiais ("requisito fraco", "indicação de conjunto", etc.) Por exemplo,
Os requisitos do tipo "Indicação de Conjunto" acima ficam armazenados como a seguir:
>>> from jupiterweb import Disciplina
>>> disciplina = Disciplina("RCG4041")
>>> dados = disciplina.obter_dados()
>>> dados["requisitos"]
{
'17200 Terapia Ocupacional (noturno)': [
[Requisito(sigla='RCG4040',tipo='indicação de conjunto'), Requisito(sigla='RCG3052',tipo='requisito')]
],
'17201 Terapia Ocupacional (integral)': [
[Requisito(sigla='RCG4040',tipo='indicação de conjunto'), Requisito(sigla='RCG3052',tipo='requisito')]
]
}
Também é possível obter o período ideal da disciplina para cada curso, que fica disponível na página de requisitos de Jupiterweb. Esses dados ficam armazenados em um dicionário semelhante ao de requisitos:
>>> from jupiterweb import Disciplina
>>> disciplina = Disciplina("MAE0501")
>>> dados = disciplina.obter_dados()
>>> dados["periodo ideal"]
{
'12042 Bacharelado em Ciências Atuariais (noturno)': 6,
'45061 Estatística - Bacharelado (integral)': 8,
'45062 Estatística - Bacharelado (integral)': 6,
}
🤝 Como contribuir
Caso queira contribuir mas não saiba por onde começar, aqui estão algumas melhorias e funcionalidades que ainda não foram implementadas:
- Buscar disciplinas por parte do nome, horário, vagas remanescentes, etc. (o Jupiterweb já tem essas funcionalidades)
- 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
- Obter informações sobre docentes (nome, instituto, departamento, disciplinas que ministra/ministrou, etc.)
- Obter informações do calendário escolar, disponível em PDF no Jupiterweb
- Controle de erros mais robusto, para lidar com as inconsistências do Jupiterweb
- Carregamento segmentado dos dados da disciplina, para evitar que o processo de scraping demore muito (por exemplo, para obter apenas o nome da disciplina, não deveríamos fazer scraping das páginas de requisitos e de oferecimento)
- Testes automáticos para verificar o funcionamento do scraping
- Documentação mais completa e exemplos de uso
- Qualquer alteração nas funções de scraping que torne a biblioteca mais robusta
Contribuições são muito bem-vindas!
📄 Licença
MIT © IME Jr
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jupiterweb_scraper-1.0.1.tar.gz.
File metadata
- Download URL: jupiterweb_scraper-1.0.1.tar.gz
- Upload date:
- Size: 18.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d135b6bbf2df9a22504fa2daa6dbe2786bdbcb23a73f7940c83b9953b10940c
|
|
| MD5 |
b96ca27b10fabc3c64b5a60ffa9a6b61
|
|
| BLAKE2b-256 |
ba66d4faab12b62e2105120fcc8a5220ceecf4623cc9d630804cd325d81f6692
|
File details
Details for the file jupiterweb_scraper-1.0.1-py3-none-any.whl.
File metadata
- Download URL: jupiterweb_scraper-1.0.1-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea97b01d2701688ade1c3f074ffd6afcf0edb24ca54b1962687c10cf7417edaa
|
|
| MD5 |
db51f012661ca0b1495770db7f30d0e1
|
|
| BLAKE2b-256 |
7a742570060bb6fbcd758614a9542bdcdc808f209f47e317d49471ad4ba2ac10
|