Script Python pour récupérer le nombre de vues d’une page
This commit is contained in:
		
							parent
							
								
									3fd565fd2a
								
							
						
					
					
						commit
						f0e1c4c815
					
				|  | @ -0,0 +1,235 @@ | ||||||
|  | import bottleneck | ||||||
|  | import calendar | ||||||
|  | import collections | ||||||
|  | from datetime import datetime | ||||||
|  | import logging | ||||||
|  | import math | ||||||
|  | import matplotlib | ||||||
|  | from matplotlib import pyplot | ||||||
|  | import requests | ||||||
|  | import sys | ||||||
|  | import mwclient | ||||||
|  | 
 | ||||||
|  | # Chemin racine pour les API Wikimedia | ||||||
|  | wikimedia_base_path = 'https://wikimedia.org/api/rest_v1' | ||||||
|  | 
 | ||||||
|  | # Patron d’accès à l’API pageviews de Wikimedia | ||||||
|  | wikimedia_pageviews_path = '/' + '/'.join([ | ||||||
|  |     'metrics', 'pageviews', 'per-article', '{project}', | ||||||
|  |     '{access}', '{agent}', '{article}', '{granularity}', | ||||||
|  |     '{start}', '{end}' | ||||||
|  | ]) | ||||||
|  | 
 | ||||||
|  | # Format de dates utilisée pour l’API Wikimedia | ||||||
|  | wikimedia_date_format = '%Y%m%d' | ||||||
|  | 
 | ||||||
|  | # Date de première disponibilité des pageviews sur l’API Wikimedia | ||||||
|  | wikimedia_pageviews_start = datetime(2015, 7, 1) | ||||||
|  | 
 | ||||||
|  | # Objet pour afficher le journal d’exécution | ||||||
|  | logger = logging.getLogger('pageviews') | ||||||
|  | 
 | ||||||
|  | def wrapping_move_mean(data, window): | ||||||
|  |     """ | ||||||
|  |     Calcule une moyenne par fenêtre circulaire sur un tableau de valeurs. | ||||||
|  | 
 | ||||||
|  |     :param data: Tableau de valeurs. | ||||||
|  |     :param window: Taille de la fenêtre. | ||||||
|  |     :return: Tableau moyenné avec une fenêtre de taille donnée. Le résultat | ||||||
|  |     a les même dimensions que l’entrée. | ||||||
|  |     """ | ||||||
|  |     down_half_window = math.floor(window / 2) | ||||||
|  |     up_half_window = math.ceil(window / 2) | ||||||
|  |     padded_data = data[-down_half_window:] + data + data[:up_half_window] | ||||||
|  |     return bottleneck.move_mean(padded_data, window=window)[window - 1:] | ||||||
|  | 
 | ||||||
