MediaWiki:Gadget-translation editor.js/Statistiques/code

Définition, traduction, prononciation, anagramme et synonyme sur le dictionnaire libre Wiktionnaire.
Sauter à la navigation Sauter à la recherche

Ces codes ont servi à générer les statistiques présentes dans MediaWiki:Gadget-translation editor.js/Statistiques.

Le principe pour générer les stats est simple :

  1. On compte d’un côté toutes les traductions dans les résumés d’édition du type (Traductions : +langue : [[traduction]] ; +langue2 : [[traduction2]] (assisté))
  2. Puis on analyse tous les diffs contenant des ajouts de nombreuses traductions, avec des résumés tronqués du type : (Traductions : +langue : [[traduction]] ; +langue2 : [[traduction 2]] ; +langue3 : [[traduction 3]] ; +lang…). Dans un tel cas, analyser le résumé d’édition n’est pas suffisant pour savoir combien de traductions ont été ajoutées et dans quelles langues. On analyse donc les diffs.

La première partie a été réalisée via un code écrit en C++ (parce qu’inspirée d'un script de Pamputt), tandis que la 2e a été implémentée en Python, via le framework Pywikibot.

Partie 1

  1 #include <iostream>
  2 #include <fstream>
  3 #include <string>
  4 #include <map>
  5 
  6 using namespace std;
  7 
  8 // Dump à télécharger à : https://dumps.wikimedia.org/frwiktionary/latest/ :
  9 // frwiktionary-latest-pages-meta-history.xml.7z , puis à décompresser
 10 string input_folder = "C:\\Users\\automatik\\Downloads\\";
 11 string input_file_history = input_folder + "frwiktionary-20180901-pages-meta-history.xml";
 12 
 13 // Adresses des fichiers contenant les résultats
 14 string output_folder = "C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\";
 15 string output_file_res = output_folder + "stats-trads-res.txt"; // Chiffres-clé
 16 string output_file_langs = output_folder + "stats-trads-langs.txt"; // Stats par langue
 17 string output_file_dates = output_folder + "stats-trads-dates.txt"; // Stats par date
 18 string output_file_contribs = output_folder + "stats-trads-contributors.txt"; // Stats par contributeur
 19 string output_file_ips = output_folder + "stats-trads-ips.txt"; // Stats par IP
 20 string output_file_diffs = output_folder + "stats-trads-diffs-to-check.txt"; // Diffs à analyser en Partie 2
 21 
 22 string from_date = "2014-07"; // Mois à partir duquel on dénombre les ajouts (mois inclus)
 23 string to_date = "2018-09"; // Mois jusqu’auquel on dénombre les ajouts (mois exclus)
 24 
 25 /**** STATS ****/
 26 std::map<string, int> stats_langs = {}; // exemple (en début d’analyse) : { "italien": 3, "allemand" : 8 }
 27 std::map<string, int> stats_dates = {}; // ex : { "2014-08": 6169, "2014-09" : 6203 }
 28 std::map<string, int> stats_contribs = {}; // ex : { "Un utilisateur": 213, "Un deuxième utilisateur": 65 }
 29 std::map<string, int> stats_ips = {}; // ex : { "184.252.23.16": 11, "174.145.64.87": 75 }
 30 // Maps pour stocker temporairement des résultats (pour être sûr que les trads analysées n'ont pas été révoquées)
 31 // Ces maps sont transférées vers les "map" finaux ci-dessus une fois qu'on est sûr que les traductions concernées n'ont pas été révoquées
 32 // Note : pour s'assurer que les ajouts ne sont pas révoqués, on lit tous les résumés d'édition qui succèdent à un ajout de traduction
 33 // jusqu'à ce qu'on tombe sur une révocation ou autre chose qu'un ajout de traduction (via l'outil). Si l'ajout correspond à la dernière version
 34 // de l'article, on le considère comme non révoqué
 35 std::map<string, int> stats_langs_tmp = {};
 36 std::map<string, int> stats_dates_tmp = {};
 37 std::map<string, int> stats_contribs_tmp = {};
 38 std::map<string, int> stats_ips_tmp = {};
 39 
 40 unsigned int counter_pages = 0; // Nombre de pages parcourues dans le dump (simplement pour afficher la progression de la tâche)
 41 unsigned int counter_additions = 0; // Nombre d'ajouts avec l'outil de traductions (ajouts révoqués ou non)
 42 unsigned int counter_pages_with_trans = 0; // Nombre de pages avec au moins un ajout de traduction non révoqué
 43 unsigned int counter_trans = 0; // Nombre de traductions ajoutées et non révoquées
 44 unsigned int total_count_contribs = 0, total_count_ips = 0; // Nombre de traductions non révoquées ajoutées par des contributeurs enregistrés / non enregistrés
 45 unsigned int total_count_contribs_tmp = 0, total_count_ips_tmp = 0; // Dénombrements temporaires transférés si traductions pas révoquées
 46 
 47 unsigned int counter_additions_contributors = 0; // Nombre d’ajouts de traduction(s) par des utilisateurs… inscrits
 48 unsigned int counter_additions_ips = 0;          //         "           "           "           "        … non inscrits
 49 unsigned int revoked_trans_contribs = 0; // Nombre de traductions révoquées… de contributeurs inscrits
 50 unsigned int revoked_trans_ips = 0;      //      "          "        "     … de contributeurs non inscrits
 51 
 52 string remember; // diffs à mettre de côté car résumé d'édition tronqué
 53 string remember_tmp; // Variable temporaire (transférée si traductions correspondantes non révoquées)
 54 int cnt_rem = 0; // Nombre de diffs à analyser
 55 int cnt_rem_tmp = 0;
 56 /**************/
 57 
 58 // Transfert d’une map (temporaire) à une autre (dénombrant les résultats finaux)
 59 void transfer_between_maps(map<string, int> &map_source, map<string, int> &map_dest) {
 60     for(auto const &item : map_source) {
 61         if (map_dest[item.first]) {
 62             map_dest[item.first] += item.second;
 63         } else {
 64             map_dest[item.first] = item.second;
 65         }
 66     }
 67 }
 68 // Vide les variables temporaires après un transfert
 69 void empty_all_tmp_vars() {
 70     stats_langs_tmp = {};
 71     stats_dates_tmp = {};
 72     stats_contribs_tmp = {};
 73     stats_ips_tmp = {};
 74     total_count_contribs_tmp = 0;
 75     total_count_ips_tmp = 0;
 76     cnt_rem_tmp = 0;
 77     remember_tmp = "";
 78 }
 79 // Transfert de toutes les variables temporaires vers leurs versions persistantes
 80 void transfer_all_tmp_vars() {
 81     transfer_between_maps(stats_langs_tmp, stats_langs);
 82     transfer_between_maps(stats_dates_tmp, stats_dates);
 83     transfer_between_maps(stats_contribs_tmp, stats_contribs);
 84     transfer_between_maps(stats_ips_tmp, stats_ips);
 85     total_count_contribs += total_count_contribs_tmp;
 86     total_count_ips += total_count_ips_tmp;
 87     counter_trans += (total_count_contribs_tmp + total_count_ips_tmp);
 88     remember += remember_tmp;
 89     cnt_rem += cnt_rem_tmp;
 90     empty_all_tmp_vars();
 91 }
 92 
 93 // Fonction effectuant le traitement de la Partie 1
 94 void count_trads_added_with_te()
 95 {
 96     /**** FICHIERS D'ENTREE ET DE SORTIE ****/
 97     ifstream infile(input_file_history, ifstream::in);
 98     if (!infile) {
 99         cout << "Le dump n'est pas situe a " << input_file_history << endl;
100         return;
101     }
102     ofstream out_res(output_file_res, ofstream::out);
103     if (!out_res) {
104         cout << "Probleme avec le fichier de sortie global " << endl;
105         return;
106     }
107     ofstream out_langs(output_file_langs, ofstream::out);
108     if (!out_langs) {
109         cout << "Probleme avec le fichier de sortie sur les langues" << endl;
110         return;
111     }
112     ofstream out_dates(output_file_dates, ofstream::out);
113     if (!out_dates) {
114         cout << "Probleme avec le fichier de sortie sur les dates" << endl;
115         return;
116     }
117     ofstream out_contribs(output_file_contribs, ofstream::out);
118     if (!out_contribs) {
119         cout << "Probleme avec le fichier de sortie sur les contributeurs" << endl;
120         return;
121     }
122     ofstream out_ips(output_file_ips, ofstream::out);
123     if (!out_ips) {
124         cout << "Probleme avec le fichier de sortie sur les ips" << endl;
125         return;
126     }
127     ofstream out_diff(output_file_diffs, ofstream::out);
128     if (!out_diff) {
129         cout << "Probleme avec le fichier de sortie de diffs" << endl;
130         return;
131     }
132     /***************************************/
133 
134     /*** PARAMETRES ***/
135     // Pour s’assurer que la révision en cours est bien dans la période d’analyse désirée (et définie plus haut)
136     int from_year = stoi(from_date.substr(0, 4));
137     int from_month = stoi(from_date.substr(5, 2));
138     int to_year = stoi(to_date.substr(0, 4));
139     int to_month = stoi(to_date.substr(5, 2));
140     /******************/
141 
142     /**** REVISION EN COURS ****/
143     bool pending_rev = false;
144     bool additions_to_count = false; // Ajouts à décompter ? Variable vidée après s'être assurée que les ajouts décomptés n'ont pas été annulés
145     bool ip_additions_to_count = 0;      // Nombre d'ajouts d'IPs à décompter comme révoqués si tel est le cas
146     bool contrib_additions_to_count = 0; //      "      "   de contributeurs    "      "         "      "
147     bool id_read = false;
148     string title, rev_id, prev_rev_id; // Informations utiles pour les ajouts dont on veut analyser les diffs
149     int ns = -1;
150     string tstamp; // Timestamp de la révision en cours d'analyse
151     int year, month;
152     string contributor; // Nom du contributeur ou adresse IP
153     bool is_ip = false; // Si le contributeur est enregistré ou non
154     string comment = ""; // résumé d’édition en cours d’analyse
155     string lang; // langue de la traduction en cours d’analyse (langues des traductions mentionnées dans le résumé d'édition)
156 
157     string line; // Ligne du dump actuellement en cours d’analyse
158     size_t pos1, pos2, pos3;
159 
160     bool entry_with_trans = false;
161     /***************************/
162 
163     while (getline(infile, line)) {
164         if (line.find("<title>") != string::npos) {
165             pos1 = line.find("<title>");
166             pos2 = line.find("</title>");
167             title= line.substr(pos1+7, pos2-pos1-7);
168             counter_pages++;
169         }
170         // Si pas main, on passe
171         if (line.find("<ns>") != string::npos) {
172             pos1 = line.find("<ns>");
173             pos2 = line.find("</ns>");
174             ns = stoi(line.substr(pos1+4, pos2-pos1-4));
175         }
176         if (ns != 0) {
177             continue;
178         }
179         if (line.find("<revision>") != string::npos) {
180             pending_rev = true;
181             id_read = false;
182         }
183         if (pending_rev) {
184             if (!id_read && line.find("<id>") != string::npos) {
185                 pos1 = line.find("<id>");
186                 pos2 = line.find("</id>");
187                 prev_rev_id = rev_id;
188                 rev_id = line.substr(pos1+4, pos2-pos1-4);
189                 id_read = true;
190             }
191             // On verifie que l'édition est faite dans la période choisie
192             if (line.find("<timestamp>") != string::npos) {
193                 pos1 = line.find("<timestamp>");
194                 pos2 = line.find("</timestamp>");
195                 tstamp = line.substr(pos1+11, 7);
196                 year = stoi( tstamp.substr(0, 4) );
197                 month = stoi( tstamp.substr(5, 2) );
198                 if (year < from_year || (year == from_year && month < from_month) ||
199                     year > to_year || (year == to_year && month >= to_month)
200                     ) {
201                     pending_rev = false;
202                     if (additions_to_count) {
203                         transfer_all_tmp_vars();
204                         additions_to_count = false;
205                     }
206                     continue;
207                 }
208             }
209             // Pour un utilisateur on aura dans le fichier XML <username></username>, tandis que pour une IP on aura <ip></ip>
210             if (line.find("<username>") != string::npos) {
211                 pos1 = line.find("<username>");
212                 pos2 = line.find("</username>");
213                 contributor = line.substr(pos1+10, pos2-pos1-10);
214                 is_ip = false;
215             }
216             if (line.find("<ip>") != string::npos) {
217                 pos1 = line.find("<ip>");
218                 pos2 = line.find("</ip>");
219                 contributor = line.substr(pos1+4, pos2-pos1-4);
220                 is_ip = true;
221             }
222 
223             // On analyse tous les commentaires des pages de l’espace principal à la recherche de
224             // résumés d’édition correspondant à des ajouts traductions avec Translation editor
225             if (line.find("<comment>") != string::npos) {
226                 pos1 = line.find("<comment>");
227                 pos2 = line.find("</comment>");
228                 comment = line.substr(pos1+9, pos2-pos1-9);
229                 if (comment.substr(0, 15) == "Traductions : +") { // cas 1 : traductions ajoutées avec l'outil : on met de côté
230                                                                 // avant de s’assurer qu'elles n'ont pas été révoquées
231                     additions_to_count = true;
232                     counter_additions++;
233                     if (is_ip) {
234                         ip_additions_to_count++;
235                         counter_additions_ips++;
236                     } else {
237                         contrib_additions_to_count++;
238                         counter_additions_contributors++;
239                     }
240                     // Si le résumé d’édition est tronqué, on garde les infos de côté pour analyser + tard les diffs
241                     if (comment.find("(assist") == string::npos) {
242                         remember_tmp += "title=" + title + ";prev_rev_id=" + prev_rev_id + ";rev_id=" + rev_id +
243                                     ";contrib=" + contributor + ";is_ip=" + (is_ip ? "true" : "false") +
244                                     ";date=" + tstamp + "\n";
245                         cnt_rem_tmp++;
246                         continue;
247                     }
248                     entry_with_trans = true;
249                     pos3 = 11;
250                     do {
251                         pos3 += 4;
252                         lang = comment.substr(pos3, comment.find(" :", pos3) - pos3);
253                         if (lang == "en-tête") continue; // On ignore les modifications d’en-tête
254                         if (stats_langs_tmp.count(lang)) {
255                             stats_langs_tmp[lang]++;
256                         } else {
257                             stats_langs_tmp[lang] = 1;
258                         }
259                         if (stats_dates_tmp.count(tstamp)) {
260                             stats_dates_tmp[tstamp]++;
261                         } else {
262                             stats_dates_tmp[tstamp] = 1;
263                         }
264                         if (!is_ip) {
265                             if (stats_contribs_tmp.count(contributor)) {
266                                 stats_contribs_tmp[contributor]++;
267                             } else {
268                                 stats_contribs_tmp[contributor] = 1;
269                             }
270                             total_count_contribs_tmp++;
271                         } else {
272                             if (stats_ips_tmp.count(contributor)) {
273                                 stats_ips_tmp[contributor]++;
274                             } else {
275                                 stats_ips_tmp[contributor] = 1;
276                             }
277                             total_count_ips_tmp++;
278                         }
279                         pos3 = comment.find(" ; +", pos3);
280                     } while (pos3 != string::npos); // On répète la boucle pour toutes les traductions présentes dans le résumé d’édition
281                 } else if ((comment.substr(0, 15) == "R\u00E9vocation des"
282                             || comment.substr(0, 14) == "Annulation des")
283                             && additions_to_count
284                            ) { // cas 2 : révocation d'ajout(s) de traduction : on ignore
285                     revoked_contributor_additions += contrib_additions_to_count;
286                     revoked_ip_additions += ip_additions_to_count;
287                     empty_all_tmp_vars();
288                     additions_to_count = false;
289                     ip_additions_to_count = 0;
290                     contrib_additions_to_count = 0;
291                     entry_with_trans = false;
292                 } else if (additions_to_count) { // cas 3 : non-révocation après ajout de traductions :
293                                                  // on compte les trads précédemment analysées
294                     transfer_all_tmp_vars();
295                     additions_to_count = false;
296                     ip_additions_to_count = 0;
297                     contrib_additions_to_count = 0;
298                 }
299             }
300             if (line.find("</revision>") != string::npos) {
301                 pending_rev = false;
302             }
303         }
304         if (line.find("</title>") != string::npos) {
305             if (additions_to_count) { // cas ou la derniere modif d'une page est un ajout de traduction (non révoqué) :
306                                     // on prend alors en compte les ajouts de trads
307                 transfer_all_tmp_vars();
308                 additions_to_count = false;
309                 ip_additions_to_count = 0;
310                 contrib_additions_to_count = 0;
311             }
312             if (entry_with_trans) {
313                 counter_pages_with_trans++;
314                 // Tous les 100 pages modifiées avec Translation editor, on affiche des stats récapitulatives
315                 if (counter_pages_with_trans % 100 == 0) {
316                     cout << counter_pages_with_trans << " pages modifiees sur " <<
317                         counter_pages << " scannees - " << counter_trans << " trads ajoutees avec TE (et "
318                                         << revoked_contributor_additions + revoked_ip_additions << "/"
319                                         << counter_additions << " ajouts revoques) contribs: "
320                                         << counter_additions_contributors << " ; IPs: " << counter_additions_ips << endl;
321                 }
322                 // Tous les 10 000 pages modifiées avec Translation editor, on affiche les stats accumulés jusqu’alors
323                 // (pour le suivi et le repérage anticipé d’aberrations dans les résultats)
324                 if (counter_pages_with_trans % 10000 == 0) {
325                     for (const auto &p : stats_langs) {
326                         std::cout << p.first << " : " << p.second << '\n';
327                     }
328                     for (const auto &p : stats_dates) {
329                         std::cout << p.first << " : " << p.second << '\n';
330                     }
331                     for (const auto &p : stats_contribs) {
332                         std::cout << p.first << " : " << p.second << '\n';
333                     }
334                     std::cout << "Total contributeurs : " << total_count_contribs << '\n';
335                     std::cout << "Total IPs : " << total_count_ips << '\n';
336                     cout << cnt_rem << " diffs a analyser en +" << endl;
337                 }
338             }
339             entry_with_trans = false;
340         }
341     }
342 
343     cout << cnt_rem << " diffs a analyser en +" << endl;
344     cout << counter_pages_with_trans << " pages modifiees avec translation_editor (" << counter_trans << " traductions)" << endl;
345 
346     infile.close();
347 
348     out_res  << "* Traductions ajoutées : " << counter_trans << endl;
349     out_res << "* Ajouts avec l'outil : " << counter_additions << " (contributeurs : " << counter_additions_contributors
350             << " ; IPs : " << counter_additions_ips << ")" << endl;
351     out_res << "* Pages modifiées avec translation_editor : " << counter_pages_with_trans << endl;
352     out_res << "* Traductions ajoutées par des utilisateurs inscrits : " << total_count_contribs << endl;
353     out_res << "* Traductions ajoutées par des utilisateurs non inscrits : " << total_count_ips << endl;
354     out_res << "* Ajouts de contributeurs révoqués : " << revoked_contributor_additions << endl;
355     out_res << "* Ajouts d'IPs révoqués : " << revoked_ip_additions << endl;
356 
357     // Ecriture des resultats dans le fichier de sortie
358     for (const auto &p : stats_langs) {
359         out_langs << p.first << " : " << p.second << endl;
360     }
361     for (const auto &p : stats_dates) {
362         out_dates << p.first << " : " << p.second << endl;
363     }
364     for (const auto &p : stats_contribs) {
365         out_contribs << p.first << " : " << p.second << endl;
366     }
367     for (const auto &p : stats_ips) {
368         out_ips << p.first << " : " << p.second << endl;
369     }
370 
371     out_res.close();
372     out_langs.close();
373     out_dates.close();
374     out_contribs.close();
375     out_ips.close();
376 
377     // Entrees restantes à traiter en analysant les diffs => dans un fichier a part
378     out_diff << remember;
379     out_diff.close();
380 }
381 
382 int main()
383 {
384     count_trads_added_with_te();
385 
386     cout << "End of processing!" << endl;
387     return 0;
388 }

Ce script a pris 25 à 30 minutes à s’exécuter (Windows 10, processeur quadricore (2.3GHz), 8 Go de RAM).

Partie 2

  1 # -*- coding: utf-8 -*-
  2 import re
  3 import time
  4 import pywikibot
  5 import languages_list # Copie de [[MediaWiki:Gadget-translation editor.js/langues.json]] (la liste étant préfixée de languages =)
  6 import ast
  7 from subprocess import call
  8 
  9 site = pywikibot.Site()
 10 
 11 languages = languages_list.languages
 12 
 13 // Dossier qui contient les résultats générés précédemment et notamment le fichier
 14 // stats-trads-diffs-to-check.txt qui contient les infos sur les diffs à analyser.
 15 folder = "C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\"
 16 file_langs = 'stats-trads-langs-after-diffs.txt'
 17 file_dates = 'stats-trads-dates-after-diffs.txt'
 18 file_contributors = 'stats-trads-contributors-after-diffs.txt'
 19 file_ips = 'stats-trads-ips-after-diffs.txt'
 20 
 21 def sort_dict_by_value(d):
 22   return sorted(d.items(), key=lambda x: x[1])
 23   
 24 def first_ten_items_dict(d):
 25   return first_n_items_dict(d, 10)
 26   
 27 def first_n_items_dict(d, n, first_index):
 28   '''Returns a list of n top-valued items from index first_index'''
 29   return sorted(d.items(), key=lambda x: x[1], reverse=True)[first_index:first_index+n]
 30   
 31 def dict_from_file(file, folder=None):
 32   with open(folder+file, encoding='utf-8') as f:
 33     dict_results = ast.literal_eval(f.read())
 34   return dict_results
 35   
 36 def generate_graph(results, file_graph='graph_template.txt', cat='langs', label='lang'):
 37   '''
 38   @param results: an object containing the results
 39   @type results: str representing a file,
 40                 or dict or list of tuples
 41   '''
 42   input_file = folder + file_graph
 43   output_file = folder + 'graph-{}.txt'.format(cat)
 44   if isinstance(results, str):
 45     with open(folder+results) as f:
 46       results = ast.literal_eval(f.read())
 47   with open(input_file, 'r', encoding='utf-8') as f:
 48     content = f.read()
 49     specific_results = ''
 50     if isinstance(results, dict):
 51       for k in results:
 52         specific_results += '        {{"{}": "{}", "amount": {} }},\n'.format(label, k, str(results[k]))
 53     elif isinstance(results, list):
 54       for item in results:
 55         specific_results += '        {{"{}": "{}", "amount": {} }},\n'.format(label, item[0], str(item[1]))
 56     else:
 57       print('results objects should be either a string, a list or a dict, but it is a {}'.format(type(results))); return
 58     specific_results = specific_results[:-2] # removing last ",\n"
 59     content = content.replace('__TO_REPLACE__', specific_results)
 60     content = content.replace('__LABEL__', label)
 61     oblique_labels = '__OBLIQUE_LABELS__'
 62     if cat == 'dates':
 63         content = content.replace(oblique_labels, ', "properties": { "labels": {"angle": {"value": -45}, "dx": {"value": -20} } } ')
 64     else:
 65       content = content.replace(oblique_labels, '')
 66   with open(output_file, 'w', encoding='utf-8') as f:
 67     f.write(content)
 68   print('Graph saved to {}'.format(output_file))
 69   choice = pywikibot.inputChoice(u"Open file?", ["yes", "no"], ["y", "n"], default="n")
 70   if choice == 'y':
 71     call(["notepad", output_file])
 72     
 73 def generate_langs_graph(nbLangs=15, first_index=0, filename_var='langs'):
 74     d = dict_from_file(file_langs)
 75     data = first_n_items_dict(d, nbLangs, first_index)
 76     generate_graph(data, file_graph='graph_template.txt', cat=filename_var, label='lang')
 77     
 78 def generate_dates_graph():
 79     generate_graph(file_dates, file_graph='graph_template.txt', cat='dates', label='date')
 80     
 81 def results_to_wikitable(dict, header1='Langues', header2='Traductions ajoutées'):
 82   wikitext = '{{| class="wikitable sortable mw-collapsible"\n! {} !! {}'.format(header1, header2)
 83   for k in dict:
 84     wikitext += '\n|-\n| {} || {}'.format(k, str(dict[k]))
 85   wikitext += '\n|}'
 86   return wikitext
 87   
 88 def raw_results_to_wikitable(filename, dest=None, header1='Langues', header2='Traductions ajoutées'):
 89   if dest is None:
 90     print('A destination file must be provided')
 91     return
 92   input_file = folder + filename
 93   output_file = folder + dest
 94   with open(input_file, encoding='utf-8') as f:
 95     for line in f:
 96       dict_results = ast.literal_eval(line)
 97       break
 98   with open(output_file, 'w+', encoding='utf-8') as f:
 99     f.write(results_to_wikitable(dict_results, header1, header2))
100   print('Wikitable saved to {}'.format(output_file))
101   choice = pywikibot.inputChoice(u"Open file?", ["yes", "no"], ["y", "n"], default="n")
102   if choice == 'y':
103     call(["notepad", output_file])
104 
105 def count_trads_per_diff():
106   '''
107   Génère des statistiques d'ajout de trads lorsque les résumés d'édition sont tronqués
108   et necessitent une analyse des diffs
109   '''
110   stats_langs = {}
111   stats_dates = {}
112   stats_contribs = {}
113   stats_ips = {}
114   
115   cpt = 0
116   cpt_trads = 0
117   count_contributors = 0
118   count_ips = 0
119 
120   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-langs.txt", encoding="utf-8") as f:
121     for line in f:
122       match = re.search("(.+) : (\d+)", line)
123       if match is None:
124         continue
125       stats_langs[match[1]] = int(match[2])
126   
127   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-dates.txt", encoding="utf-8") as f:
128     for line in f:
129       match = re.search("(.+) : (\d+)", line)
130       stats_dates[match[1]] = int(match[2])
131   
132   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-contributors.txt", encoding="utf-8") as f:
133     for line in f:
134       match = re.search("(.+) : (\d+)", line)
135       if match is None:
136         continue
137       stats_contribs[match[1]] = int(match[2])
138       
139   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-ips.txt", encoding="utf-8") as f:
140     for line in f:
141       match = re.search("(.+) : (\d+)", line)
142       if match is None:
143         continue
144       stats_ips[match[1]] = int(match[2])
145       
146   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-diffs-to-check.txt", encoding="utf-8") as f:
147     for line in f:
148       # line = "title=lire;prev_rev_id=18770633;rev_id=18908602;contrib=Test;is_ip=false;date=2015-01"
149       # title = line.split(';')[0].split('=')[1]
150       from_rev = line.split(';')[1].split('=')[1]
151       to_rev = line.split(';')[2].split('=')[1]
152       contrib = line.split(';')[3].split('=')[1]
153       if line.split(';')[4].split('=')[1] == 'true':
154         is_ip = True
155       else:
156         is_ip = False
157       date = line.split(';')[5].split('=')[1].strip()
158       diff_html = site.compare(old=int(from_rev), diff=int(to_rev))
159       trads_added = re.findall('<td class="diff-addedline"><div>(?:.+<ins class="diffchange diffchange-inline">)?(.+)(?:</ins>)?</div></td>', diff_html)
160       codes = []
161       for t in trads_added:
162         codes += re.findall('{{trad[+-]{0,2}\|([^|]+)\|', t)
163       for code in codes:
164         if code in languages:
165           lang_name = languages[code]
166         elif code in languages['redirects']:
167           lang_name = languages[languages['redirects'][code]]
168         else:
169           # Les codes langue non répertoriés dans la liste des langues sont ignorés
170           # (1 seul cas le 1/09/2018, un ajout en zh-tc, code supprimé depuis)
171           print('CODE ' + code + ' NOT FOUND (and translation ignored) - ' + line.strip())
172           continue
173         if lang_name:
174           # sys.stdout.write(',' + lang_name)
175           if lang_name in stats_langs:
176             stats_langs[lang_name] += 1
177           else:
178             stats_langs[lang_name] = 1
179         if is_ip:
180           if contrib in stats_ips:
181             stats_ips[contrib] += 1
182           else:
183             stats_ips[contrib] = 1
184           count_ips += 1
185         else:
186           if contrib in stats_contribs:
187             stats_contribs[contrib] += 1
188           else:
189             stats_contribs[contrib] = 1
190           count_contributors += 1
191         if date in stats_dates:
192           stats_dates[date] += 1
193         else:
194           stats_dates[date] = 1
195         cpt_trads += 1
196       cpt += 1
197       if cpt % 100 == 0:
198         print(str(cpt) + " diffs traites (" + str(cpt_trads) + " traductions)")
199   print(stats_dates)
200   print(stats_langs)
201   print(stats_contribs)
202   print(str(stats_ips)[:1000] + '...') // Liste dIPs trop longue, on la tronque
203   
204   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-res-after-diffs.txt", "w+", encoding="utf-8") as f:  
205     res = "Résultats des stats sur les résumés d'édition tronqués (ajout de traductions par lots) :\n"
206     res += "Traductions ajoutées : " + str(cpt_trads) + "\n"
207     res += "Traductions ajoutées par des utilisateurs inscrits : " + str(count_contributors) + "\n"
208     res += "Traductions ajoutées par des utilisateurs non inscrits : " + str(count_ips) + "\n"
209     f.write(res)
210     
211   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-langs-after-diffs.txt", "w+", encoding="utf-8") as f:
212     f.write(str(stats_langs))
213   
214   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-dates-after-diffs.txt", "w+", encoding="utf-8") as f:
215     f.write(str(stats_dates))
216   
217   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-contributors-after-diffs.txt", "w+", encoding="utf-8") as f:
218     f.write(str(stats_contribs))
219       
220   with open("C:\\Users\\automatik\\Wiktionnaire\\Stats_translation_editor\\stats-trads-ips-after-diffs.txt", "w+", encoding="utf-8") as f:
221     f.write(str(stats_ips))
222     
223 if __name__ == '__main__':
224     start_time = time.time()
225     count_trads_per_diff()
226     print("--- %s seconds ---" % (time.time() - start_time))
227     # Les fonctions suivantes créent les fichiers et proposent à l’utilisateur de l’ouvrir (avec le programme notepad, voir plus haut)
228     # afin de pouvoir directement copier-coller le résultat sur le wiki.
229     raw_results_to_wikitable(file_dates, dest='wikitable-dates.txt', header1='Mois', header2='Traductions ajoutées')
230     raw_results_to_wikitable(file_langs, dest='wikitable-langs.txt', header1='Langue', header2='Traductions ajoutées')
231     raw_results_to_wikitable(file_ips, dest='wikitable-ips.txt', header1='Utilisateur non enregistré', header2='Traductions ajoutées')
232     raw_results_to_wikitable(file_contributors, dest='wikitable-contributors.txt', header1='Utilisateur enregistré', header2='Traductions ajoutées')
233     generate_langs_graph()
234     generate_langs_graph(15, 15, filename_var='langs2')
235     generate_dates_graph()

Ce script a pris moins de 10 minutes à s’exécuter (Windows 10, processeur quadricore (2.3GHz), 8 Go de RAM)

Il fait référence au fichier graph_template.txt suivant pour la génération des graphiques :

{{#tag:graph|
{
  "version": 4,
  "width": 1000,
  "height": 200,
  "padding": {"top": 20, "left": 65, "bottom": 60, "right": 10},
 
  "data": [
    {
      "name": "table",
      "values": [
__TO_REPLACE__
      ]
    }
  ],
 
  "signals": [
    {
      "name": "tooltip",
      "init": {},
      "streams": [
        {"type": "rect:mouseover", "expr": "datum"},
        {"type": "rect:mouseout", "expr": "{}"}
      ]
    }
  ],
 
  "predicates": [
    {
      "name": "tooltip", "type": "==",
      "operands": [{"signal": "tooltip._id"}, {"arg": "id"}]
    }
  ],
 
  "scales": [
    { "name": "xscale", "type": "ordinal", "range": "width",
      "domain": {"data": "table", "field": "__LABEL__"} },
    { "name": "yscale", "type": "linear", "range": "height",
      "domain": {"data": "table", "field": "amount"} }
  ],
 
  "axes": [
    { "type": "x", "scale": "xscale"__OBLIQUE_LABELS__},
    { "type": "y", "scale": "yscale" }
  ],
 
  "marks": [
    {
      "type": "rect",
      "from": {"data":"table"},
      "properties": {
        "enter": {
          "x": {"scale": "xscale", "field": "__LABEL__"},
          "width": {"scale": "xscale", "band": true, "offset": -1},
          "y": {"scale": "yscale", "field": "amount"},
          "y2": {"field": {"group": "height"} }
        },
        "update": { "fill": {"value": "steelblue"} },
        "hover": { "fill": {"value": "red"} }
      }
    },
    {
      "type": "text",
      "properties": {
        "enter": {
          "align": {"value": "center"},
          "fill": {"value": "#333"}
        },
        "update": {
          "x": {"scale": "xscale", "signal": "tooltip.__LABEL__"},
          "dx": {"scale": "xscale", "band": true, "mult": 0.5},
          "y": {"scale": "yscale", "signal": "tooltip.amount", "offset": -5},
          "text": {"signal": "tooltip.amount"},
          "fillOpacity": {
            "rule": [
              {
                "predicate": {"name": "tooltip", "id": {"value": null} },
                "value": 0
              },
              {"value": 1}
            ]
          }
        }
      }
    }
  ]
}
| mode=interactive }}