Pendant les vacances d'été, j'ai pris l'habitude de réécouter des épisodes du podcast Rendez-vous avec X, tous archivés sur le site d'un passionné, http://rendezvousavecmrx.free.fr. Le site propose une liste alphabétique des différents épisodes sur une page web. Il se trouve que je préfère utiliser une application de podcast, et que pour pouvoir relire tous ces épisodes, il me faut un flux RSS. Je propose donc dans ce billet de créer un flux RSS à partir du site que je viens de citer.

Le lien pour le gestionnaire de podcast est le suivant : https://raw.githubusercontent.com/flothesof/posts/master/files/podcast_mr_x.xml

Un flux RSS ?

Un flux RSS est un fichier qui permet de déclarer les épisodes d'un podcast ainsi que de diffuser les liens vers les médias (mp3). La page wikipédia propose l'exemple suivant :

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>Mon site</title>
        <description>Ceci est un exemple de flux RSS 2.0</description>
        <lastBuildDate>Sat, 07 Sep 2002 00:00:01 GMT</lastBuildDate>
        <link>http://www.example.org</link>
        <item>
            <title>Actualité N°1</title>
            <description>Ceci est ma première actualité</description>
            <pubDate>Sat, 07 Sep 2002 00:00:01 GMT</pubDate>
            <link>http://www.example.org/actu1</link>
        </item>
        <item>
            <title>Actualité N°2</title>
            <description>Ceci est ma seconde actualité</description>
            <pubDate>Sat, 07 Sep 2002 00:00:01 GMT</pubDate>
            <link>http://www.example.org/actu2</link>
        </item>
    </channel>
</rss>

Pour écrire un fichier RSS, nous allons donc tout d'abord récupérer les épisodes disponibles sur le site http://rendezvousavecmrx.free.fr.

Téléchargement des liens vers les épisodes

Nous allons partir de la page qui recense tous les épisodes par liste alphabétique : http://rendezvousavecmrx.free.fr/page/liste.php. Nous allons utiliser BeautifulSoup pour ceci.

In [1]:
from bs4 import BeautifulSoup
In [2]:
import requests

Dans un premier temps, nous téléchargeons la page :

In [3]:
r = requests.get('http://rendezvousavecmrx.free.fr/page/liste.php')
r.encoding = 'utf-8'
In [4]:
soup = BeautifulSoup(r.text, 'html.parser')

Puis nous en extrayons les lignes qui contiennent chacun une émission :

In [5]:
rows = soup.find('table').find_all('tr')

Enfin, nous extrayons de chaque ligne l'adresse vers la page dédiée :

In [6]:
links = ['http://rendezvousavecmrx.free.fr/page/' + row.find_all('td')[1].find('a').attrs['href'] for row in rows]

Combien d'émissions trouvons nous ?

In [7]:
len(links)
Out[7]:
769

Extraction des propriétés de chaque épisode

A partir de ces liens individuels, nous pouvons télécharger chacune des pages et en extraire les informations que nous cherchons. Prenons pour exemple le premier lien de la liste.

In [8]:
link = links[6]
link
Out[8]:
'http://rendezvousavecmrx.free.fr/page/detail_emission.php?cle_emission=411'

On télécharge la page :

In [9]:
r = requests.get(link)
r.encoding = 'utf-8'
In [10]:
soup = BeautifulSoup(r.text, 'html.parser')
In [11]:
centre = soup.find(id='centre')

On en extrait la date de l'émission :

In [12]:
[tag.next_sibling.strip() for tag in centre.find('strong').select('br')[:-1]]
Out[12]:
['07 juillet 2007', '06 janvier 2007']

Le lien de téléchargement :

In [13]:
centre.find(id='telechargement').find('a').attrs['href']
Out[13]:
'../audio/mr_x_2007_01_06.mp3'
In [14]:
centre.find(id='telechargement').find('a').attrs['href'].replace('..', 'http://rendezvousavecmrx.free.fr')
Out[14]:
'http://rendezvousavecmrx.free.fr/audio/mr_x_2007_01_06.mp3'

Ainsi que le titre :

In [15]:
centre.find(id='titre').text.strip()
Out[15]:
'17 octobre 1961'

Et la description :

