Chronobot
Chronobot est un bot programmé en langage Python et qui agit sur des pages Wikipast. Il permet de regrouper tous les événements qui se sont passés en une année dans une même page
Fonctionnement
Chronobot va d'abord effectuer un scan sur l'ensemble des pages biographiques crées par les élèves du cours "Humanités Digitales" (2017). Il effectue ensuite un scan sur tous les événements relevés dans la page et en stocke la date (sous forme AAAA.MM.JJ) et la description dans une liste. Une fois cette liste en main, il crée une page pour chaque jour différent. Pour des raisons pratiques, il est plus logique de n'avoir qu'une seule page par année avec tous les événements relevés durant cette année classé chronologiquement. Pour ce faire, au lieu d'agir directement sur l'url de la page, Chronobot redirige chaque lien d'un jour vers l'url de l'année correspondante. Enfin, il y écrit l'événement dans l'ordre chronologique.La gestion des doublons est résolue ultérieurement.
Généralités
- Concepteurs : Arnau Albà Jacas, Sonia Bouchiba, Jonathan Charrière, Sébastien Morel, Aurélien Verdier.
- UserName : ChronoBOT
Liste des fonctions
Fonctions principales
- select_page.py parcourt les différentes pages sur une séléection précise de Users (ceux du cours SHS) et en retourne les pages crées sous formes de chaînes brutes (String). Les images (.png) sont ôtées ainsi que
- recherchedates.py prend en argument une page donnée (string) et en sort une liste année/événement
- modify_links.py modifie toutes les pages des dates pour qu'elles soient redirigées vers la page de l'année.
- create_new_site.py crée une page Wikipast avec le contenu donné
- place_evenement.py reçoit les pages dans lesquelles elle doit écrire ainsi que les événements sous forme de chaîne. Elle a deux rôles majeures. D'une part, elle écrit (tout en s'adpatant aux différents formats de date) la date et l'événement dans la page. Simultanément, elle trie les événements dans l'ordre chronologique.
- event_not_in_page.py gère les doublons en comparant deux strings-évenements caractère par caractère. Il considère ainsi deux évenements identiques s'ils contiennent les mêmes suites de caractères. Il ne tient pas compte des virgules (,), points (.) et espaces ( ) s'ils sont différents dans les deux strings.
Fonctions secondaires
- retrieve_content.py prend en input le nom d'une page et retourne tout le texte contenu en syntaxe wiki.
- split_date.py est utilisée par place_evenement.py.
Algorithme
L'algo général se trouve dans la fonction main.py. Il utilise la fonction main_one_page.py aui contient l'agorithme général.
Code source
main_one_page.py et main.py
main_one_page.py
from create_new_site import create_new_site
from create_site import create_site
from modify_links import modify_links
from place_evenement import place_evenement
from event_not_in_page import event_not_in_page
from recherchedates2 import recherchedates2
#ajouter ici les autres fonctions dont on aura besoin
def main_one_page(page):
page=page.replace(" ","_")
#modifie les dates pour rediger vers une année
modify_links(page)
#renvoie un array de deux colonnes: une colonne de dates et une colonne d'evenements
elements=recherchedates2(page)
for element in elements:
evenement = element[1]
date = element[0]
annee = date[:4]
#cree le site si il existe pas encore
create_site(annee)
if event_not_in_page(annee,evenement):
content=place_evenement(date,evenement)
create_new_site(annee,content)
main.py
'''
Alors ça c'est le main. Si on le lance ça crée
toutes las pages année normalement.
'''
from select_page import select_page
from main_one_page import main_one_page
import datetime
from order_all import order_all
page_list=select_page()
i=0
years_modified=[]
for page in page_list:
print('Je suis en train de faire la page: '+page)
print("J'ai fait un "+str(i/len(page_list)*100)+'%')
years_modified=years_modified+main_one_page(page)
i=i+1
print("j'ai fini, il me reste qu'à tout ordonner")
#ici il ordonne toutes les pages, parce que la fonction place_evenement
#met les evenements juste à la fin de la page
order_all(years_modified)
now = datetime.datetime.now()
year=str(now.year)
month=str(now.month)
day=str(now.day)
hour=str(now.hour)
minute=str(now.minute)
if len(month)==1:
month='0'+month
if len(day)==1:
day='0'+day
if len(hour)==1:
hour='0'+hour
if len(minute)==1:
minute='0'+minute
fichier=open('lastdate.txt','w')
fichier.write(year+'-'+month+'-'+day+'T'+hour+':'+minute+':00Z')
fichier.close()
select_pages.py
Code source
# -*- coding: utf-8 -*-
#return a list of str containing all the name of wikipast page, without the one beginin with "Fichier:" or the one beginin with 4 number (ex:"1945")
#only pages changed by user in the "whiteliste.txt" and done AFTER the date write in "lastdate.txt" are given
#to change the "lastdate.txt", please put those line where needed :
#if needed, it strated with "2017-02-05T16:00:00Z"
#import datetime
#now = datetime.datetime.now()
#year=str(now.year)
#month=str(now.month)
#day=str(now.day)
#hour=str(now.hour)
#minute=str(now.minute)
#if len(month)==1:
# month='0'+month
#if len(day)==1:
# day='0'+day
#if len(hour)==1:
# hour='0'+hour
#if len(minute)==1:
# minute='0'+minute
#
#fichier=open('lastdate.txt','w')
#fichier.write(year+'-'+month+'-'+day+'T'+hour+':'+minute+':00z')
#fichier.close()
Whitelist
Frederickaplan Maud Vbuntinx Testbot SparqlBot IB SourceBot PageUpdaterBot Orthobot BioPathBot ChronoBOT Amonbaro AntoineL AntoniasBanderos Arnau Arnaudpannatier Aureliver Brunowicht Burgerpop Cedricviaccoz Christophe Claudioloureiro Ghislain Gregoire3245 Hirtg Houssm Icebaker JenniCin JiggyQ JulienB Kl Kperrard Leandro Kieliger Marcus Martin MatteoGiorla Mireille Mj2905 Musluoglucem Nacho Nameless Nawel O'showa PA Qantik QuentinB Raphael.barman Roblan11 Romain Fournier Sbaaa Snus Sonia Tboyer Thierry Titi Vlaedr Wanda
recherchedates2.py
import requests
from bs4 import BeautifulSoup
from retrieve_content import retrieve_content
def recherchedates2(page_name):
x=str(retrieve_content(page_name))
x=x.split("\n")
dates=[]
for i in x:
d_start=i.find("[[")
if (d_start!=-1) and i[d_start+2:d_start+6].isnumeric():
d_start=d_start+2
d_end=i[d_start:].find("]]")+d_start
date=i[d_start:d_end]
d_start=d_start-2
even='*'+i[d_start:]
dates.append([date,even])
return(dates)
modify_links.py
'''
cette fonction prend en input le nom d'une page.
Pour toutes les dates de type [[AAAA/MM/JJ]], elle crée le
lien de redirection vers la page de l'année.
Pour les dates de type [[AAAA]] elle ne fait
rien puisqu'elles sont déjà un lien vers l'année
'''
import requests
from bs4 import BeautifulSoup
from retrieve_content import retrieve_content
months=["Janvier","F.C3.A9vrier","Mars","Avril","Mai","Juin","Juillet","Ao.C3.BBt","Septembre","Octobre","Novembre","D.C3.A9cembre"]
def modify_links(page_name):
###############
###############
#retrieve dates
text=retrieve_content(page_name)
text=text.split("\n")
dates=[]
for line in text:
d_start=line.find("[[")
if (d_start!=-1) and line[d_start+2:d_start+6].isnumeric():
d_start=d_start+2
d_end=line[d_start:].find("]]")+d_start
date=line[d_start:d_end]
if len(date)!=4:
dates.append(date)
###############
###############
#create pages with redirection code
user = "ChronoBOT"
passw = "sajas2017"
baseurl='http://wikipast.epfl.ch/wikipast/'
summary='ChronoBOT page creation'
# Login request
payload={'action':'query','format':'json','utf8':'','meta':'tokens','type':'login'}
r1=requests.post(baseurl + 'api.php', data=payload)
#login confirm
login_token=r1.json()['query']['tokens']['logintoken']
payload={'action':'login','format':'json','utf8':'','lgname':user,'lgpassword':passw,'lgtoken':login_token}
r2=requests.post(baseurl + 'api.php', data=payload, cookies=r1.cookies)
#get edit token2
params3='?format=json&action=query&meta=tokens&continue='
r3=requests.get(baseurl + 'api.php' + params3, cookies=r2.cookies)
edit_token=r3.json()['query']['tokens']['csrftoken']
edit_cookie=r2.cookies.copy()
edit_cookie.update(r3.cookies)
for date in dates:
year=date[:4]
if(int(date[5:7])<13):
month=months[int(date[5:7])-1]
content="#REDIRECT [["+year+"#"+month+"]]"
else:
content="#REDIRECT [["+year+"]]"
# save action
payload={'action':'edit','assert':'user','format':'json','utf8':'','text':content,'summary':summary,'title':date,'token':edit_token}
r4=requests.post(baseurl+'api.php',data=payload,cookies=edit_cookie)
create_site.py
'''
cette fonction prend en input une année, et crée un site en blanc
pour cette année
Si le site existe déjà, il ne fait rien
'''
import requests
def create_site(year):
#check if year is a four digit number
year=str(year)
if(len(year)!=4)or not(year.isnumeric()):
return
user = "ChronoBOT"
passw = "sajas2017"
baseurl='http://wikipast.epfl.ch/wikipast/'
summary='ChronoBOT page creation'
#check if page already exists
if(requests.get('http://wikipast.epfl.ch/wikipast/index.php/'+year)).status_code!=404:
return
# Login request
payload={'action':'query','format':'json','utf8':'','meta':'tokens','type':'login'}
r1=requests.post(baseurl + 'api.php', data=payload)
#login confirm
login_token=r1.json()['query']['tokens']['logintoken']
payload={'action':'login','format':'json','utf8':'','lgname':user,'lgpassword':passw,'lgtoken':login_token}
r2=requests.post(baseurl + 'api.php', data=payload, cookies=r1.cookies)
#get edit token2
params3='?format=json&action=query&meta=tokens&continue='
r3=requests.get(baseurl + 'api.php' + params3, cookies=r2.cookies)
edit_token=r3.json()['query']['tokens']['csrftoken']
edit_cookie=r2.cookies.copy()
edit_cookie.update(r3.cookies)
content=""
# save action
payload={'action':'edit','assert':'user','format':'json','utf8':'','text':content,'summary':summary,'title':year,'token':edit_token}
r4=requests.post(baseurl+'api.php',data=payload,cookies=edit_cookie)
order.py et order_all.py
order.py
from smaller_than import smaller_than
def order(text):#ça ordonne un text
text1=text.split('\n')
#separate events from other useless lines in the page
newtext=[]
craptext=[]
for line in text1:
if (line[:3]=='*[[') and (line[3:7].isnumeric()):
newtext.append(line)
else:
craptext.append(line)
#case where page is empty, only has one line in it, or only has crap in it
if len(newtext)<2:
return text
#check for repeated lines
provisional=[]
for line in newtext:
if line not in provisional:
provisional.append(line)
newtext=provisional
ok=0
while(ok==0):
ok=1
for i in range(len(newtext)-1):
pos1 = newtext[i].find(']]')
date1 = newtext[i][3:pos1]
pos2 = newtext[i+1].find(']]')
date2 = newtext[i+1][3:pos2]
if not smaller_than(date1,date2):
ok=0
event=newtext[i]
newtext[i]=newtext[i+1]
newtext[i+1]=event
newtext='\n'.join(newtext)+'\n'+'\n'.join(craptext)
return newtext
order_all.py
from order import order
from retrieve_content import retrieve_content
from create_new_site import create_new_site
def order_all(years):#ça ordonne toutes les annees
done=[]
for year in years:
if year not in done:
print('I am ordering page '+year)
content=order(retrieve_content(year))
create_new_site(year,content)
done.append(year)
place_evenement.py
from retrieve_content import retrieve_content
def place_evenement(annee,evenement): #format annee: 'aaaa'
#format dans wikipast: '*[[aaaa.mm.jj]] (...)' seulement le début du format nous intéress
return retrieve_content(annee)+'\n'+evenement
event_not_in_page.py
'''
cette fonction prend en input une annee et un evenement.
Si cet evenement est déja dans la page de l'année il renvoie 0
Si il n'est pas encore dans la page de l'année il renvoie 1
'''
from retrieve_content import retrieve_content
def event_not_in_page(date,evenement):
text=retrieve_content(str(date)).replace(' ','')
text=text.replace(',','')
text=text.replace('.','')
pos=evenement.find(']]')
evenement=evenement[pos+2:].replace(' ','')
evenement=evenement.replace(',','')
evenement=evenement.replace('.','')
if evenement in text:
return 0
else:
return 1
retrieve_content.py
'''
cette fonction prend en input le nom d'une page
et retourne tout le text qu'il y a dedans, en code wiki
'''
import requests
from bs4 import BeautifulSoup
def retrieve_content(name):
baseurl='http://wikipast.epfl.ch/wikipast/'
result=requests.post(baseurl+'api.php?action=query&titles='+name+'&export&exportnowrap')
soup=BeautifulSoup(result.text, "html.parser")
code=''
if soup.findAll("text")[0]['bytes']=='0':
return code
for primitive in soup.findAll("text"):
code+=primitive.string
return code
split_date.py
def split_date(date): #format date: 'aaaa.mm.jj' /!\ la date doit être au format string
date=str(date); #on est jamais trop sûr
annee='';
mois='';
jour='';
for i in range(len(date)):
if(i<4): annee=annee+date[i];
if(i>4 and i<7): mois=mois+date[i];
if(i>7 and i<10): jour=jour+date[i];
return [annee, mois, jour];
smaller_than.py
def smaller_than(date1,date2):#need to be format aaaa.mm.dd
#a year of format aaaa is always smaller
#case aaaa
if(len(date1)==4):
return 1
if(len(date2)==4):
return 0
#case
m1=date1[5:7]
d1=date1[8:]
m2=date2[5:7]
d2=date2[8:]
if(m1<m2):
return 1
elif(m1>m2):
return 0
elif(m1==m2):
if(d1<=d2):
return 1
else:
return 0
Critiques et faiblesses du bot
- Optimisation du code (plusieurs minutes pour s'exéctuer sur toutes les pages.)
- Dates mal-écrites (par exemple inversion mois-jour pas prise en compte)
- Si une année a déjà été crée avant notre bot et une phrase a été ajoutée, par exemeple "Grande année" redigirigée vers "1995" comportait une phrase commançant par "Grande année". Pour résoudre ce problème, deux solutions sont enbisageables : la première, assez radicale, est d'effacer la ligne en question dans la page Wikpast. La seconde (celle choisie, plus douce) est
- Gestion des doublons : pourrait être amélioré dans la comparaison de deux événements similaires. Actuellement, seules deux événements écrits exactement pareils ne sont pas dédoublés (par la fonction event_not_in_page.py), mais on pourrait imaginer qu'un événement du type Mariage qui serait écrit comme "A se marie avec B" dans la page de A et "B ser marie avec B" dans la page de B, et qui serait donc relevé deux fois.
