Skip to article frontmatterSkip to article content
Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout

fetch the Internet

JSON

commençons par une donnée en JSON; par exemple ceci src/country-by-population.json
pour trouver le bon lien, je clique sur ‘Raw’ et je copie ici

URL1 = "https://raw.githubusercontent.com/samayo/country-json/master/src/country-by-population.json"

Il s’agit pour nous d’écrire un code qui va chercher cette donnée et les traduit en une structure Python (à base de list et dict) -
la structure en question est déjà présente dans le JSON.

Indices sur la librairie requests

ce module ne fait pas partie de la librairie standard, il faut l’installer avec .. wait for it

pip install requests

(il y a des fonctions de ce genre dans la librairie standard, mais c’est celle-ci qui est le plus souvent utilisée)

poster la requête

import json

import requests
# il faut être un peu patient, surtout si votre réseau est un peu lent

response = requests.get(URL1)

les attributs de l’objet response

# dans ce genre de cas dir() est utile
# sauf que c'est un peu trop bavard
# dir(response)
# du coup ça vaut le coup de filter un peu

[symbol for symbol in dir(response) if not symbol.startswith('_')]
['apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encoding', 'headers', 'history', 'is_permanent_redirect', 'is_redirect', 'iter_content', 'iter_lines', 'json', 'links', 'next', 'ok', 'raise_for_status', 'raw', 'reason', 'request', 'status_code', 'text', 'url']
# si tout s'est bien passé ici on doit avoir 200
# (pour être précis: entre 200 et 299)
# c'est un des codes de retours de HTTP
# si c'est par exemple 404 ça signifie que cette URL n'existe plus

response.status_code
200
# si on était curieux on pourrait aussi faire