|  | def wikimedia_page_views(site, article): | ||||||
|  |     """ | ||||||
|  |     Obtient le nombre de visites sur une page Wikipédia par jour. | ||||||
|  | 
 | ||||||
|  |     :param site: Site Wikipédia ciblé. | ||||||
|  |     :param article: Article ciblé dans le site. | ||||||
|  |     :return: Compteur associant chaque jour à son nombre de visites. | ||||||
|  |     """ | ||||||
|  |     # Soumet une requête à l’API REST pour obtenir les vues de l’article | ||||||
|  |     res = requests.get(wikimedia_base_path + wikimedia_pageviews_path.format( | ||||||
|  |         project=site.host, | ||||||
|  |         article=article, | ||||||
|  |         access='all-access', | ||||||
|  |         agent='user', | ||||||
|  |         granularity='daily', | ||||||
|  |         start=wikimedia_pageviews_start.strftime(wikimedia_date_format), | ||||||
|  |         end=datetime.today().strftime(wikimedia_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 wikimedia_article_canonical_name(site, article): | ||||||
|  |     """ | ||||||
|  |     Obtient le nom canonique d’un article après avoir suivi les redirections. | ||||||
|  | 
 | ||||||
|  |     :param site: Objet empaquetant l’API du site Wikipédia ciblé. | ||||||
|  |     :param article: Article ciblé dans le site. | ||||||
|  |     :return: Nom canonique de l’article. | ||||||
|  |     """ | ||||||
|  |     original_page = site.pages[article] | ||||||
|  | 
 | ||||||
|  |     if not original_page.exists: | ||||||
|  |         raise Exception( | ||||||
|  |             'Article « {} » inexistant sur {}' | ||||||
|  |             .format(article, site.host) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     return original_page.resolve_redirect().name | ||||||
|  | 
 | ||||||
|  | def wikimedia_article_views(site, article): | ||||||
|  |     """ | ||||||
|  |     Obtient le nombre de visites sur un article Wikipédia, incluant la page | ||||||
|  |     canonique et toutes les pages redirigées vers celle-ci, par jour. | ||||||
|  | 
 | ||||||
|  |     :param site: Objet empaquetant l’API du site Wikipédia ciblé. | ||||||
|  |     :param article: Article ciblé dans le site. | ||||||
|  |     :return: Liste de couples contenant d’une part un jour et d’autre part | ||||||
|  |     le nombre de visites associées à ce jour. | ||||||
|  |     """ | ||||||
|  |     # Récupération de l’ensemble des pages qui redirigent vers la page donnée | ||||||
|  |     response = site.api('query', prop='redirects', titles=article) | ||||||
|  |     page_response = list(response['query']['pages'].values())[0] | ||||||
|  | 
 | ||||||
|  |     if 'missing' in page_response: | ||||||
|  |         raise Exception( | ||||||
|  |             'Article « {} » inexistant sur {}' | ||||||
|  |             .format(article, site.host) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     # Si la réponse n’inclut pas la clé « redirects », c’est qu’il n’existe | ||||||
|  |     # aucune redirection vers la page | ||||||
|  |     redirects = [ | ||||||
|  |         item['title'] for item in | ||||||
|  |         page_response['redirects'] | ||||||
|  |     ] if 'redirects' in page_response else [] | ||||||
|  | 
 | ||||||
|  |     # Somme le nombre de visites sur chacune des pages | ||||||
|  |     return sum( | ||||||
|  |         (wikimedia_page_views(site, page) | ||||||
|  |         for page in redirects + [article]), | ||||||
|  |         start=collections.Counter() | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | def wikimedia_mean_article_views(site, article): | ||||||
|  |     """ | ||||||
|  |     Obtient des statistiques moyennes sur les vues d’un article Wikipédia par | ||||||
|  |     jour de l’année (omettant le 29 février pour les années bissextiles). | ||||||
|  | 
 | ||||||
|  |     :param site: Objet empaquetant l’API du site Wikipédia ciblé. | ||||||
|  |     :param article: Article ciblé dans le site. | ||||||
|  |     :return: Tableau de taille 365 contenant le nombre de visites moyennes pour | ||||||
|  |     chaque jour de l’année. | ||||||
|  |     """ | ||||||
|  |     data = wikimedia_article_views(site, article) | ||||||
|  | 
 | ||||||
|  |     # 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 data.items(): | ||||||
|  |         date = datetime.strptime(date_str, wikimedia_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(accumulator[datemonth]) | ||||||
|  |             / len(accumulator[datemonth]) | ||||||
|  |         ) if accumulator[datemonth] else 0 | ||||||
|  | 
 | ||||||
|  |     # Fait une moyenne glissante sur 7 jours | ||||||
|  |     days = [item[1] for item in sorted( | ||||||
|  |         list(accumulator.items()), | ||||||
|  |         key=lambda x: x[0] | ||||||
|  |     )] | ||||||
|  | 
 | ||||||
|  |     return wrapping_move_mean(days, window=60) | ||||||
|  | 
 | ||||||
|  | def create_year_plot(): | ||||||
|  |     """ | ||||||
|  |     Initialise un graphe avec en abscisse les 365 jours d’une année non | ||||||
|  |     bissextile. | ||||||
|  | 
 | ||||||
|  |     :return: Figure et axes Matplotlib. | ||||||
|  |     """ | ||||||
|  |     fig, ax = pyplot.subplots() | ||||||
|  | 
 | ||||||
|  |     ax.set_xlabel('Jours de l’année') | ||||||
|  |     ax.set_xticks([ | ||||||
|  |         datetime(1, month, 1).toordinal() | ||||||
|  |         for month in range(1, 13) | ||||||
|  |     ]) | ||||||
|  | 
 | ||||||
|  |     ax.set_xticklabels(calendar.month_abbr[1:13]) | ||||||
|  |     return fig, ax | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     logging.basicConfig(level=logging.INFO) | ||||||
|  | 
 | ||||||
|  |     if len(sys.argv) < 3: | ||||||
|  |         print("""Utilisation : {} [project] [article]... | ||||||
|  | Obtient les statistiques moyenne de vue de pages wiki. | ||||||
|  | 
 | ||||||
|  | Paramètres : | ||||||
|  | 
 | ||||||
|  |     project     Projet Wikipédia ciblé. | ||||||
|  |     article     Nom(s) d’article(s) Wikipédia ciblé(s). | ||||||
|  | 
 | ||||||
|  | Au moins un article doit être donné. Le nombre de visites est moyenné sur | ||||||
|  | l’année dans une fenêtre de 60 jours. Les redirections d’article sont suivies | ||||||
|  | et toute visite sur une page de redirection pointant vers l’article est | ||||||
|  | dénombrées comme une visite sur la page canonique. | ||||||
|  | """.format(sys.argv[0]), file=sys.stderr) | ||||||
|  |         sys.exit(1) | ||||||
|  | 
 | ||||||
|  |     project = sys.argv[1] | ||||||
|  |     articles = sys.argv[2:] | ||||||
|  | 
 | ||||||
|  |     site = mwclient.Site(project) | ||||||
|  |     fig, ax = create_year_plot() | ||||||
|  | 
 | ||||||
|  |     for article in articles: | ||||||
|  |         canonical_article = wikimedia_article_canonical_name(site, article) | ||||||
|  | 
 | ||||||
|  |         if article != canonical_article: | ||||||
|  |             logger.info( | ||||||
|  |                 'Suivi de la redirection de « {} » en « {} »' | ||||||
|  |                 .format(article, canonical_article) | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         data = wikimedia_mean_article_views(site, canonical_article) | ||||||
|  |         ax.plot(data, label=canonical_article) | ||||||
|  |         ax.set_ylabel('Vues par jour') | ||||||
|  | 
 | ||||||
|  |     fig.legend() | ||||||
|  |     fig.autofmt_xdate() | ||||||
|  |     fig.tight_layout() | ||||||
|  | 
 | ||||||
|  |     pyplot.show() | ||||||
		Loading…
	
		Reference in New Issue