In [16]:
centre.find(id='emission').text.strip()
Out[16]:
"Il s'agit de l'une des pages les plus noires de notre histoire contemporaine : la répression de la manifestation algérienne du 17 octobre 1961. Ce jour-là, ou plutôt cette nuit-là, les forces de l'ordre, policiers, gendarmes, CRS confondus ont fait preuve d'une violence inouïe, rarement vue sur le territoire français. Un véritable déchaînement au coeur de Paris qui a sans doute fait au moins 200 morts parmi les manifestants. Et pendant plusieurs jours on repêchera régulièrement des cadavres dans la Seine.Pourtant, ces événements sanglants passeront presque inaperçus. Il est clair que la France n'a pas voulu voir. Et d'abord parce que les victimes étaient des indigènes, comme on disait à l'époque. C'est à dire des Français de seconde zone. Cet aveuglement sera tel que les autorités, à commencer par le préfet de police Maurice Papon, pourront longtemps prétendre que seuls trois hommes ont trouvé la mort ce 17 octobre 1961. Et encore l'un d'entre eux, un européen, n'aurait-il été victime que d'une crise cardiaque.Censure, indifférence, mensonges, amnistie précipitée se seront donc conjugués afin que ce massacre soit rejeté dans les oubliettes de l'Histoire. Et il faudra presque 30 ans et l'ouverture du procès de Maurice Papon pour crimes contre l'Humanité pour que la vérité, accablante, émerge peu à peu.Mais le plus étonnant n'est peut-être pas là. Pourquoi, alors que la paix en Algérie semblait pratiquement acquise en cette fin 1961, une manifestation pacifique a-t-elle été réprimée de façon aussi meurtrière ? Quels étaient les mobiles des uns et des autres ? Qui avait intérêt à noyer dans le sang ce sursaut de fierté des Algériens de France ?Monsieur X essaie de démêler les fils d'une affaire qui demeure une tache indélébile sur l'histoire de notre pays."

Nous sommes prêts à écrire une fonction qui va nous permettre de générer les données dont nous avons besoin.

In [17]:
from collections import OrderedDict
In [18]:
def extract_props(r):
    """Extracts properties from request r."""
    soup = BeautifulSoup(r.text, 'html.parser')
    centre = soup.find(id='centre')
    props = OrderedDict()
    props['date'] = [tag.next_sibling.strip() for tag in centre.find('strong').select('br')[:-1]]
    props['titre'] = centre.find(id='titre').text.strip()
    props['media'] = centre.find(id='telechargement').find('a').attrs['href'].replace('..', 'http://rendezvousavecmrx.free.fr')
    props['contenu'] = centre.find(id='emission').text.strip()
    return props

On vérifie que la fonction donne le résultat attendu :

In [19]:
extract_props(r)
Out[19]:
OrderedDict([('date', ['07 juillet 2007', '06 janvier 2007']),
             ('titre', '17 octobre 1961'),
             ('media',
              'http://rendezvousavecmrx.free.fr/audio/mr_x_2007_01_06.mp3'),
             ('contenu',
              "Il s'agit de l'une des pages les plus noires de notre histoire contemporaine : la répression de la manifestation algérienne du 17 octobre 1961. Ce jour-là, ou plutôt cette nuit-là, les forces de l'ordre, policiers, gendarmes, CRS confondus ont fait preuve d'une violence inouïe, rarement vue sur le territoire français. Un véritable déchaînement au coeur de Paris qui a sans doute fait au moins 200 morts parmi les manifestants. Et pendant plusieurs jours on repêchera régulièrement des cadavres dans la Seine.Pourtant, ces événements sanglants passeront presque inaperçus. Il est clair que la France n'a pas voulu voir. Et d'abord parce que les victimes étaient des indigènes, comme on disait à l'époque. C'est à dire des Français de seconde zone. Cet aveuglement sera tel que les autorités, à commencer par le préfet de police Maurice Papon, pourront longtemps prétendre que seuls trois hommes ont trouvé la mort ce 17 octobre 1961. Et encore l'un d'entre eux, un européen, n'aurait-il été victime que d'une crise cardiaque.Censure, indifférence, mensonges, amnistie précipitée se seront donc conjugués afin que ce massacre soit rejeté dans les oubliettes de l'Histoire. Et il faudra presque 30 ans et l'ouverture du procès de Maurice Papon pour crimes contre l'Humanité pour que la vérité, accablante, émerge peu à peu.Mais le plus étonnant n'est peut-être pas là. Pourquoi, alors que la paix en Algérie semblait pratiquement acquise en cette fin 1961, une manifestation pacifique a-t-elle été réprimée de façon aussi meurtrière ? Quels étaient les mobiles des uns et des autres ? Qui avait intérêt à noyer dans le sang ce sursaut de fierté des Algériens de France ?Monsieur X essaie de démêler les fils d'une affaire qui demeure une tache indélébile sur l'histoire de notre pays.")])

