Un flux RSS pour le podcast Rendez-vous avec X
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.
from bs4 import BeautifulSoup
import requests
Dans un premier temps, nous téléchargeons la page :
r = requests.get('http://rendezvousavecmrx.free.fr/page/liste.php')
r.encoding = 'utf-8'
soup = BeautifulSoup(r.text, 'html.parser')
Puis nous en extrayons les lignes qui contiennent chacun une émission :
rows = soup.find('table').find_all('tr')
Enfin, nous extrayons de chaque ligne l'adresse vers la page dédiée :
links = ['http://rendezvousavecmrx.free.fr/page/' + row.find_all('td')[1].find('a').attrs['href'] for row in rows]
Combien d'émissions trouvons nous ?
len(links)
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.
link = links[6]
link
'http://rendezvousavecmrx.free.fr/page/detail_emission.php?cle_emission=411'
On télécharge la page :
r = requests.get(link)
r.encoding = 'utf-8'
soup = BeautifulSoup(r.text, 'html.parser')
centre = soup.find(id='centre')
On en extrait la date de l'émission :
[tag.next_sibling.strip() for tag in centre.find('strong').select('br')[:-1]]
['07 juillet 2007', '06 janvier 2007']
Le lien de téléchargement :
centre.find(id='telechargement').find('a').attrs['href']
'../audio/mr_x_2007_01_06.mp3'
centre.find(id='telechargement').find('a').attrs['href'].replace('..', 'http://rendezvousavecmrx.free.fr')
'http://rendezvousavecmrx.free.fr/audio/mr_x_2007_01_06.mp3'
Ainsi que le titre :
centre.find(id='titre').text.strip()
'17 octobre 1961'
Et la description :
centre.find(id='emission').text.strip()
"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.
from collections import OrderedDict
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 :
extract_props(r)
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 :
import tqdm
allprops = []
for link in tqdm.tqdm(links):
r = requests.get(link)
r.encoding = 'utf-8'
allprops.append(extract_props(r))
100%|██████████| 769/769 [00:50<00:00, 15.27it/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 :
import pandas as pd
import dateparser
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')
df
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... | |
... | ... | ... | ... | ... |
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).
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']
df['bytes'] = [byte_lengths[media] for media in df.media]
df.head()
100%|██████████| 765/765 [00:40<00:00, 18.69it/s]
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.
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'
image = ET.SubElement(channel, "image")
image_url = ET.SubElement(image, "url")
image_url.text = 'https://cdn.radiofrance.fr/s3/cruiser-production/2019/10/b3ceaae4-c8dc-4b1a-870a-7f24f80bdee4/1400x1400_rf_omm_0000021551_ite.jpg'
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.