import collections from datetime import datetime, timedelta from .http import session import numpy from scipy import stats # Chemin racine pour les API Wikimedia pageviews_root = 'https://wikimedia.org/api/rest_v1' # Patron d’accès à l’API pageviews pour un article pageviews_article_endpoint = '/' + '/'.join([ 'metrics', 'pageviews', 'per-article', '{project}', '{access}', '{agent}', '{article}', '{granularity}', '{start}', '{end}' ]) # Chemin d’accès à l’API pageviews pour un projet complet pageviews_project_endpoint = '/' + '/'.join([ 'metrics', 'pageviews', 'aggregate', '{project}', '{access}', '{agent}', '{granularity}', '{start}', '{end}' ]) # Format de dates utilisée pour l’API Wikimedia pageviews_date_format = '%Y%m%d' # Date de première disponibilité des pageviews sur l’API Wikimedia pageviews_first_data = datetime(2015, 7, 1) # Date de dernière disponibilité des pageviews sur l’API Wikimedia pageviews_last_data = datetime.today() - timedelta(days=1) # Tableau contenant tous les jours de l’année de 1 à 365 year_all_days = numpy.arange(1, 366) def _year_date_distance(a, b): """ Calcule la distance entre deux jours de l’année. :param a: Première valeur (peut être un tableau numpy). :param b: Seconde valeur (peut être un tableau numpy). :return: Valeur de la distance. >>> _year_date_distance(10, 360) 15 >>> _year_date_distance(numpy.array([10, 182, 355]), 182) [172, 0, 173] """ return numpy.stack(( numpy.mod(a - b, len(year_all_days)), numpy.mod(b - a, len(year_all_days)) )).min(axis=0) def smooth(views, scale): """ Lisse un tableau de valeurs contenant une valeur par jour de l’année en utilisant un noyau gaussien. À la bordure de fin ou de début d’année, le lissage est fait avec l’autre morceau de l’année. :param views: Données à lisser. :param scale: Variance du noyau gaussien. :return: Données lissées. """ ref_pdf = stats.norm.pdf( _year_date_distance(year_all_days, 1), scale=scale ) pdf_matrix = numpy.stack([ numpy.roll(ref_pdf, day - 1) for day in year_all_days ]) return pdf_matrix.dot(views) def get_aggregate(project): """ Obtient le nombre de visites sur Wikipédia par jour. :param project: Projet Wikipédia ciblé. :return: Compteur associant chaque jour à son nombre de visites. """ res = session.get(pageviews_root + pageviews_project_endpoint.format( project=project, access='all-access', agent='user', granularity='daily', start=pageviews_first_data.strftime(pageviews_date_format), end=pageviews_last_data.strftime(pageviews_date_format) )) data = res.json() # Vérifie que la réponse reçue indique un succès if res.status_code != 200: if 'detail' in data: detail = data['detail'] message = ', '.join(detail) if type(detail) == list else detail raise Exception(message) else: raise Exception('Erreur {}'.format(res.status_code)) # Construit le dictionnaire résultant return collections.Counter(dict( (record['timestamp'][:8], record['views']) for record in data['items'] )) def get_article(project, article): """ Obtient le nombre de visites sur un article Wikipédia par jour. :param project: Projet Wikipédia ciblé. :param article: Article ciblé dans le site. :return: Compteur associant chaque jour à son nombre de visites. """ res = session.get(pageviews_root + pageviews_article_endpoint.format( project=project, article=article, access='all-access', agent='user', granularity='daily', start=pageviews_first_data.strftime(pageviews_date_format), end=pageviews_last_data.strftime(pageviews_date_format) )) data = res.json() # Vérifie que la réponse reçue indique un succès if res.status_code != 200: if 'detail' in data: detail = data['detail'] message = ', '.join(detail) if type(detail) == list else detail raise Exception(message) else: raise Exception('Erreur {}'.format(res.status_code)) # Construit le dictionnaire résultant return collections.Counter(dict( (record['timestamp'][:8], record['views']) for record in data['items'] )) def mean(views): """ Calcule les vues moyennes par jour de l’année à partir d’un ensemble de vues enregistrées (omettant le 29 février pour les années bissextiles). :param views: Vues enregistrées par date. :return: Tableau de taille 365 contenant le nombre de visites moyennes pour chaque jour de l’année. """ # Fait la moyenne pour chaque jour hormis le 29 février accumulator = {} datemonth_format = '%m%d' for day in range(1, 366): datemonth = datetime.fromordinal(day).strftime(datemonth_format) accumulator[datemonth] = [] for date_str, views in views.items(): date = datetime.strptime(date_str, pageviews_date_format) if not (date.month == 2 and date.day == 29): datemonth = date.strftime(datemonth_format) accumulator[datemonth].append(views) for datemonth, value in accumulator.items(): accumulator[datemonth] = sum(value) / len(value) if value else 0 # Rassemble les valeurs moyennes pour chaque jour dans l'ordre de l'année return [item[1] for item in sorted( list(accumulator.items()), key=lambda x: x[0] )]