Created
September 21, 2020 13:58
-
-
Save Maes95/24bcbc9ed16cc32ee19c890acf826904 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# coding: utf-8 | |
# # Mineria de Repositorios con GitHub | |
# La mayoria de librerias de este ejemplo están disponibles de manera nativa con Python 3.7+ (si lo tienes instalado). Solo será necesario instalar la librería que hace uso de la API de GitHub: | |
# ```bash | |
# $ pip install PyGithub | |
# ``` | |
import os | |
import datetime | |
import random | |
import json | |
import pickle | |
from github import Github | |
# Generamos un Token para consultar la API de GitHub a través de la libreria GitHub | |
# - Necesitamos tener una cuenta en GitHub | |
# - Seguimos [este sencillo tutorial](https://docs.github.com/es/github/authenticating-to-github/creating-a-personal-access-token) para generarlo | |
# - El token NO es necesario para realizar las consultas, pero la cuota de peticiones que podemos hacer es significatimanete mayor y nos ahorrará mucho tiempo de espera entre peticiones | |
token ="<token>" | |
g = Github("maes95",token) | |
# Podemos realizar consultas sencillas utilizando una serie de parámetros definidos por la librería (que se incluirán en la consulta a la API). Podemos ver en detalle la documentación el [repositorio de la librería](https://github.com/PyGithub/PyGithub) o en su [documentación oficial](https://pygithub.readthedocs.io/en/latest/examples/MainClass.html#search-repositories-by-language) | |
# | |
# Al ejecutar un consulta, obtendremos un objeto generador | |
# - No realiza ninguna consulta o búsqueda | |
# - Comienza a realizarla al iterar sobre el generador | |
query=""" | |
language:java | |
stars:>=500 | |
forks:>=300 | |
created:<2015-01-01 | |
pushed:>2020-01-01 | |
archived:false | |
is:public | |
""" | |
generator = g.search_repositories(query=query) | |
# Podemos convertir en generador en una lista de repositorios de manera sencilla en Python. En este casi SI se realizará la consulta | |
# Al iterar (internamente) el generador, crea una lista a partir de la búsqueda | |
repositories= list(generator) | |
# Guardamos la información de los repositorios recuperados en un archivo binario de Python | |
# - Utilizamos la librería pickle | |
# - Las búsquedas en la API de GitHub pueden variar con el tiempo, podemos obtener más o menos repositorios al realizar la misma búsqueda | |
# - Lo guardamos con un timestamp para diferenciarlo inequivocamente | |
date=str(datetime.datetime.now()) | |
with open('repos_%s.pickle'%date, 'wb') as f: | |
pickle.dump(repositories, f) | |
print("Total repos: %d"%len(repositories)) | |
# Leemos el archivo binario. Si hemos cerrado y abierto de nuevo el notebook, el timestamp habrá cambiado y tendremos que ponerlo a mano. | |
repos = 'repos_%s.pickle'%date | |
#repos = 'repos_2020-09-21 12:03:13.421536.pickle' | |
with open(repos, 'rb') as f: | |
repositories = pickle.load(f) | |
# Sobre los repositorios encontramos, podemos realizar filtros. Para empezar, vamos a filtrar los repositorios por su número de commits (para quedarnos solo con ciertos repositorios) | |
MAX_COMMITS = 10000 | |
MIN_COMMITS = 1000 | |
filtered_repos = [] | |
for repo in repositories: | |
commits = repo.get_commits().totalCount | |
if commits >= MIN_COMMITS and commits <= MAX_COMMITS: | |
filtered_repos.append(repo) | |
print("Total projects %d"%len(filtered_repos)) | |
# Realizamos un nuevo filtro. Vamos a quedarnos solo con los proyectos que tienen almenos uno de los distintos sistemas de construcción más típicos en Java. | |
# | |
# - Maven (pom.xml) | |
# - Gradle (build.gradle) | |
# - Ant (build.xml) | |
# | |
# Esta consulta es algo más laboriosa que la anterior, ya que tiene que comprobar que alguno de los archivos del repositorio coincide con los archivos de configuración que definimos. Tenemos que tener cuidado en el orden de las consultas, para que resulten lo más optimas posible. | |
filtered_repos_2 = [] | |
for repo in filtered_repos: | |
contents = repo.get_contents("") | |
for content_file in contents: | |
if content_file.path in ["build.gradle", "pom.xml", "build.xml"]: | |
filtered_repos_2.append(repo) | |
break | |
print("Total repos: %d"%len(filtered_repos_2)) | |
# Además de filtrar los proyectos por ficheros que contengan, también podemos inspeccionar ficheros concretos (incluso grupos de ficheros, por ejemplo, con terminación .java) | |
filtered_repos_3 = [] | |
for repo in filtered_repos_2: | |
contents = repo.get_contents("") | |
isAndroid = False | |
for file in contents: | |
if file.path == "build.gradle": | |
isAndroid = 'com.android.tools.build' in str(repo.get_contents("build.gradle").decoded_content) | |
break | |
if not isAndroid: filtered_repos_3.append(repo) | |
print("Total projects %d"%len(filtered_repos_3)) | |
# Puede que a pesar de los filtros que realizamos, obtengamos un gran número de repositorios, demasiados para el experimento que queremos realizar. Por ello, podemos limitar la muestra de repositorios escogiendo un número significativo al azar | |
# Seleccionamos 30 proyectos de manera aleatoria | |
sampling = random.choices(filtered_repos_3, k=30) | |
for project in sampling: | |
project_name = project.full_name.split("/")[1] | |
print(project.full_name) | |
# Ya tenemos seleccionados los repositorios. Pero GitHub no nos garantiza que estos repositorios siempre sigan ahí, podrían: | |
# - Ser borrados | |
# - Convertirse en repositorios privados | |
# - Desaparecer commits o ramas de su histórico | |
# | |
# Por ello, una buena idea nada más seleccionarlos es clonarlos y tener una copia en local. Para ellos creamos una carpeta dónde guardarlos. | |
# | |
# La creación de una carpeta con cualquier lenguaje de programación es trivial, especialmente con las librerias actuales, pero me gustaría ilustrar un concepto sencillo, pero eficaz: no sobrescribir nunca los recursos creados. Podemos perder información previa al sobrescribirla con la nueva. En el caso de un directorio, simplemente no nos dejara crearlo, pero a la hora de crear archivos, los reemplazará sin preguntarnos. | |
folder_name = 'repositories' | |
if not os.path.exists(folder_name): | |
print("Folder %s created!"%folder_name) | |
os.mkdir("repositories") | |
else: | |
print("Folder %s already exist"%folder_name) | |
# Clonamos de forma iterativa los repositorios en la nueva carpeta. Si ejecutamos de nuevo la siguiente celda, no clonará de nuevo los proyectos ya existentes | |
for project in sampling: | |
project_name = project.full_name.split("/")[1] | |
project_folder = "%s/%s" % (folder_name, project_name) | |
# CHECK IF PROJECT EXISTS | |
if os.path.exists(project_folder): | |
print(" -> Project %s already exist in local folder!"%project.full_name) | |
else: | |
get_ipython().system('git clone $project.clone_url $project_folder') | |
print(" -> Project %s cloned!"%project_name) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment