L'inspiration pour ce post m'est venu de l'article Aspirer le site Ameli-direct, découvert à l'occasion du reportage du Monde pour lequel ce travail avait été entrepris.
Première étape : obtenir le code source d'une page avec les noms des médecins. A noter, comme décrit sur le lien ci-dessus, que les cookies (infosoins, AmeliDirectPersist) sont nécessaires, bien que je ne connaisse pas leur rôle exact.
Après des tests sous Linux et Windows, il m'est apparu deux choses :
A noter que l'on peut changer l'url et mettre 0 pour le nombre de résultats par page afin d'obtenir la totalité des réponses.
import urllib2
opener = urllib2.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
opener.addheaders.append(('Cookie', 'infosoins=0cmlvijmmprmtgq3i0lqeia2v4; AmeliDirectPersist=2973622583.20480.0000'))
url = "http://ameli-direct.ameli.fr/professionnels-de-sante/recherche-1/liste-resultats-page-1-par_page-0-tri-aleatoire-087ced0dd578295c4ad4ce3a493099e2.html"
f = opener.open(url)
lines = f.readlines()
len(lines)
867
Deuxième étape : parser le code HTML pour en tirer :
Je m'inspire ici de la classe que j'avais déjà codée pour mon parser initial.
from HTMLParser import HTMLParser
class AmeliHTMLParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.data = {'nom': [], 'prof': [], 'tel': []}
self.listening = False
self.data_type = ''
def handle_starttag(self, tag, attrs):
if tag == 'div':
if len(attrs) == 1:
if attrs[0] == ('class', 'medecin-item-nom-recherche'):
self.listening = True
self.data_type = 'nom'
elif attrs[0] == ('class', 'medecin-item-prof-recherche'):
self.listening = True
self.data_type = 'prof'
elif attrs[0] == ('class', 'medecin-item-tel-recherche'):
self.listening = True
self.data_type = 'tel'
def handle_endtag(self, tag):
if tag == 'div':
if self.listening == True:
#il faut penser à archiver les données
self.listening = False
else:
self.listening = False
def handle_data(self, data):
if self.listening == True:
self.data[self.data_type].append(data)
ameli_parser = AmeliHTMLParser()
for line in lines:
ameli_parser.feed(line)
ameli_parser.data.keys()
['nom', 'tel', 'prof']
On peut maintenant trier nos données et extraires seulement ce qui nous intéresse.
# carte vitale
data = {}
data['vitale'] = filter(lambda x: x!='', map(str.strip, ameli_parser.data['prof']))
# phone numbers and conventions
s = ''.join(ameli_parser.data['tel'])
s = map(str.strip, s.split('\n'))
data['tel'] = []
data['convention'] = []
for i in range(1, len(s)):
if i % 2 == 1:
data['tel'].append(s[i])
if i % 2 == 0:
data['convention'].append(s[i])
# names and addresses
s = ' '.join(ameli_parser.data['nom'])
s = map(str.strip, s.split('\n'))
data['nom'] = []
data['adresse'] = []
for i in range(1, len(s)):
if (i - 1) % 4 == 0:
data['nom'].append(s[i])
if (i - 1) % 4 == 3:
data['adresse'].append(s[i])
Nous pouvons maintenant créer une liste qui regroupe les informations par médecin.
data_list = [[data['nom'][i], data['tel'][i],
data['vitale'][i], data['convention'][i],
data['adresse'][i]] for i in range(len(data['adresse']))]
len(data_list)
24
for i in range(len(data_list)):
print(', '.join(data_list[i]))
KELLER PIERRE, 0143354333, Carte vitale : Oui, Conventionné secteur 2, 3 RUE VICTOR CONSIDERANT 75014 PARIS COMMEAU DOMINIQUE, 0145417363, Carte vitale : Oui, Conventionné secteur 2, 22 RUE ERNEST CRESSON 75014 PARIS KRETZ GILLES, 0140446530, Carte vitale : Oui, Conventionné secteur 2, 150 RUE R LOSSERAND 75014 PARIS WERTHEL ANDREE LUCE, 0145427574, Carte vitale : Oui, Conventionné secteur 2, 24 AVENUE VILLEMAIN 75014 PARIS BREZIN ANTOINE, 0158412200, Carte vitale : Non, Conventionné secteur 2, 27 RUE DU FBG SAINT JACQUES 75014 PARIS DRYLEWICZ JOEL, 0145453212, Carte vitale : Oui, Conventionné secteur 1, 87 RUE DE GERGOVIE 75014 PARIS CHASSIGNOL ALEXIS, 0143271069, Carte vitale : Oui, Conventionné secteur 2, SELARL CHASSIGNOL VAN WENT 79 AVENUE DU GENERAL LECLERC 75014 PARIS MEYER MARIE CLAUDE, 0143206255, Carte vitale : Non, Conventionné secteur 2, 29 BOULEVARD E.QUINET 75014 PARIS GIRARD DECIS ANAIS, 0143354103, Carte vitale : Oui, Conventionné secteur 2, 10 RUE DAGUERRE 75014 PARIS CHARLOT JEAN CLAUDE, 0143224233, Carte vitale : Non, Conventionné secteur 2, 228 BOULEVARD RASPAIL 75014 PARIS VAN WENT CHARLES, , Carte vitale : Oui, Conventionné secteur 2, SELARL CHASSIGNOL VAN WENT 79 AVENUE DU GENERAL LECLERC 75014 PARIS TAZARTES MICHEL, 0143209175, Carte vitale : Non, Conventionné secteur 2, 142 BOULEVARD DU MONTPARNASSE 75014 PARIS THAN TRONG THONG, 0143354103, Carte vitale : Oui, Conventionné secteur 2, 10 RUE DAGUERRE 75014 PARIS SULMAN JEAN JACQUES, 0140470000, Carte vitale : Oui, Conventionné secteur 2, 90 AVENUE DU MAINE 75014 PARIS LE QUOY OLIVIER, 0143353148, Carte vitale : Non, Conventionné secteur 2, 29 RUE D ALESIA 75014 PARIS CHAMOUNI HUBERT, , Carte vitale : Oui, Conventionné secteur 2, 95 AVENUE DU GAL LECLERC 75014 PARIS MAGNANI MATTEO, , Carte vitale : Oui, Conventionné secteur 2, 29 RUE SARRETTE. 75014 PARIS SELLAM MICKAEL, , Carte vitale : Oui, Conventionné secteur 2, 29 RUE SARRETTE 75014 PARIS MONNET DOMINIQUE, 0158412191, Carte vitale : Non, Conventionné secteur 2, 27 RUE DU FBG SAINT JACQUES 75014 PARIS SARFATI ADRIEN, 0143354103, Carte vitale : Oui, Conventionné secteur 2, 10 RUE DAGUERRE 75014 PARIS BOUMENDIL JULIEN, 0143209175, Carte vitale : Oui, Conventionné secteur 2, 142 BOULEVARD DU MONTPARNASSE 75014 PARIS VAN WENT RICHARD, 0143271069, Carte vitale : Non, Conventionné secteur 2, SELARL VAN WENT 79 AVENUE DU GENERAL LECLERC 75014 PARIS FELLER MICHEL, 0143350828, Carte vitale : Non, Conventionné secteur 2, 118 BOULEVARD MONTPARNASSE 75014 PARIS BINN DEUTSCHER SYLVIE, 0145650424, Carte vitale : Non, Conventionné secteur 2, 12 RUE DE LA TOMBE ISSOIRE 75014 PARIS
Et finalement, nous pouvons afficher un tableau HTML avec toutes ces informations.
from IPython.display import HTML
def html_table(string_list):
output = '<table>'
for sublist in string_list:
output += ' <tr><td>'
output += ' </td><td>'.join(sublist)
output += ' </td></tr>'
output += '</table>'
return output
HTML(html_table(data_list))
KELLER PIERRE | 0143354333 | Carte vitale : Oui | Conventionné secteur 2 | 3 RUE VICTOR CONSIDERANT 75014 PARIS |
COMMEAU DOMINIQUE | 0145417363 | Carte vitale : Oui | Conventionné secteur 2 | 22 RUE ERNEST CRESSON 75014 PARIS |
KRETZ GILLES | 0140446530 | Carte vitale : Oui | Conventionné secteur 2 | 150 RUE R LOSSERAND 75014 PARIS |
WERTHEL ANDREE LUCE | 0145427574 | Carte vitale : Oui | Conventionné secteur 2 | 24 AVENUE VILLEMAIN 75014 PARIS |
BREZIN ANTOINE | 0158412200 | Carte vitale : Non | Conventionné secteur 2 | 27 RUE DU FBG SAINT JACQUES 75014 PARIS |
DRYLEWICZ JOEL | 0145453212 | Carte vitale : Oui | Conventionné secteur 1 | 87 RUE DE GERGOVIE 75014 PARIS |
CHASSIGNOL ALEXIS | 0143271069 | Carte vitale : Oui | Conventionné secteur 2 | SELARL CHASSIGNOL VAN WENT 79 AVENUE DU GENERAL LECLERC 75014 PARIS |
MEYER MARIE CLAUDE | 0143206255 | Carte vitale : Non | Conventionné secteur 2 | 29 BOULEVARD E.QUINET 75014 PARIS |
GIRARD DECIS ANAIS | 0143354103 | Carte vitale : Oui | Conventionné secteur 2 | 10 RUE DAGUERRE 75014 PARIS |
CHARLOT JEAN CLAUDE | 0143224233 | Carte vitale : Non | Conventionné secteur 2 | 228 BOULEVARD RASPAIL 75014 PARIS |
VAN WENT CHARLES | Carte vitale : Oui | Conventionné secteur 2 | SELARL CHASSIGNOL VAN WENT 79 AVENUE DU GENERAL LECLERC 75014 PARIS | |
TAZARTES MICHEL | 0143209175 | Carte vitale : Non | Conventionné secteur 2 | 142 BOULEVARD DU MONTPARNASSE 75014 PARIS |
THAN TRONG THONG | 0143354103 | Carte vitale : Oui | Conventionné secteur 2 | 10 RUE DAGUERRE 75014 PARIS |
SULMAN JEAN JACQUES | 0140470000 | Carte vitale : Oui | Conventionné secteur 2 | 90 AVENUE DU MAINE 75014 PARIS |
LE QUOY OLIVIER | 0143353148 | Carte vitale : Non | Conventionné secteur 2 | 29 RUE D ALESIA 75014 PARIS |
CHAMOUNI HUBERT | Carte vitale : Oui | Conventionné secteur 2 | 95 AVENUE DU GAL LECLERC 75014 PARIS | |
MAGNANI MATTEO | Carte vitale : Oui | Conventionné secteur 2 | 29 RUE SARRETTE. 75014 PARIS | |
SELLAM MICKAEL | Carte vitale : Oui | Conventionné secteur 2 | 29 RUE SARRETTE 75014 PARIS | |
MONNET DOMINIQUE | 0158412191 | Carte vitale : Non | Conventionné secteur 2 | 27 RUE DU FBG SAINT JACQUES 75014 PARIS |
SARFATI ADRIEN | 0143354103 | Carte vitale : Oui | Conventionné secteur 2 | 10 RUE DAGUERRE 75014 PARIS |
BOUMENDIL JULIEN | 0143209175 | Carte vitale : Oui | Conventionné secteur 2 | 142 BOULEVARD DU MONTPARNASSE 75014 PARIS |
VAN WENT RICHARD | 0143271069 | Carte vitale : Non | Conventionné secteur 2 | SELARL VAN WENT 79 AVENUE DU GENERAL LECLERC 75014 PARIS |
FELLER MICHEL | 0143350828 | Carte vitale : Non | Conventionné secteur 2 | 118 BOULEVARD MONTPARNASSE 75014 PARIS |
BINN DEUTSCHER SYLVIE | 0145650424 | Carte vitale : Non | Conventionné secteur 2 | 12 RUE DE LA TOMBE ISSOIRE 75014 PARIS |
Maintenant que nous avons les adresses en mémoire, nous pouvons utiliser une API de Geocoding pour localiser leurs positions avec leurs latitudes et longitudes. Tout d'abord, un exemple.
from geopy import geocoders
g = geocoders.GoogleV3()
place, (lat, lng) = g.geocode("3 RUE VICTOR CONSIDERANT 75014 PARIS ")
print "%s: %.5f, %.5f" % (place, lat, lng)
3 Rue Victor Considérant, 75014 Paris, France: 48.83521, 2.33110
L'API de Google Maps donne droit à 2500 codages par jour (gratuitement). La liste précédente étant de 24 noms, cela ne devrait pas poser de problèmes. La deuxième étape de notre problème est de construire une carte sur laquelle nous pourrons positionner cette liste de latitudes et longitudes. Nous pouvons pour cela utiliser le package basemap, qui fait partie de matplotlib. Une carte de France est obtenue de la manière suivante :
Nous utilisons dans la suite le toolkit Basemap, qui permet de dessiner efficacement des cartes en coordonnées latitude et longitude. Tout d'abord, nous examinons quelques exemples introductifs.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
# setup Lambert Conformal basemap.
figure(figsize=(10, 10))
m = Basemap(width=2000000, height=2000000, projection='lcc',
resolution='l',
lat_1=40.,lat_2=60.,
lat_0=46.,lon_0=3.)
# draw coastlines.
m.drawcoastlines()
# draw a boundary around the map, fill the background.
# this background will end up being the ocean color, since
# the continents will be drawn on top.
m.drawmapboundary(fill_color='aqua')
# fill continents, set lake color same as ocean color.
m.fillcontinents(color='coral',lake_color='aqua')
m.drawcountries()
<matplotlib.collections.LineCollection at 0x90a2070>
Nous pouvons faire mieux que la carte précédente, en ajoutant du relief par exemple. Les données ne sont malheureusement disponibles qu'en basse résolution...
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
# setup Lambert Conformal basemap.
figure(figsize=(10, 10), dpi=300)
m = Basemap(width=2000000, height=2000000, projection='lcc',
resolution='f',
lat_1=40.,lat_2=60.,
lat_0=46.,lon_0=3.)
m.etopo()
<matplotlib.image.AxesImage at 0x24ba7710>
Et si nous nous intéressions à une carte des arrondissements de Paris ? Voici déjà du code pour dessiner la Seine.
from mpl_toolkits.basemap import Basemap
figure(figsize=(10, 10))
m = Basemap(width=20000, height=20000, projection='lcc',
resolution='h',
lat_1=40.,lat_2=60.,
lat_0=48.86,lon_0=2.33)
m.drawrivers()
<matplotlib.collections.LineCollection at 0x24cae190>
from matplotlib.collections import LineCollection
import shapefile
figure(figsize=(10, 10))
ax = subplot(111)
# first, choose map coordinates and draw rivers
m = Basemap(width=20000, height=20000, projection='lcc',
resolution='h',
lat_1=40.,lat_2=60.,
lat_0=48.86,lon_0=2.33)
m.drawrivers() # the Seine
# secondly, add departments from detailed shapefile (fra_adm5)
r = shapefile.Reader(r"files/france_shapes/fra_adm5")
shapes = r.shapes()
records = r.records()
for record, shape in zip(records,shapes):
if record[6] == 75: # filtering Paris data
lons,lats = zip(*shape.points)
data = np.array(m(lons, lats)).T
if len(shape.parts) == 1:
segs = [data,]
else:
segs = []
for i in range(1,len(shape.parts)):
index = shape.parts[i-1]
index2 = shape.parts[i]
segs.append(data[index:index2])
segs.append(data[index2:])
lines = LineCollection(segs,antialiaseds=(1,))
lines.set_edgecolors('k')
lines.set_linewidth(0.1)
ax.add_collection(lines)
Nous pouvons finalement géocoder les adresses et les afficher sur la carte utilisée jusqu'à présent. Tout d'abord, le géocodage :
import time
time.sleep(0.5)
from geopy import geocoders
g = geocoders.GoogleV3()
coords = {}
for item in data_list:
adr = item[4]
place, (lat, lng) = g.geocode(adr)
coords[item[0]] = [place, (lat, lng)]
time.sleep(0.5)
for key in coords.keys():
print coords[key][1]
(48.82772019999999, 2.3271682) (48.83998889999999, 2.3300707) (48.82772019999999, 2.3271682) (48.837823, 2.3223636) (48.8309416, 2.3122582) (48.8414852, 2.3306113) (48.8352106, 2.3311049) (48.841577, 2.3228992) (48.8329855, 2.3282261) (48.8262389, 2.3267529) (48.8262063, 2.3293581) (48.83071169999999, 2.3192323) (48.8336753, 2.3305608) (48.83238619999999, 2.3354335) (48.82772019999999, 2.3271682) (48.83740479999999, 2.3388418) (48.8336753, 2.3305608) (48.83740479999999, 2.3388418) (48.8336753, 2.3305608) (48.84073, 2.3329251) (48.84073, 2.3329251) (48.8262063, 2.3293581) (48.8276003, 2.3335279) (48.8318535, 2.3167192)
Nous préparons ces données sous la forme de deux listes utilisées dans la suite.
latitudes = [item[1][1][0] for item in coords.items()]
longitudes = [item[1][1][1] for item in coords.items()]
labels = [item[0] for item in data_list]
Et enfin, la dernière étape, l'affichage sous forme de points avec des labels. Je me suis inspirée du [tutorial suivant][http://peak5390.wordpress.com/2012/12/08/matplotlib-basemap-tutorial-plotting-points-on-a-simple-map/).
from matplotlib.collections import LineCollection
import shapefile
figure(figsize=(15, 15))
ax = subplot(111)
# first, choose map coordinates and draw rivers
m = Basemap(width=5000, height=5000, projection='lcc',
resolution='h',
lat_1=40.,lat_2=60.,
lat_0=48.82,lon_0=2.33)
m.drawrivers() # the Seine
# secondly, add departments from detailed shapefile (fra_adm5)
r = shapefile.Reader(r"files/france_shapes/fra_adm5")
shapes = r.shapes()
records = r.records()
for record, shape in zip(records,shapes):
if record[6] == 75: # filtering Paris data
lons, lats = zip(*shape.points)
data = np.array(m(lons, lats)).T
if len(shape.parts) == 1:
segs = [data,]
else:
segs = []
for i in range(1,len(shape.parts)):
index = shape.parts[i-1]
index2 = shape.parts[i]
segs.append(data[index:index2])
segs.append(data[index2:])
lines = LineCollection(segs,antialiaseds=(1,))
lines.set_edgecolors('k')
lines.set_linewidth(0.1)
ax.add_collection(lines)
# thirdly, plot coordinates and labels of doctors
x, y = m(longitudes, latitudes)
m.plot(x, y, 'bo', markersize=18)
for label, xpt, ypt in zip(labels, x, y):
text(xpt, ypt, label)
# finally, plot home coordinates
x, y = m(2.325656, 48.823494)
m.plot(x, y, 'ro', markersize=18)
[<matplotlib.lines.Line2D at 0xb7a3110>]