response.headers
{'Connection': 'keep-alive', 'Content-Length': '3197', 'Cache-Control': 'max-age=300', 'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox", 'Content-Type': 'text/plain; charset=utf-8', 'ETag': 'W/"81c9e62269c8660e7bf10f234993c94c4ea4dc2b725e4bd5f2df78505cab71e7"', 'Strict-Transport-Security': 'max-age=31536000', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'deny', 'X-XSS-Protection': '1; mode=block', 'X-GitHub-Request-Id': '1761:37D6C5:18CA17:1C848A:68FA54ED', 'Content-Encoding': 'gzip', 'Accept-Ranges': 'bytes', 'Date': 'Thu, 23 Oct 2025 16:16:47 GMT', 'Via': '1.1 varnish', 'X-Served-By': 'cache-chi-kigq8000090-CHI', 'X-Cache': 'MISS', 'X-Cache-Hits': '0', 'X-Timer': 'S1761236208.685540,VS0,VE74', 'Vary': 'Authorization,Accept-Encoding', 'Access-Control-Allow-Origin': '*', 'Cross-Origin-Resource-Policy': 'cross-origin', 'X-Fastly-Request-ID': 'd95391021be35b10c01674613d520438e187c816', 'Expires': 'Thu, 23 Oct 2025 16:21:47 GMT', 'Source-Age': '0'}
# pour voir par exemple la taille de notre donnée

response.headers['Content-Length']
'3197'
# mais bon, bien sûr ce qu'on veut surtout c'est le contenu
# c'est une chaine, regardons les 100 premiers caractères

print(type(response.text))
print(response.text[:100], '...')
<class 'str'>
[
    {
        "country": "Afghanistan",
        "population": 37172386
    },
    {
        "count ...

indices sur la librairie json

pour le coup, elle est dans la stdlib; les deux méthodes intéressantes sont

le code

du coup le code que je dois écrire pour faire le job est simplement

def get_url_as_json(url):
    """
    Fetch a URL and decode its result as JSON
    """

    with requests.get(url) as response:
        return json.loads(response.text)
python_friendly = get_url_as_json(URL1)

# on obtient quoi comme type ?
type(python_friendly)
list
python_friendly[:3]
[{'country': 'Afghanistan', 'population': 37172386}, {'country': 'Albania', 'population': 2866376}, {'country': 'Algeria', 'population': 42228429}]
python_friendly[-3:]
[{'country': 'Yemen', 'population': 28498687}, {'country': 'Zambia', 'population': 17351822}, {'country': 'Zimbabwe', 'population': 14439018}]
len(python_friendly)
244

option: transformer la structure

# et ici par exemple je pourrais décider que c'est plus pratique
# sous la forme d'un dictionnaire name -> population

population = {d['country']: d['population'] for d in python_friendly}
# de sorte que je peux faire simplement
population['France']
66977107

CSV

Vous trouverez à cette URL un accès aux données de population par pays data/population.csv

# pareil, on clique sur 'Raw' pour obtenir le lien vers la donnée 'crue'

URL2 = "https://raw.githubusercontent.com/datasets/population/main/data/population.csv"
# il faut être un peu patient car c'est bcp + gros que tout à l'heure

response = requests.get(URL2)
response.headers['Content-Length']
'130037'

indices sur la librairie csv

# il semble donc qu'on ait affaire à un csv classique
# qu'on peut décortiquer avec object csv.reader
# https://docs.python.org/3/library/csv.html

import csv

il se trouve que la librairie csv fournit un objet csv.reader qui prend un paramètre de type file
c’est-à-dire comme par exemple le résultat de open()

ça demande une petite gymnastique, car nous avons le texte en mémoire, et pas dans un fichier...

option 1 - pas beau !

très inélégant, mais qui peut marcher à la rigueur:
j’ai une chaine, je l’écris dans un fichier, que je peux ensuite rouvrir en lecture pour le passer à csv.reader ...
une option viable en dernier recours, mais on ne va pas le faire, car c’est très vilain, et il y a une astuce pour s’en sortir autrement:

option 2 - mieux

exactement pour contourner ce genre de cas, il y a une classe io.StringIO qui, en partant d’une chaine en mémoire, se comporte comme un fichier ! et donc on va utiliser cela :

# pour utiliser csv.reader, il nous faut un objet de type fichier (file-like object)
# et c'est là que StringIO est super pratique

from io import StringIO

with (StringIO(response.text)) as file_like:
    reader = csv.reader(file_like, delimiter=',')
    # on regarde juste les 3 premiers enregistrements
    for i, row in enumerate(reader, 1):
        print(i, row)
        if i >= 3:
            break
1 ['Country Name', 'Country Code', 'Year', 'Value']
2 ['Aruba', 'ABW', '1960', '54922']
3 ['Aruba', 'ABW', '1961', '55578']

À ce stade, on voit comment on peut transformer nos données en listes Python; mais on va s’arrêter là, parce qu’en pratique ça c’est vraiment un exercice qu’on ferait en pandas...

un aperçu de pandas

pandas est une librairie externe aussi, qu’il faut absolument connaitre un peu, dès qu’on manipule de la data
et pour commencer il y a une fonction read_csv qui va faire le job complet (acquisition et chargement) en un seul appel:

import pandas as pd
# en vrai on fait comme ça: être patient .. à nouveau

df = pd.read_csv(URL2)
# on regarde un peu ce qu'il y a dedans

df.head(3)

# on peut aussi regarder la fin avec df.tail(3)
Loading...
# conversion en date

df['time'] = pd.to_datetime(df.Year, format="%Y")
df.head(3)
Loading...
# on nettoie / renomme

del df['Year']

df = df.rename(columns = {
    'Country Name': 'country',
    'Country Code': 'code',
    'Value': 'population',
})
df.head(2)
Loading...
# on choisit un index un peu plus parlant

df = df.set_index('time')
df.head(2)
Loading...
# on se définit arbitrairement une période qui nous intéresse

begin = pd.to_datetime('1980', format='%Y')
end = pd.to_datetime('2010', format='%Y')
# on filtre ce qui nous intéresse

df_france = df[(df.country == 'France') & (df.index >= begin) & (df.index <= end)]
df_france.head
<bound method NDFrame.head of country code population time 1980-01-01 France FRA 55274184.0 1981-01-01 France FRA 55603353.0 1982-01-01 France FRA 55806789.0 1983-01-01 France FRA 56108330.0 1984-01-01 France FRA 56383085.0 1985-01-01 France FRA 56665619.0 1986-01-01 France FRA 56956002.0 1987-01-01 France FRA 57266167.0 1988-01-01 France FRA 57598186.0 1989-01-01 France FRA 57943062.0 1990-01-01 France FRA 58261012.0 1991-01-01 France FRA 58554242.0 1992-01-01 France FRA 58846584.0 1993-01-01 France FRA 59103094.0 1994-01-01 France FRA 59324863.0 1995-01-01 France FRA 59541294.0 1996-01-01 France FRA 59754504.0 1997-01-01 France FRA 59968066.0 1998-01-01 France FRA 60190684.0 1999-01-01 France FRA 60501986.0 2000-01-01 France FRA 60918661.0 2001-01-01 France FRA 61364377.0 2002-01-01 France FRA 61812142.0 2003-01-01 France FRA 62249855.0 2004-01-01 France FRA 62707588.0 2005-01-01 France FRA 63180854.0 2006-01-01 France FRA 63622342.0 2007-01-01 France FRA 64016890.0 2008-01-01 France FRA 64375116.0 2009-01-01 France FRA 64706436.0 2010-01-01 France FRA 65026211.0>
# on peut maintenant dessiner

df_france[['population']].plot();
<Figure size 640x480 with 1 Axes>