On peut maintenant faire une boucle sur chacune des émissions :

In [20]:
import tqdm
In [21]:
allprops = []
for link in tqdm.tqdm(links):
    r = requests.get(link)
    r.encoding = 'utf-8'
    allprops.append(extract_props(r))
100%|██████████| 769/769 [00:48<00:00, 16.93it/s]

Construction d'une table des épisodes classée par date

Avec les données précédentes, nous pouvons maintenant créer un grand tableau de toutes les émissions :

In [24]:
import pandas as pd
import dateparser
In [26]:
df = pd.DataFrame(allprops)
df['date'] = [items[0] for items in df['date']]
df[df.titre == ''] = pd.np.nan
df[df.media == 'http://rendezvousavecmrx.free.fr/audio/'] = pd.np.nan
df = df.dropna()
df['date'] = pd.to_datetime([dateparser.parse(date).date() for date in df.date])
df = df.sort_values(by='date')
In [27]:
df
Out[27]:
date titre media contenu
312 1997-01-04 La 5ème colonne http://rendezvousavecmrx.free.fr/audio/mr_x_19...
687 1997-01-11 Rennes-le-Château et l'abbé Saunières http://rendezvousavecmrx.free.fr/audio/mr_x_19...
639 1997-01-18 Noël Field http://rendezvousavecmrx.free.fr/audio/mr_x_19...
503 1997-01-25 Le réseau Odessa http://rendezvousavecmrx.free.fr/audio/mr_x_19...
469 1997-02-01 Le masque de fer http://rendezvousavecmrx.free.fr/audio/mr_x_19...
332 1997-02-08 La défaite de 1940 http://rendezvousavecmrx.free.fr/audio/mr_x_19...
186 1997-02-22 Jean de Broglie http://rendezvousavecmrx.free.fr/audio/mr_x_19...
723 1997-03-01 Sergueï Efron http://rendezvousavecmrx.free.fr/audio/mr_x_19...
395 1997-03-08 La rumeur Mitterand http://rendezvousavecmrx.free.fr/audio/mr_x_19...
666 1997-03-15 Pierre Cardot http://rendezvousavecmrx.free.fr/audio/mr_x_19...
649 1997-03-22 Opération Timsilt http://rendezvousavecmrx.free.fr/audio/mr_x_19...
106 1997-03-29 Dracula http://rendezvousavecmrx.free.fr/audio/mr_x_19...
706 1997-04-05 Rudolph Abel http://rendezvousavecmrx.free.fr/audio/mr_x_19...
468 1997-04-12 Le mage de Marsal http://rendezvousavecmrx.free.fr/audio/mr_x_19...
86 1997-04-19 Che Guevara http://rendezvousavecmrx.free.fr/audio/mr_x_19...
172 1997-04-26 Israel Beer http://rendezvousavecmrx.free.fr/audio/mr_x_19...
196 1997-05-03 Joseph Joinovici http://rendezvousavecmrx.free.fr/audio/mr_x_19...
92 1997-05-10 Croyez-vous aux fantômes ? http://rendezvousavecmrx.free.fr/audio/mr_x_19...
229 1997-05-17 L'affaire Farewell http://rendezvousavecmrx.free.fr/audio/mr_x_19...
504 1997-05-24 Le réseau Prosper http://rendezvousavecmrx.free.fr/audio/mr_x_19...
310 1997-05-31 L'opposition à l'indépendance algérienne http://rendezvousavecmrx.free.fr/audio/mr_x_19...
455 1997-06-07 Le Général Sikorski http://rendezvousavecmrx.free.fr/audio/mr_x_19...
354 1997-06-21 La guerre du Kipour http://rendezvousavecmrx.free.fr/audio/mr_x_19...
728 1997-06-28 Szkolnikoff http://rendezvousavecmrx.free.fr/audio/mr_x_19...
326 1997-07-05 La chasse aux cerveaux http://rendezvousavecmrx.free.fr/audio/mr_x_19...
370 1997-09-13 La Main Rouge http://rendezvousavecmrx.free.fr/audio/mr_x_19...
591 1997-09-20 Les vedettes de Cherbourg http://rendezvousavecmrx.free.fr/audio/mr_x_19...
617 1997-09-27 Michel Baroin http://rendezvousavecmrx.free.fr/audio/mr_x_19...
217 1997-10-04 L'affaire Cicéron http://rendezvousavecmrx.free.fr/audio/mr_x_19...
376 1997-10-18 La mort de JFK http://rendezvousavecmrx.free.fr/audio/mr_x_19...
... ... ... ... ...
695 2014-11-22 Retour sur la Papouasie Occidentale annexée pa... http://rendezvousavecmrx.free.fr/audio/mr_x_20... Ils ont enfin recouvré la liberté, Valentine B...
388 2014-11-29 La revanche de Sankara http://rendezvousavecmrx.free.fr/audio/mr_x_20... Jamais une expression imagée n'a été aussi bie...
382 2014-12-06 La persécution des Ouïgours en Chine (1/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... On l'appelle la rébellion des parapluies... Ce...
383 2014-12-13 La persécution des Ouïgours en Chine (2/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... C'est le Far West des Chinois : le Xinjiang, u...
625 2014-12-20 Mokhtar Belmokhtar http://rendezvousavecmrx.free.fr/audio/mr_x_20... L'actualité avait finir par les oublier quelqu...
621 2014-12-27 Mincemeat, une opération d'intoxication au coe... http://rendezvousavecmrx.free.fr/audio/mr_x_20... 'Opération Chair à pâté' ! Non, il ne s'agit p...
112 2015-01-03 Edward VIII, un roi nazi ? http://rendezvousavecmrx.free.fr/audio/mr_x_20... On a voulu faire croire à une histoire d'amour...
511 2015-01-10 Le scandale UraMin (1/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... Polar, thriller, scandale. Et même affaire d'é...
512 2015-01-17 Le scandale UraMin (2/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... Deux milliards et demi de dollars : le prix d'...
99 2015-01-24 Décembre 2014 : échange d'espions entre Cuba e... http://rendezvousavecmrx.free.fr/audio/mr_x_20... Pourquoi a-t-elle été oubliée ? Oui, pourquoi ...
58 2015-01-31 Assassinats et corruption sur la Côte d'Azur (... http://rendezvousavecmrx.free.fr/audio/mr_x_20... Vingt ans après, le mystère n'est toujours pas...
59 2015-02-07 Assassinats et corruption sur la Côte d'Azur (... http://rendezvousavecmrx.free.fr/audio/mr_x_20... La députée Yann Piat a été assassinée en févri...
735 2015-02-14 Togo, le pays des 'sorciers blancs' http://rendezvousavecmrx.free.fr/audio/mr_x_20... Après le Burkina Faso, la République démocrati...
693 2015-02-21 Retour sur la Libye (1/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... Où va la Libye ? Dotée de centaines de milices...
694 2015-02-28 Retour sur la Libye (2/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... Et si les Kadhafistes revenaient au pouvoir en...
8 2015-03-07 1944, massacre au camp de Thiaroye http://rendezvousavecmrx.free.fr/audio/mr_x_20... Et si tout avait commencé à Thiaroye ? Tout, c...
678 2015-03-14 Qui a tué le procureur argentin Alberto Nisman... http://rendezvousavecmrx.free.fr/audio/mr_x_20... 'Je suis Nisman !' Comme les millions de Franç...
679 2015-03-21 Qui a tué le procureur argentin Alberto Nisman... http://rendezvousavecmrx.free.fr/audio/mr_x_20... Ses proches sont formels : le procureur argent...
132 2015-03-28 Gary Webb, l'homme qui a eu raison trop tôt http://rendezvousavecmrx.free.fr/audio/mr_x_20... Le résumé de l'histoire, le pitch, comme on di...
767 2015-04-11 Zaïre : le retour des 'Affreux' http://rendezvousavecmrx.free.fr/audio/mr_x_20... On les a appelés les 'Affreux'. Des mercenaire...
168 2015-04-18 Iran, le retour (1/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... Il faut le reconnaître : l'Iran est devenu auj...
169 2015-04-25 Iran, le retour (2/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... 'Vers l'Orient compliqué, je volais avec des i...
351 2015-05-02 La guerre des clans au Kazakhstan et ses rebon... http://rendezvousavecmrx.free.fr/audio/mr_x_20... Il est tout puissant. Et il est riche, très ri...
546 2015-05-09 Les cellules Alpha http://rendezvousavecmrx.free.fr/audio/mr_x_20... C'est le secret dans le secret ! L'existence a...
637 2015-05-16 Nkrumah et Ben Barka frères de combat (1/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... Certains de ses partisans l'appelaient 'Monsie...
638 2015-05-23 Nkrumah et Ben Barka frères de combat (2/2) http://rendezvousavecmrx.free.fr/audio/mr_x_20... C'était le héros du panafricanisme et il ne rê...
742 2015-05-30 Ukraine : le plan de Poutine http://rendezvousavecmrx.free.fr/audio/mr_x_20... Novorossia ! La nouvelle Russie ! Tel est le n...
203 2015-06-06 Kim Jong Il fait son cinéma http://rendezvousavecmrx.free.fr/audio/mr_x_20... L'histoire est à peine croyable : pour vivifie...
74 2015-06-13 Bouaké : une affaire d'Etat http://rendezvousavecmrx.free.fr/audio/mr_x_20... La tragédie de Bouaké, c'était une bavure mani...
743 2015-06-20 Un état palestinien peut-il encore exister ? http://rendezvousavecmrx.free.fr/audio/mr_x_20... Un Etat palestinien existera-t-il un jour ? La...

765 rows × 4 columns

Pour finir, il nous faut rajouter à chaque item de la table la longueur du fichier mp3 en bytes (voir ici).

In [28]:
byte_lengths = {}
for media in tqdm.tqdm(df.media):
    r = requests.head(media)
    if r.status_code == 200:
        byte_lengths[media] = r.headers['content-length']
100%|██████████| 765/765 [00:27<00:00, 27.47it/s]
In [29]:
df['bytes'] = [byte_lengths[media] for media in df.media]
In [30]:
df.head()
Out[30]:
date titre media contenu bytes
312 1997-01-04 La 5ème colonne http://rendezvousavecmrx.free.fr/audio/mr_x_19... 71937792
687 1997-01-11 Rennes-le-Château et l'abbé Saunières http://rendezvousavecmrx.free.fr/audio/mr_x_19... 74407680
639 1997-01-18 Noël Field http://rendezvousavecmrx.free.fr/audio/mr_x_19... 73890048
503 1997-01-25 Le réseau Odessa http://rendezvousavecmrx.free.fr/audio/mr_x_19... 74558208
469 1997-02-01 Le masque de fer http://rendezvousavecmrx.free.fr/audio/mr_x_19... 73537536

Ecriture d'un fichier RSS

Pour écrire un fichier RSS avec Python, nous allons utiliser la libraire ElementTree. Nous suivons le schéma montré en haut du billet et ajoutons ligne par ligne les épisodes.

In [31]:
import xml.etree.cElementTree as ET

rss = ET.Element("rss", version="2.0")
channel = ET.SubElement(rss, "channel")
title = ET.SubElement(channel, "title")
title.text = 'Podcast Rendez-vous avec X'
description = ET.SubElement(channel, "description")
description.text = "Podcast inofficiel de l'émission Rendez-vous avec X, tiré du site http://rendezvousavecmrx.free.fr/"
for index, row in df.iterrows():
    item = ET.SubElement(channel, "item")
    item_title = ET.SubElement(item, "title")
    item_title.text = row['titre']
    item_description = ET.SubElement(item, "description")
    item_description.text = row['contenu']
    item_pubdate = ET.SubElement(item, "pubDate")
    item_pubdate.text = row.date.strftime('%a, %d %b %Y 13:15:00')
    item_enclosure = ET.SubElement(item, "enclosure", url='{}'.format(row.media),
                                   length=row.bytes,
                                  type="audio/mpeg")
tree = ET.ElementTree(rss)
tree.write("files/podcast_mr_x.xml", encoding='utf-8')

Et voilà le travail ! Le lien à ajouter au gestionnaire de podcast est le suivant : https://raw.githubusercontent.com/flothesof/posts/master/files/podcast_mr_x.xml.

Ce billet a été écrit à l'aide d'un notebook Jupyter. Son contenu est sous licence BSD. Une vue statique de ce notebook peut être consultée et téléchargée ici : 20170811_RSSFeedMonsieurX.ipynb.