wikimedica-disease-search/data/pageviews/pageviews.py

236 lines
7.6 KiB
Python
Raw Normal View History

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 daccès à lAPI pageviews de Wikimedia
wikimedia_pageviews_path = '/' + '/'.join([
'metrics', 'pageviews', 'per-article', '{project}',
'{access}', '{agent}', '{article}', '{granularity}',
'{start}', '{end}'
])
# Format de dates utilisée pour lAPI Wikimedia
wikimedia_date_format = '%Y%m%d'
# Date de première disponibilité des pageviews sur lAPI Wikimedia
wikimedia_pageviews_start = datetime(2015, 7, 1)
# Objet pour afficher le journal dexé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 lentré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 à lAPI REST pour obtenir les vues de larticle
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 dun article après avoir suivi les redirections.
:param site: Objet empaquetant lAPI du site Wikipédia ciblé.
:param article: Article ciblé dans le site.
:return: Nom canonique de larticle.
"""
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 lAPI du site Wikipédia ciblé.
:param article: Article ciblé dans le site.
:return: Liste de couples contenant dune part un jour et dautre part
le nombre de visites associées à ce jour.
"""
# Récupération de lensemble 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 ninclut pas la clé «redirects», cest quil nexiste
# 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 dun article Wikipédia par
jour de lannée (omettant le 29 février pour les années bissextiles).
:param site: Objet empaquetant lAPI 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 lanné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 dune année non
bissextile.
:return: Figure et axes Matplotlib.
"""
fig, ax = pyplot.subplots()
ax.set_xlabel('Jours de lanné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) darticle(s) Wikipédia ciblé(s).
Au moins un article doit être donné. Le nombre de visites est moyenné sur
lannée dans une fenêtre de 60 jours. Les redirections darticle sont suivies
et toute visite sur une page de redirection pointant vers larticle 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()