<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dev du Sud</title>
    <description>The latest articles on DEV Community by Dev du Sud (@devdusud).</description>
    <link>https://dev.to/devdusud</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4144889%2Fead5322a-de85-4109-8a26-f9854b867d52.jpg</url>
      <title>DEV Community: Dev du Sud</title>
      <link>https://dev.to/devdusud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devdusud"/>
    <language>en</language>
    <item>
      <title>Publier sur Dev.to depuis un serveur, avec 130 lignes de Python et zéro dépendance</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:40:55 +0000</pubDate>
      <link>https://dev.to/devdusud/publier-sur-devto-depuis-un-serveur-avec-130-lignes-de-python-et-zero-dependance-2ec2</link>
      <guid>https://dev.to/devdusud/publier-sur-devto-depuis-un-serveur-avec-130-lignes-de-python-et-zero-dependance-2ec2</guid>
      <description>&lt;p&gt;Écrire dans un éditeur web, coller son markdown, choisir ses tags, cliquer sur publier :&lt;br&gt;
quand on écrit une fois par mois, c'est très bien. Quand on écrit deux fois par semaine, ça&lt;br&gt;
devient une corvée — et les corvées finissent par ne plus être faites.&lt;/p&gt;

&lt;p&gt;Mon besoin était simple : j'écris un fichier &lt;code&gt;.md&lt;/code&gt; dans un dossier, un job planifié le publie&lt;br&gt;
le lendemain matin à 8 h 40. Pas d'interface, pas de SDK, pas de dépendance à installer sur un&lt;br&gt;
serveur qui doit rester debout pendant des années.&lt;/p&gt;

&lt;p&gt;Voici le pipeline complet, avec les erreurs que j'ai réellement rencontrées.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ce dont l'API a besoin
&lt;/h2&gt;

&lt;p&gt;Trois choses, et la troisième est celle qu'on oublie.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;La clé API, dans l'en-tête &lt;code&gt;api-key&lt;/code&gt;. Elle se génère dans les paramètres du compte Dev.to.&lt;/li&gt;
&lt;li&gt;L'URL : &lt;code&gt;https://dev.to/api/articles&lt;/code&gt; pour créer, &lt;code&gt;https://dev.to/api/articles/&amp;lt;id&amp;gt;&lt;/code&gt; pour
modifier.&lt;/li&gt;
&lt;li&gt;Un en-tête &lt;code&gt;User-Agent&lt;/code&gt;. Sans lui, vous prenez un &lt;code&gt;403&lt;/code&gt; et vous cherchez pendant vingt
minutes pourquoi votre clé « ne marche pas ».&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;La clé doit vivre dans un fichier d'environnement, jamais dans le script :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxxxxxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Lit un .env minimaliste : KEY=VALUE, commentaires en #.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Étape 1 — lire le front-matter sans PyYAML
&lt;/h2&gt;

&lt;p&gt;Un article, c'est un fichier markdown avec un en-tête entre deux &lt;code&gt;---&lt;/code&gt;. Pas besoin d'un&lt;br&gt;
analyseur YAML complet : trois champs suffisent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le fichier ressemble à ça :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Un titre qui donne envie de cliquer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;La&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;phrase&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;que&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dev.to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;affichera&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dans&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;les&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;résultats."&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python,automation,tutorial&lt;/span&gt;
&lt;span class="na"&gt;cover_image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://exemple.fr/couverture.jpg&lt;/span&gt;
&lt;span class="na"&gt;canonical_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://mon-site.fr/mon-article&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Le corps, en markdown normal.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Étape 2 — le piège du titre vide
&lt;/h2&gt;

&lt;p&gt;C'est l'erreur que j'ai réellement commise, et elle est publique : un fichier sans champ&lt;br&gt;
&lt;code&gt;title&lt;/code&gt; a été publié sous le titre « Sans titre ». Un fichier de brouillons de posts pour les&lt;br&gt;
réseaux sociaux s'était retrouvé dans le dossier de la file, et le script l'a pris pour un&lt;br&gt;
article.&lt;/p&gt;

&lt;p&gt;Depuis, la validation se fait &lt;strong&gt;avant&lt;/strong&gt; tout appel réseau :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fm&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SAUTÉ (pas de titre): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;           &lt;span class="c1"&gt;# on ne publie pas, on passe au suivant
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deux lignes. Elles valent leur pesant de réputation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Étape 3 — les tags valides, sinon 422
&lt;/h2&gt;

&lt;p&gt;Dev.to n'accepte que des tags de sa liste. Un tag inconnu renvoie&lt;br&gt;
&lt;code&gt;422 — tag not found&lt;/code&gt;, et l'article n'est pas créé. Le plus simple est de filtrer contre une&lt;br&gt;
liste blanche :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;VALID_TAGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;javascript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;productivity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;automation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;programming&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tutorial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;beginners&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;webdev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linux&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;career&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opensource&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;devops&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;performance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;testing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;startup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discuss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;showdev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watercooler&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;architecture&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;codequality&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;comparison&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VALID_TAGS&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discuss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quatre tags maximum, c'est la limite de l'API. Les filtrer en silence vaut mieux qu'un échec&lt;br&gt;
de publication pour une virgule de trop.&lt;/p&gt;
&lt;h2&gt;
  
  
  Étape 4 — créer en brouillon, puis publier
&lt;/h2&gt;

&lt;p&gt;Publier directement, c'est un seul appel : &lt;code&gt;POST&lt;/code&gt; avec &lt;code&gt;"published": true&lt;/code&gt;. Ça marche… quand&lt;br&gt;
l'API va bien. Aux heures de pointe, j'ai vu passer des &lt;code&gt;503&lt;/code&gt; sur cet appel, et un &lt;code&gt;503&lt;/code&gt; sur un&lt;br&gt;
&lt;code&gt;POST&lt;/code&gt; non idempotent laisse un doute désagréable : l'article a-t-il été créé, ou pas ?&lt;/p&gt;

&lt;p&gt;Le pattern qui tient : créer le brouillon, récupérer son &lt;code&gt;id&lt;/code&gt;, puis publier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (X11; Linux x86_64) MonBot/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1) brouillon
&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;art_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# 2) publication, avec deux reprises sur 5xx
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;art_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un &lt;code&gt;PUT&lt;/code&gt; est idempotent : le rejouer ne crée rien en double. C'est toute la raison de cette&lt;br&gt;
danse en deux temps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Étape 5 — un fichier d'état, sinon vous publiez deux fois
&lt;/h2&gt;

&lt;p&gt;Le job tourne une fois par jour, mais il peut tourner deux fois : un redémarrage, un&lt;br&gt;
déclenchement manuel, une reprise après panne. Un petit JSON suffit à rendre le script&lt;br&gt;
idempotent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;STATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.hermes/scripts/devto-publish-state.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STATE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;déjà publié aujourd&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hui (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;

&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;
&lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                      &lt;span class="c1"&gt;# un article par jour, dans l'ordre alphabétique
# … publication …
&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STATE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.tmp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;STATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;               &lt;span class="c1"&gt;# écriture atomique
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;os.replace&lt;/code&gt; plutôt qu'une écriture directe : si la machine s'éteint au mauvais moment, on&lt;br&gt;
garde l'ancien état au lieu d'un fichier tronqué.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le cron, et la seule ligne qui compte
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;40 8 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; flock &lt;span class="nt"&gt;-n&lt;/span&gt; /tmp/devto.lock /usr/bin/python3 &lt;span class="se"&gt;\&lt;/span&gt;
  /home/moi/.hermes/scripts/publish-devto.py &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /home/moi/logs/devto.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Une fois par jour, à heure fixe, avec un verrou. Et lisez ce log de temps en temps : c'est&lt;br&gt;
tout l'objet de mon article précédent sur les crons qui meurent en silence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Les quatre erreurs que j'ai vues en vrai
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;Cause réelle&lt;/th&gt;
&lt;th&gt;Réaction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;403&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;En-tête &lt;code&gt;User-Agent&lt;/code&gt; absent&lt;/td&gt;
&lt;td&gt;L'ajouter, toujours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;401&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;unauthorized&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clé absente, expirée ou révoquée&lt;/td&gt;
&lt;td&gt;Régénérer la clé&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;422&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tag not found&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tag hors liste blanche&lt;/td&gt;
&lt;td&gt;Filtrer les tags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;422&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Title has already been used in the last five minutes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Doublon de titre&lt;/td&gt;
&lt;td&gt;Attendre, ou varier le titre&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;503&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;API surchargée&lt;/td&gt;
&lt;td&gt;Réessayer avec un backoff&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Le &lt;code&gt;422&lt;/code&gt; sur le titre m'a coûté une soirée : mes deux tentatives de reprise ré-envoyaient le&lt;br&gt;
même titre, donc échouaient de la même façon. Une reprise ne doit se déclencher que sur une&lt;br&gt;
erreur &lt;strong&gt;transitoire&lt;/strong&gt; — jamais sur une erreur de contrat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que ça donne à l'usage
&lt;/h2&gt;

&lt;p&gt;J'écris un fichier markdown, je le nomme &lt;code&gt;11-mon-sujet.md&lt;/code&gt;, je le pose dans le dossier. Le&lt;br&gt;
lendemain à 8 h 40 il est en ligne, avec ses tags et sa couverture. Le fichier est marqué&lt;br&gt;
comme publié, et il ne repartira pas.&lt;/p&gt;

&lt;p&gt;Cent trente lignes, zéro dépendance installée, et un pipeline que je peux encore lire dans&lt;br&gt;
deux ans. C'est exactement ce que je demande à un outil interne.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Votre cron est mort depuis trois semaines et personne ne vous a rien dit</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Sun, 01 Nov 2026 19:55:11 +0000</pubDate>
      <link>https://dev.to/devdusud/votre-cron-est-mort-depuis-trois-semaines-et-personne-ne-vous-a-rien-dit-1fpc</link>
      <guid>https://dev.to/devdusud/votre-cron-est-mort-depuis-trois-semaines-et-personne-ne-vous-a-rien-dit-1fpc</guid>
      <description>&lt;p&gt;Un job planifié ne tombe pas en panne comme un site web. Un site web qui tombe, on le voit :&lt;br&gt;
les visiteurs écrivent, les statistiques s'effondrent. Un cron qui tombe, lui, produit exactement&lt;br&gt;
la même chose qu'un cron qui n'avait rien à faire : du silence.&lt;/p&gt;

&lt;p&gt;Je l'ai découvert en ouvrant le fichier des tâches planifiées de ma machine. Une publication&lt;br&gt;
automatique d'articles était en état « pause » depuis le 16 octobre à 14 h 26. Treize exécutions&lt;br&gt;
consécutives en échec avant ça, du 4 au 16 octobre. Personne n'avait été prévenu — et surtout,&lt;br&gt;
rien ne s'était arrêté : les autres jobs continuaient, la machine était en bonne santé, tout&lt;br&gt;
avait l'air normal.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pourquoi un job meurt sans bruit
&lt;/h2&gt;

&lt;p&gt;Trois causes, et dans mon cas les trois en même temps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. La panne réseau.&lt;/strong&gt; Les journaux montrent une résolution DNS impossible au moment du&lt;br&gt;
déclenchement : &lt;code&gt;URLError&lt;/code&gt;, puis &lt;code&gt;Name or service not known&lt;/code&gt;. Le job a échoué treize fois&lt;br&gt;
de suite, à la même heure, chaque matin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. L'ordonnanceur qui se protège.&lt;/strong&gt; Après un certain nombre d'échecs consécutifs, un&lt;br&gt;
scheduler correct met le job en pause. C'est la bonne décision : ça évite de marteler une API&lt;br&gt;
morte. Mais la mise en pause n'est pas forcément visible pour un humain, et rien ne garantit&lt;br&gt;
qu'on la remarque un jour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. La sortie que personne ne lit.&lt;/strong&gt; Un script qui écrit dans un fichier de log ne prévient&lt;br&gt;
personne. Un script qui sort en code 0 alors qu'il n'a rien fait prévient encore moins.&lt;/p&gt;

&lt;p&gt;Il existe un quatrième cas, plus vicieux : le script tourne, sort en 0, et ne fait rien. Le&lt;br&gt;
cas classique, c'est le dossier vide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file vide&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# ← le drame est ici
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tout va bien. Surtout pour le monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Garde-fou 1 — un battement de cœur horodaté
&lt;/h2&gt;

&lt;p&gt;Un fichier, écrit &lt;strong&gt;uniquement en cas de succès réel&lt;/strong&gt;, avec l'horodatage à l'intérieur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;

&lt;span class="n"&gt;HEARTBEAT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.local/state/mon-job-last-success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;marquer_succes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;HEARTBEAT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;HEARTBEAT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Règle : on écrit le heartbeat à la fin, après l'action réussie. Pas au début, pas « à peu&lt;br&gt;
près », pas « si tout s'est bien passé jusqu'ici ». Si le script plante au milieu, aucun&lt;br&gt;
heartbeat — et c'est précisément l'information qu'on veut.&lt;/p&gt;
&lt;h2&gt;
  
  
  Garde-fou 2 — le chien de garde qui surveille le chien de garde
&lt;/h2&gt;

&lt;p&gt;Un second job, indépendant, qui ne fait qu'une chose : mesurer l'âge du heartbeat et crier&lt;br&gt;
si l'âge dépasse le seuil.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;HB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.local/state/mon-job-last-success"&lt;/span&gt;
&lt;span class="nv"&gt;SEUIL_H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;   &lt;span class="c"&gt;# 3 jours pour une publication quotidienne&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;notify-send &lt;span class="s2"&gt;"Job: aucun heartbeat n'a jamais été écrit"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
  exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;AGE_H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; %Y &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AGE_H&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SEUIL_H&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;notify-send &lt;span class="s2"&gt;"Job muet depuis &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AGE_H&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; h (seuil &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SEUIL_H&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; h)"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ok — dernier succès il y a &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AGE_H&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; h"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ce garde-fou ne répare rien : il &lt;strong&gt;révèle&lt;/strong&gt;. Dans mon cas, il aurait parlé le 16 octobre au&lt;br&gt;
soir au lieu du 24.&lt;/p&gt;

&lt;p&gt;Le seuil est la seule décision qui compte. Trop court, on crie pour un job hebdomadaire&lt;br&gt;
parfaitement normal ; trop long, on découvre la panne quand le stock est déjà épuisé. Pour une&lt;br&gt;
publication quotidienne, trois jours laissent une marge honnête : un week-end de panne réseau&lt;br&gt;
ne déclenche rien, trois jours de silence déclenchent tout.&lt;/p&gt;

&lt;p&gt;Si vous ne voulez pas écrire ce garde-fou, les services de &lt;em&gt;dead man's switch&lt;/em&gt; existent&lt;br&gt;
(healthchecks.io, ou Uptime Kuma en auto-hébergé) : le job envoie un ping en cas de succès, le&lt;br&gt;
service crie quand le ping manque. C'est la même idée, hébergée ailleurs — et c'est plus&lt;br&gt;
robuste, puisque la surveillance ne meurt pas avec la machine surveillée.&lt;/p&gt;
&lt;h2&gt;
  
  
  Garde-fou 3 — distinguer « rien à faire » de « je n'ai pas pu faire »
&lt;/h2&gt;

&lt;p&gt;C'est tout le piège du &lt;code&gt;file vide&lt;/code&gt;. Un job qui n'avait rien à faire et un job cassé doivent&lt;br&gt;
produire deux signaux différents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALERTE file vide&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# ni succès, ni panne : problème de stock
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avec un code de sortie non nul, le scheduler marque l'exécution en échec et la remonte. Trois&lt;br&gt;
codes suffisent pour se comprendre entre humains :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Sens&lt;/th&gt;
&lt;th&gt;Réaction attendue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Succès, quelque chose a été fait&lt;/td&gt;
&lt;td&gt;Rien&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Erreur technique (réseau, API, disque)&lt;/td&gt;
&lt;td&gt;Regarder les logs, réessayer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rien à faire, et c'est anormal&lt;/td&gt;
&lt;td&gt;Recharger la file, réapprovisionner&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;La différence entre &lt;code&gt;1&lt;/code&gt; et &lt;code&gt;3&lt;/code&gt; est la plus utile du lot : elle dit si on doit réparer la&lt;br&gt;
plomberie ou remplir le réservoir.&lt;/p&gt;
&lt;h2&gt;
  
  
  Garde-fou 4 — tester le contrat, pas seulement le script
&lt;/h2&gt;

&lt;p&gt;Un script testé une fois en septembre ne dit rien de son comportement en novembre. Deux&lt;br&gt;
vérifications automatiques, une fois par semaine :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) le script est-il encore démarrable ?&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; py_compile &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.hermes/scripts/mon-job.py"&lt;/span&gt;

&lt;span class="c"&gt;# 2) a-t-il encore de quoi travailler ?&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/devto-articles"&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.md 2&amp;gt;/dev/null | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si le second nombre vaut zéro, le job va tourner pour rien. C'est exactement ce qui s'est&lt;br&gt;
passé chez moi : les fichiers déjà publiés avaient été déplacés dans un sous-dossier&lt;br&gt;
&lt;code&gt;publies/&lt;/code&gt;, et le script ne regarde que la racine du dossier. Zéro candidat, tous les jours,&lt;br&gt;
sans la moindre erreur.&lt;/p&gt;

&lt;p&gt;Ajoutez &lt;code&gt;flock&lt;/code&gt; pour éviter deux exécutions simultanées, et &lt;code&gt;set -euo pipefail&lt;/code&gt; pour que&lt;br&gt;
l'échec d'une commande intermédiaire ne soit pas avalée :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flock &lt;span class="nt"&gt;-n&lt;/span&gt; /tmp/mon-job.lock /usr/bin/python3 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.hermes/scripts/mon-job.py"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Le détail qui m'a coûté le plus de temps
&lt;/h2&gt;

&lt;p&gt;Deux runs sur treize ont échoué pour une raison différente du réseau :&lt;br&gt;
&lt;code&gt;422 — Title has already been used in the last five minutes&lt;/code&gt;. L'API refuse un titre identique&lt;br&gt;
dans une fenêtre de cinq minutes, et mon script réessayait avec le même titre. Un type d'échec&lt;br&gt;
sur deux est invisible dans un compteur global, et il suffit à casser un job.&lt;/p&gt;

&lt;p&gt;Leçon : un échec n'est pas un échec. &lt;code&gt;503&lt;/code&gt; (serveur fatigué), &lt;code&gt;422&lt;/code&gt; (contrat violé), &lt;code&gt;401&lt;/code&gt;&lt;br&gt;
(clé expirée) demandent trois réactions opposées — réessayer, corriger, réveiller un humain.&lt;br&gt;
Les traiter pareil, c'est garantir qu'aucun des trois ne sera bien traité.&lt;/p&gt;

&lt;h2&gt;
  
  
  La checklist que j'applique maintenant
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Un heartbeat écrit &lt;strong&gt;après&lt;/strong&gt; le succès, jamais avant.&lt;/li&gt;
&lt;li&gt;[ ] Un garde-fou indépendant qui mesure l'âge du heartbeat.&lt;/li&gt;
&lt;li&gt;[ ] Trois codes de sortie : &lt;code&gt;0&lt;/code&gt; succès, &lt;code&gt;1&lt;/code&gt; panne, &lt;code&gt;3&lt;/code&gt; stock vide.&lt;/li&gt;
&lt;li&gt;[ ] Un test hebdomadaire du contrat : le script compile, la file n'est pas vide.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;flock&lt;/code&gt; sur chaque job qui touche à un état partagé.&lt;/li&gt;
&lt;li&gt;[ ] La sortie du job &lt;strong&gt;lue par quelque chose&lt;/strong&gt;, jamais seulement écrite sur disque.&lt;/li&gt;
&lt;li&gt;[ ] Un job mis en pause par le scheduler déclenche une alerte, pas un silence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ce que ça change
&lt;/h2&gt;

&lt;p&gt;Rien de spectaculaire : un heartbeat, un chien de garde, trois codes de sortie, un test&lt;br&gt;
hebdomadaire. Une quarantaine de lignes en tout. Mais depuis, le silence est devenu une&lt;br&gt;
information.&lt;/p&gt;

&lt;p&gt;Un job planifié n'est pas un service qui tourne. C'est une promesse renouvelée chaque nuit —&lt;br&gt;
et une promesse non tenue ne fait aucun bruit. C'est à vous d'installer le bruit.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Sauvegarder ses fichiers importants automatiquement avec Python</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Fri, 23 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/devdusud/sauvegarder-ses-fichiers-importants-automatiquement-avec-python-271i</link>
      <guid>https://dev.to/devdusud/sauvegarder-ses-fichiers-importants-automatiquement-avec-python-271i</guid>
      <description>&lt;h2&gt;
  
  
  Le jour où on regrette
&lt;/h2&gt;

&lt;p&gt;On pense souvent aux sauvegardes trop tard. Le disque dur tombe en panne, l'ordinateur refuse de démarrer, un fichier est supprimé par erreur, ou un dossier client disparaît. À ce moment-là, on se dit : « j'aurais dû faire une copie ».&lt;/p&gt;

&lt;p&gt;La bonne nouvelle, c'est qu'une sauvegarde simple n'a pas besoin d'être compliquée. Avec Python, on peut copier automatiquement un dossier important vers un autre emplacement : un disque externe, un dossier synchronisé, ou un NAS à la maison.&lt;/p&gt;

&lt;p&gt;Ce tuto montre une sauvegarde basique, lisible, et adaptée aux débutants. Ce n'est pas une solution professionnelle complète, mais c'est déjà beaucoup mieux que rien.&lt;/p&gt;

&lt;h2&gt;
  
  
  La règle 3-2-1 en version simple
&lt;/h2&gt;

&lt;p&gt;On entend souvent parler de la règle 3-2-1 : trois copies, sur deux supports différents, dont une hors de la maison ou de l'entreprise. Pour commencer, retenez surtout ceci : une seule copie sur le même ordinateur ne suffit pas.&lt;/p&gt;

&lt;p&gt;Si votre dossier original et votre sauvegarde sont sur le même disque, une panne peut emporter les deux. L'idéal est de copier vers un support différent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le script Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;SOURCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documents-importants&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DESTINATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sauvegardes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;horodatage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d_%H-%M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dossier_backup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DESTINATION&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;horodatage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;SOURCE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Le dossier source n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;existe pas : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SOURCE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copytree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SOURCE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dossier_backup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sauvegarde créée dans : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dossier_backup&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ce que fait ce script
&lt;/h2&gt;

&lt;p&gt;Le dossier &lt;code&gt;documents-importants&lt;/code&gt; est le dossier à protéger. Le dossier &lt;code&gt;sauvegardes&lt;/code&gt; est l'endroit où Python va créer une copie. À chaque lancement, le script ajoute la date et l'heure dans le nom du dossier, par exemple &lt;code&gt;backup_2026-10-11_18-30&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C'est très pratique, car on garde plusieurs versions. Si vous avez modifié un fichier hier et que vous regrettez aujourd'hui, vous pouvez retrouver une ancienne copie.&lt;/p&gt;

&lt;p&gt;La fonction &lt;code&gt;copytree&lt;/code&gt; copie tout le dossier. Elle est simple et efficace. Pour de très gros volumes, il existe des méthodes plus avancées qui ne copient que les fichiers modifiés. Mais pour apprendre et protéger un petit dossier, cette version est parfaite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment l'adapter
&lt;/h2&gt;

&lt;p&gt;Remplacez &lt;code&gt;documents-importants&lt;/code&gt; par le chemin de votre vrai dossier. Par exemple, cela peut être un dossier &lt;code&gt;Clients&lt;/code&gt;, &lt;code&gt;Factures&lt;/code&gt;, &lt;code&gt;Photos famille&lt;/code&gt;, ou &lt;code&gt;Administratif&lt;/code&gt;. Remplacez aussi &lt;code&gt;sauvegardes&lt;/code&gt; par votre disque externe si vous en avez un.&lt;/p&gt;

&lt;p&gt;Sur Windows, un chemin peut ressembler à &lt;code&gt;D:/Sauvegardes&lt;/code&gt;. Sur Linux ou Mac, il peut ressembler à &lt;code&gt;/media/ludo/DisqueUSB/Sauvegardes&lt;/code&gt;. Le plus important est de tester avec un petit dossier avant de viser vos vrais fichiers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatiser vraiment
&lt;/h2&gt;

&lt;p&gt;Un script qu'on oublie de lancer ne sert pas à grand-chose. L'étape suivante consiste donc à le lancer automatiquement. Sur Windows, on peut utiliser le Planificateur de tâches. Sur Linux ou Mac, on peut utiliser cron ou un service équivalent.&lt;/p&gt;

&lt;p&gt;Choisissez un rythme réaliste. Pour des fichiers très importants, une sauvegarde quotidienne peut être utile. Pour des documents moins actifs, une fois par semaine suffit peut-être.&lt;/p&gt;

&lt;h2&gt;
  
  
  Les erreurs fréquentes
&lt;/h2&gt;

&lt;p&gt;Première erreur : ne jamais vérifier les sauvegardes. Une sauvegarde doit être testée. Ouvrez quelques fichiers copiés pour confirmer qu'ils sont lisibles.&lt;/p&gt;

&lt;p&gt;Deuxième erreur : laisser le disque externe toujours branché. Si un ransomware chiffre vos fichiers, il peut aussi toucher le disque branché. Pour une vraie protection, branchez le disque, lancez ou laissez faire la sauvegarde, puis débranchez-le.&lt;/p&gt;

&lt;p&gt;Troisième erreur : confondre synchronisation et sauvegarde. Un service cloud qui synchronise supprime parfois aussi le fichier partout si vous le supprimez par erreur. C'est pratique, mais ce n'est pas toujours une sauvegarde historique.&lt;/p&gt;

&lt;h2&gt;
  
  
  À retenir
&lt;/h2&gt;

&lt;p&gt;Une sauvegarde simple aujourd'hui vaut mieux qu'un plan parfait jamais mis en place. Commencez avec un dossier important, une copie datée, et une vérification manuelle. Ensuite, vous pourrez automatiser le lancement.&lt;/p&gt;

&lt;p&gt;Python ne rend pas vos données invincibles, mais il peut vous éviter le grand moment de solitude du fichier perdu.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Générer un PDF de rapport ou de facture sans acheter de logiciel</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Wed, 21 Oct 2026 22:14:54 +0000</pubDate>
      <link>https://dev.to/devdusud/generer-un-pdf-de-rapport-ou-de-facture-sans-acheter-de-logiciel-2ee2</link>
      <guid>https://dev.to/devdusud/generer-un-pdf-de-rapport-ou-de-facture-sans-acheter-de-logiciel-2ee2</guid>
      <description>&lt;h2&gt;
  
  
  Pourquoi générer un PDF soi-même
&lt;/h2&gt;

&lt;p&gt;Beaucoup de petites activités ont besoin de documents simples : une facture, un compte rendu d'intervention, un rapport de visite, un devis rapide, une fiche client. On peut le faire dans Word ou LibreOffice, bien sûr. Mais quand le même document revient souvent, remplir les mêmes lignes à la main devient vite pénible.&lt;/p&gt;

&lt;p&gt;Python peut générer un PDF automatiquement à partir de quelques informations : nom du client, date, lignes de prestation, total. Pas besoin d'acheter un logiciel compliqué pour comprendre le principe.&lt;/p&gt;

&lt;p&gt;Dans ce tuto, on va créer une mini facture très simple. Elle ne remplace pas un logiciel de comptabilité complet, surtout si vous avez des obligations légales précises. Mais elle montre comment produire un PDF propre, répétable, et facile à adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  L'outil utilisé
&lt;/h2&gt;

&lt;p&gt;On va utiliser &lt;code&gt;reportlab&lt;/code&gt;, une bibliothèque Python gratuite très connue pour créer des PDF. Si elle n'est pas installée, il faudra l'ajouter une fois sur votre ordinateur. Ensuite, le script pourra générer autant de PDF que vous voulez.&lt;/p&gt;

&lt;p&gt;Le principe est très simple : on crée une page, on écrit du texte à des positions précises, puis on sauvegarde le fichier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le script Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reportlab.lib.pagesizes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;A4&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reportlab.pdfgen&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;canvas&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reportlab.lib.units&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Boulangerie du Port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;numero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAC-2026-001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;lignes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Diagnostic ordinateur caisse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;49.00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sauvegarde des fichiers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;39.00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Nettoyage logiciel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;29.00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;fichier_pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;facture-exemple.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fichier_pdf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pagesize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;A4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;largeur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hauteur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A4&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica-Bold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hauteur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Facture&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hauteur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Numéro : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numero&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hauteur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;3.7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Client : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hauteur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;4.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;largeur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hauteur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;4.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hauteur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;5.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prix&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lignes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;montant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quantite&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;prix&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;montant&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawRightString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quantite&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; x &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prix&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; €&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawRightString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;montant&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; €&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;largeur&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica-Bold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawRightString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; €&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Document généré automatiquement avec Python.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PDF créé : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fichier_pdf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ce que fait le script
&lt;/h2&gt;

&lt;p&gt;La liste &lt;code&gt;lignes&lt;/code&gt; contient les prestations. Chaque ligne a trois informations : le texte, la quantité, et le prix unitaire. Python calcule le total tout seul. Si vous ajoutez une prestation, le montant final change automatiquement.&lt;/p&gt;

&lt;p&gt;Les coordonnées peuvent sembler étranges au début. Dans un PDF, on place les éléments sur une page, un peu comme sur une feuille avec une règle. &lt;code&gt;2 * cm&lt;/code&gt; signifie simplement « deux centimètres depuis le bord ». On n'a pas besoin de tout comprendre d'un coup : commencez par modifier les textes et les prix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment l'utiliser au quotidien
&lt;/h2&gt;

&lt;p&gt;Vous pouvez transformer ce script en modèle. Par exemple, un réparateur peut changer le nom du client et les prestations après chaque intervention. Une association peut générer un rapport mensuel. Un freelance peut produire un devis simple.&lt;/p&gt;

&lt;p&gt;Le vrai intérêt arrive quand les données viennent d'un fichier. Imaginez un tableau avec tous les clients du mois. Python pourrait lire chaque ligne et générer un PDF par client. Là, le gain de temps devient énorme.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attention aux obligations légales
&lt;/h2&gt;

&lt;p&gt;Une facture réelle doit respecter des règles : identité de l'entreprise, numéro unique, dates, mentions, TVA selon votre situation, conditions de paiement, et parfois d'autres informations. Ce tuto ne donne pas un modèle légal complet. Il donne une base technique.&lt;/p&gt;

&lt;p&gt;Si vous voulez l'utiliser pour une vraie activité, vérifiez les mentions obligatoires avec une source officielle ou votre comptable. L'automatisation ne dispense pas d'être carré.&lt;/p&gt;

&lt;h2&gt;
  
  
  À retenir
&lt;/h2&gt;

&lt;p&gt;Créer un PDF avec Python, ce n'est pas réservé aux grosses entreprises. Pour des documents répétitifs, c'est même une excellente porte d'entrée dans l'automatisation. Vous commencez avec une facture simple, puis vous ajoutez petit à petit votre logo, vos couleurs, vos conditions, ou vos données clients.&lt;/p&gt;

&lt;p&gt;Le but n'est pas de faire joli dès le premier jour. Le but est d'arrêter de recopier les mêmes informations à la main.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Trier et classer automatiquement ses photos et documents avec Python</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Wed, 21 Oct 2026 22:14:44 +0000</pubDate>
      <link>https://dev.to/devdusud/trier-et-classer-automatiquement-ses-photos-et-documents-avec-python-b9j</link>
      <guid>https://dev.to/devdusud/trier-et-classer-automatiquement-ses-photos-et-documents-avec-python-b9j</guid>
      <description>&lt;h2&gt;
  
  
  Le grand dossier en vrac
&lt;/h2&gt;

&lt;p&gt;Sur beaucoup d'ordinateurs, il existe un dossier qui fait peur. Il s'appelle souvent &lt;code&gt;Téléchargements&lt;/code&gt;, &lt;code&gt;Bureau&lt;/code&gt;, ou &lt;code&gt;À trier&lt;/code&gt;. Dedans, on trouve des photos, des PDF, des captures d'écran, des factures, des fichiers Word, des archives ZIP, parfois même des choses qu'on ne reconnaît plus.&lt;/p&gt;

&lt;p&gt;Ranger tout ça à la main prend du temps. Et comme ce n'est pas très amusant, on repousse. Résultat : le dossier grossit encore.&lt;/p&gt;

&lt;p&gt;Python peut aider à faire un premier tri automatique. L'idée n'est pas de deviner parfaitement le contenu de chaque document. L'idée est plus simple : classer les fichiers par famille grâce à leur extension. Les &lt;code&gt;.jpg&lt;/code&gt; vont dans &lt;code&gt;Photos&lt;/code&gt;, les &lt;code&gt;.pdf&lt;/code&gt; dans &lt;code&gt;PDF&lt;/code&gt;, les &lt;code&gt;.docx&lt;/code&gt; dans &lt;code&gt;Documents&lt;/code&gt;, et ainsi de suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi commencer par les extensions
&lt;/h2&gt;

&lt;p&gt;L'extension, c'est la fin du nom du fichier : &lt;code&gt;.jpg&lt;/code&gt;, &lt;code&gt;.pdf&lt;/code&gt;, &lt;code&gt;.xlsx&lt;/code&gt;, &lt;code&gt;.png&lt;/code&gt;. Ce n'est pas parfait, mais c'est déjà très utile. Pour un premier rangement, on n'a pas besoin d'intelligence artificielle. Une règle claire suffit.&lt;/p&gt;

&lt;p&gt;Avant de lancer le script sur votre vrai dossier &lt;code&gt;Téléchargements&lt;/code&gt;, copiez quelques fichiers dans un dossier de test. C'est une bonne habitude : on teste petit, puis on applique plus grand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le script Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;

&lt;span class="n"&gt;DOSSIER_A_TRIER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a-trier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MODE_TEST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;CATEGORIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Photos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.gif&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.webp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PDF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.doc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.docx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.odt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tableurs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.xls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.xlsx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Archives&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.rar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.7z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trouver_categorie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;categorie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extensions&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CATEGORIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;categorie&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Autres&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fichier&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DOSSIER_A_TRIER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="n"&gt;categorie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;trouver_categorie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;dossier_destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DOSSIER_A_TRIER&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;categorie&lt;/span&gt;
    &lt;span class="n"&gt;nouvelle_adresse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dossier_destination&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;categorie&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;MODE_TEST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;dossier_destination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nouvelle_adresse&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comment utiliser ce script
&lt;/h2&gt;

&lt;p&gt;Créez un dossier &lt;code&gt;a-trier&lt;/code&gt;, mettez une dizaine de fichiers dedans, puis lancez le script. Au départ, &lt;code&gt;MODE_TEST&lt;/code&gt; est à &lt;code&gt;True&lt;/code&gt;. Le script affiche ce qu'il ferait, mais ne déplace rien. C'est parfait pour vérifier que les catégories vous conviennent.&lt;/p&gt;

&lt;p&gt;Quand le résultat affiché semble bon, passez &lt;code&gt;MODE_TEST&lt;/code&gt; à &lt;code&gt;False&lt;/code&gt;. Le script créera les dossiers nécessaires et déplacera les fichiers.&lt;/p&gt;

&lt;p&gt;Si vous voulez ajouter une catégorie, modifiez simplement le dictionnaire &lt;code&gt;CATEGORIES&lt;/code&gt;. Par exemple, vous pouvez créer une catégorie &lt;code&gt;Images boutique&lt;/code&gt; pour les &lt;code&gt;.jpg&lt;/code&gt; et &lt;code&gt;.png&lt;/code&gt;, ou une catégorie &lt;code&gt;Compta&lt;/code&gt; pour certains fichiers si vous adaptez ensuite la règle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Les limites à connaître
&lt;/h2&gt;

&lt;p&gt;Ce script ne lit pas le contenu des documents. Un PDF de facture et un PDF de menu restaurant iront tous les deux dans &lt;code&gt;PDF&lt;/code&gt;. Pour faire mieux, il faudrait analyser le texte, les dates, ou les noms de fichiers. C'est possible, mais ce serait une étape suivante.&lt;/p&gt;

&lt;p&gt;Autre limite : si un fichier du même nom existe déjà dans le dossier de destination, le déplacement peut poser problème. Pour un usage sérieux, on peut ajouter une règle qui renomme automatiquement le fichier en ajoutant un numéro. Pour débuter, gardons le script lisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Une bonne méthode de rangement
&lt;/h2&gt;

&lt;p&gt;L'automatisation ne remplace pas une bonne logique. Avant de trier, demandez-vous quelles catégories vous voulez vraiment. Trop de dossiers, c'est aussi pénible que pas assez. Pour beaucoup de personnes, cinq ou six familles suffisent : Photos, PDF, Documents, Tableurs, Archives, Autres.&lt;/p&gt;

&lt;p&gt;Ensuite, lancez ce script une fois par semaine. Votre dossier &lt;code&gt;Téléchargements&lt;/code&gt; ne deviendra plus une décharge numérique.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi c'est utile pour un non-développeur
&lt;/h2&gt;

&lt;p&gt;Ce type de script donne une sensation très agréable : l'ordinateur fait enfin ce qu'on lui demande. Pas besoin de comprendre des concepts compliqués. On écrit une règle simple, puis Python l'applique sans se lasser.&lt;/p&gt;

&lt;p&gt;Pour une petite entreprise, ce rangement peut éviter de perdre des factures, des photos produits ou des documents clients. Pour un particulier, ça rend juste l'ordinateur plus respirable.&lt;/p&gt;

&lt;h2&gt;
  
  
  À retenir
&lt;/h2&gt;

&lt;p&gt;Un bon tri automatique commence petit. Une copie, quelques fichiers, un mode test, puis seulement après le vrai dossier. Avec cette méthode, vous avancez sans stress et vous gardez le contrôle.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Scraper une page web pour récupérer des infos en restant poli</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Wed, 21 Oct 2026 21:18:58 +0000</pubDate>
      <link>https://dev.to/devdusud/scraper-une-page-web-pour-recuperer-des-infos-en-restant-poli-4akp</link>
      <guid>https://dev.to/devdusud/scraper-une-page-web-pour-recuperer-des-infos-en-restant-poli-4akp</guid>
      <description>&lt;h2&gt;
  
  
  Scraper, ça veut dire quoi ?
&lt;/h2&gt;

&lt;p&gt;Scraper une page web, c'est demander à Python de lire une page internet et d'en extraire une information. Par exemple : le titre d'un article, le prix affiché sur une page produit, une liste d'horaires, ou les liens présents dans une page.&lt;/p&gt;

&lt;p&gt;C'est pratique, mais ce n'est pas une excuse pour aspirer tout un site comme un sauvage. Derrière un site, il y a un serveur, une personne, une entreprise, parfois un petit commerçant. Un script mal réglé peut envoyer trop de requêtes et gêner le site.&lt;/p&gt;

&lt;p&gt;Dans ce tuto, on va faire un exemple simple et poli : télécharger une seule page, annoncer qui on est avec un &lt;code&gt;User-Agent&lt;/code&gt;, attendre entre les demandes si on en fait plusieurs, et ne pas contourner les protections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Les règles de base
&lt;/h2&gt;

&lt;p&gt;Avant de scraper, regardez si le site propose une API ou un fichier export. C'est souvent plus propre. Lisez aussi les conditions d'utilisation si les données sont sensibles ou commerciales.&lt;/p&gt;

&lt;p&gt;Ne récupérez pas de données personnelles sans raison claire. Ne contournez pas un login, un paywall, un captcha ou une interdiction technique. Et si un site dit non dans son fichier &lt;code&gt;robots.txt&lt;/code&gt;, respectez ce signal, surtout pour un projet automatisé.&lt;/p&gt;

&lt;p&gt;Le scraping doit rester raisonnable : peu de pages, peu souvent, et avec un vrai besoin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le script Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;html.parser&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTMLParser&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExtracteurTitres&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HTMLParser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dans_h2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titres&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_starttag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dans_h2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_endtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dans_h2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;texte&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dans_h2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;texte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titres&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;requete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DevDuSudBot/1.0 contact: contact@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;reponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;extracteur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExtracteurTitres&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;extracteur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;titre&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;extracteur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;titre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ce que fait le script
&lt;/h2&gt;

&lt;p&gt;Le script télécharge une page, lit son HTML, puis récupère le texte placé dans les balises &lt;code&gt;h2&lt;/code&gt;. Sur certains sites, les titres sont dans des &lt;code&gt;h1&lt;/code&gt;, &lt;code&gt;h2&lt;/code&gt;, &lt;code&gt;h3&lt;/code&gt;, ou dans des blocs plus complexes. Il faudra donc adapter selon la page.&lt;/p&gt;

&lt;p&gt;Le &lt;code&gt;User-Agent&lt;/code&gt; est une phrase d'identité. Au lieu de se cacher, votre script explique qui il est. Pour un vrai projet, mettez une adresse de contact valable. Cela montre que vous n'essayez pas de vous faire passer pour un navigateur humain.&lt;/p&gt;

&lt;p&gt;Le &lt;code&gt;sleep(2)&lt;/code&gt; marque une pause. Ici, on ne fait qu'une page, donc il ne sert pas beaucoup. Mais si vous parcourez plusieurs pages, cette pause évite de taper trop vite sur le serveur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un exemple utile et raisonnable
&lt;/h2&gt;

&lt;p&gt;Imaginez un commerçant qui veut vérifier une fois par jour si ses horaires apparaissent correctement sur une page partenaire. Le script peut lire la page, chercher un texte, et alerter si l'information change. C'est un usage raisonnable.&lt;/p&gt;

&lt;p&gt;À l'inverse, aspirer tout un catalogue concurrent toutes les dix secondes serait agressif. Même si c'est techniquement possible, ce n'est pas une bonne idée.&lt;/p&gt;

&lt;h2&gt;
  
  
  Les limites du scraping
&lt;/h2&gt;

&lt;p&gt;Beaucoup de sites modernes chargent les informations avec JavaScript. Dans ce cas, le HTML reçu par Python peut être presque vide. Il faut alors utiliser d'autres outils, parfois plus lourds, ou chercher une API officielle.&lt;/p&gt;

&lt;p&gt;Autre limite : la structure d'une page peut changer. Si le site remplace ses &lt;code&gt;h2&lt;/code&gt; par autre chose, votre script ne trouvera plus rien. Un scraper doit donc être surveillé et corrigé de temps en temps.&lt;/p&gt;

&lt;h2&gt;
  
  
  À retenir
&lt;/h2&gt;

&lt;p&gt;Scraper, ce n'est pas voler ni spammer. C'est lire automatiquement une page publique, avec mesure et respect. La bonne attitude est simple : peu de requêtes, une identité claire, pas de contournement, pas de données sensibles inutiles.&lt;/p&gt;

&lt;p&gt;Python donne le pouvoir de récupérer de l'information. La partie importante, c'est de l'utiliser proprement.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Une facture, un fichier HTML, zéro serveur : ce que j'ai appris en fabriquant mon outil de facturation</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:51:25 +0000</pubDate>
      <link>https://dev.to/devdusud/une-facture-un-fichier-html-zero-serveur-ce-que-jai-appris-en-fabriquant-mon-outil-de-2alj</link>
      <guid>https://dev.to/devdusud/une-facture-un-fichier-html-zero-serveur-ce-que-jai-appris-en-fabriquant-mon-outil-de-2alj</guid>
      <description>&lt;p&gt;Je ne suis pas développeur. Je suis technicien IT, et je passe mes journées à réparer&lt;br&gt;
les machines des autres. Comme beaucoup d'indépendants, je facturais mal : un tableur,&lt;br&gt;
des numéros qui se perdent, des mentions légales que je recopiais d'une vieille facture.&lt;/p&gt;

&lt;p&gt;Je voulais un outil de facturation qui :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fonctionne sans connexion (je travaille souvent dans des locaux sans wifi correct) ;&lt;/li&gt;
&lt;li&gt;ne m'oblige pas à créer un compte chez quelqu'un ;&lt;/li&gt;
&lt;li&gt;ne m'abonne pas à 12 € par mois pour sortir deux factures ;&lt;/li&gt;
&lt;li&gt;tienne sur une clé USB, parce que je change souvent de machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La solution la plus simple que j'ai trouvée : une seule page HTML, du JavaScript,&lt;br&gt;
et le stockage local du navigateur. Pas de serveur, pas de base de données, pas de&lt;br&gt;
déploiement. On ouvre le fichier, on facture, on ferme. Les données ne quittent pas&lt;br&gt;
la machine.&lt;/p&gt;

&lt;p&gt;Trois choses que j'ai apprises en le construisant :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Le stockage local est plus fragile qu'on croit.&lt;/strong&gt; Un nettoyage de navigateur, et
tout disparaît. J'ai donc ajouté un export des données : ce n'est pas glamour,
mais c'est la fonctionnalité la plus importante de l'outil.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Les mentions légales sont la vraie difficulté.&lt;/strong&gt; Le code, c'est une journée.
Savoir ce qu'une facture de micro-entreprise doit contenir, c'est le reste du travail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un outil qu'on n'ose pas ouvrir ne sert à rien.&lt;/strong&gt; J'ai supprimé la moitié des
champs que je trouvais « utiles ». Une facture doit se faire en deux minutes,
pas en dix.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Je l'ai ensuite mis en PDF avec un mode d'emploi, parce que plusieurs collègues&lt;br&gt;
artisans m'ont demandé le fichier. C'est ici, 19,90 €, paiement unique :&lt;br&gt;
&lt;a href="https://revedebiz.gumroad.com/l/ngseyv" rel="noopener noreferrer"&gt;https://revedebiz.gumroad.com/l/ngseyv&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si vous bricolez le même genre d'outil, je suis preneur de vos retours — surtout&lt;br&gt;
sur la partie sauvegarde, c'est là que je suis le moins sûr de moi.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>freelance</category>
      <category>productivity</category>
    </item>
    <item>
      <title>3 erreurs qui font fuir tes clients</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Sun, 18 Oct 2026 15:51:26 +0000</pubDate>
      <link>https://dev.to/devdusud/3-erreurs-qui-font-fuir-tes-clients-54cb</link>
      <guid>https://dev.to/devdusud/3-erreurs-qui-font-fuir-tes-clients-54cb</guid>
      <description>&lt;p&gt;3 erreurs qui te coûtent des clients 💸&lt;/p&gt;

&lt;p&gt;Ton site te vend... ou te dessert. Tu sais lequel ?&lt;/p&gt;

&lt;h1&gt;
  
  
  siteweb #clients #conversion #entrepreneur #conseils
&lt;/h1&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/W_-m5lT_K5U" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>siteweb</category>
      <category>clients</category>
      <category>conversion</category>
      <category>entrepreneur</category>
    </item>
    <item>
      <title>Publier un site en 5 minutes</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Sun, 18 Oct 2026 15:50:36 +0000</pubDate>
      <link>https://dev.to/devdusud/publier-un-site-en-5-minutes-1m4n</link>
      <guid>https://dev.to/devdusud/publier-un-site-en-5-minutes-1m4n</guid>
      <description>&lt;p&gt;Un site en ligne en 5 minutes chrono ⚡&lt;/p&gt;

&lt;p&gt;Pas besoin d'une semaine, ni de 200€ de budget. Juste une méthode. Sauvegarde, la démo arrive.&lt;/p&gt;

&lt;h1&gt;
  
  
  siteweb #créationsite #devweb #hébergement #tuto
&lt;/h1&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/4TiKT64vO5U" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>siteweb</category>
      <category>créationsite</category>
      <category>devweb</category>
      <category>hébergement</category>
    </item>
    <item>
      <title>Ton travail ou ton IA ?</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Sun, 18 Oct 2026 15:50:35 +0000</pubDate>
      <link>https://dev.to/devdusud/ton-travail-ou-ton-ia--3j29</link>
      <guid>https://dev.to/devdusud/ton-travail-ou-ton-ia--3j29</guid>
      <description>&lt;p&gt;Ton travail, ou celui de ton IA ? 🤖&lt;/p&gt;

&lt;p&gt;La plupart des gens perdent 2h par jour sur des trucs qu'un agent ferait en 2 minutes. En 2026, ne pas automatiser, c'est choisir de perdre du temps.&lt;/p&gt;

&lt;h1&gt;
  
  
  IA #automatisation #agentIA #productivité #dev
&lt;/h1&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iQFepR64EnE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>ia</category>
      <category>automatisation</category>
      <category>agentia</category>
      <category>productivité</category>
    </item>
    <item>
      <title>Envoyer un email automatique pour rappeler les rendez-vous d'un commerçant</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Thu, 15 Oct 2026 07:34:10 +0000</pubDate>
      <link>https://dev.to/devdusud/envoyer-un-email-automatique-pour-rappeler-les-rendez-vous-dun-commercant-3aco</link>
      <guid>https://dev.to/devdusud/envoyer-un-email-automatique-pour-rappeler-les-rendez-vous-dun-commercant-3aco</guid>
      <description>&lt;h2&gt;
  
  
  Le besoin très concret
&lt;/h2&gt;

&lt;p&gt;Un salon de coiffure, un garage, une sophrologue, un réparateur informatique ou un cabinet de bien-être ont tous le même problème : les rendez-vous oubliés. Une personne ne vient pas, le créneau est perdu, et personne n'est content.&lt;/p&gt;

&lt;p&gt;On peut payer un gros logiciel pour gérer ça. Mais pour comprendre le principe, un petit script Python suffit. Il lit une liste de rendez-vous, repère ceux qui arrivent bientôt, puis envoie un email de rappel.&lt;/p&gt;

&lt;p&gt;Le but n'est pas de remplacer un vrai outil métier complet. Le but est de voir comment automatiser une tâche simple : « demain, pensez à votre rendez-vous ».&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce qu'on va faire
&lt;/h2&gt;

&lt;p&gt;On va imaginer un fichier de rendez-vous dans le script, pour rester simple. Dans la vraie vie, cette liste pourrait venir d'un fichier CSV, d'un agenda Google, ou d'un logiciel. Chaque rendez-vous contient un nom, une adresse email, une date et une heure.&lt;/p&gt;

&lt;p&gt;Le script vérifie les rendez-vous prévus demain. S'il en trouve un, il prépare un message et l'envoie avec une boîte email classique.&lt;/p&gt;

&lt;p&gt;Important : ne mettez jamais votre mot de passe directement dans le code. On utilise des variables d'environnement. C'est une façon plus propre de dire à Python : « va chercher le mot de passe ailleurs ».&lt;/p&gt;

&lt;h2&gt;
  
  
  Le script Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.message&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EmailMessage&lt;/span&gt;

&lt;span class="n"&gt;SMTP_SERVER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;smtp.gmail.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SMTP_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_EXPEDITEUR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EMAIL_EXPEDITEUR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;MOT_DE_PASSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EMAIL_MOT_DE_PASSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;rendez_vous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Nadia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nadia@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;14:30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Karim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;karim@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-15&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;09:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;demain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SMTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SMTP_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SMTP_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;serveur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;serveur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;starttls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;serveur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EMAIL_EXPEDITEUR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MOT_DE_PASSE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rdv&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rendez_vous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rdv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;demain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EmailMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;From&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EMAIL_EXPEDITEUR&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;To&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rdv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rappel de votre rendez-vous&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bonjour &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rdv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nom&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,

&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Petit rappel : votre rendez-vous est prévu demain à &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rdv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;heure&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.
&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Si vous avez un empêchement, merci de nous prévenir.

&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bonne journée !&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;serveur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rappel envoyé à &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rdv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nom&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comment l'adapter simplement
&lt;/h2&gt;

&lt;p&gt;La partie &lt;code&gt;rendez_vous&lt;/code&gt; est votre mini agenda. Pour tester, remplacez les emails d'exemple par une adresse à vous. Ne testez jamais directement sur de vrais clients au premier essai. Envoyez-vous le message, vérifiez le texte, puis seulement après utilisez une vraie liste.&lt;/p&gt;

&lt;p&gt;Pour Gmail, il faut souvent créer un mot de passe d'application. Ce n'est pas votre mot de passe principal. C'est un mot de passe spécial pour les scripts. D'autres fournisseurs email ont un système équivalent.&lt;/p&gt;

&lt;p&gt;Les variables &lt;code&gt;EMAIL_EXPEDITEUR&lt;/code&gt; et &lt;code&gt;EMAIL_MOT_DE_PASSE&lt;/code&gt; se configurent dans votre système. Sur Linux ou Mac, on peut les définir dans le terminal. Sur Windows, on peut les ajouter dans les variables d'environnement. L'idée est toujours la même : le code ne doit pas afficher le secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce qu'il ne faut pas faire
&lt;/h2&gt;

&lt;p&gt;N'envoyez pas 500 emails d'un coup depuis une boîte personnelle. Les fournisseurs peuvent croire à du spam. Commencez petit, avec quelques messages, et gardez un ton utile. Un rappel de rendez-vous, c'est normal. Une pub répétée, c'est autre chose.&lt;/p&gt;

&lt;p&gt;Ajoutez toujours une phrase humaine : comment annuler, comment prévenir, ou à quel numéro appeler. L'automatisation doit aider la relation client, pas donner l'impression qu'un robot froid parle à votre place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi c'est puissant
&lt;/h2&gt;

&lt;p&gt;Un email automatique, ce n'est pas magique. C'est juste une règle répétée au bon moment. Mais pour un petit commerce, ça peut éviter des oublis, libérer du temps, et donner une image plus organisée.&lt;/p&gt;

&lt;p&gt;Python devient ici un assistant discret. Il ne vend rien, il ne remplace pas le contact humain. Il pense juste à envoyer le rappel quand vous êtes occupé à autre chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  À retenir
&lt;/h2&gt;

&lt;p&gt;Commencez avec votre propre adresse email, testez, corrigez le message, puis seulement après utilisez de vrais rendez-vous. Un bon script professionnel est d'abord un script prudent.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Renommer 500 fichiers en 2 secondes avec Python</title>
      <dc:creator>Dev du Sud</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:01:02 +0000</pubDate>
      <link>https://dev.to/devdusud/renommer-500-fichiers-en-2-secondes-avec-python-1757</link>
      <guid>https://dev.to/devdusud/renommer-500-fichiers-en-2-secondes-avec-python-1757</guid>
      <description>&lt;h2&gt;
  
  
  Le problème du dossier qui déborde
&lt;/h2&gt;

&lt;p&gt;Vous avez peut-être déjà vécu ça : un dossier rempli de photos, de factures scannées ou de fichiers téléchargés avec des noms impossibles. Des &lt;code&gt;IMG_4891.jpg&lt;/code&gt;, &lt;code&gt;scan0007.pdf&lt;/code&gt;, &lt;code&gt;nouveau_document_final_vrai_final.pdf&lt;/code&gt;. Quand il y en a 10, on peut s'en sortir à la main. Quand il y en a 500, c'est une punition.&lt;/p&gt;

&lt;p&gt;L'idée de ce tuto est simple : demander à Python de parcourir un dossier et de renommer chaque fichier proprement. Pas besoin d'être développeur. On va préparer un petit script, le tester en mode prudent, puis le lancer pour de vrai.&lt;/p&gt;

&lt;p&gt;Exemple concret : un commerçant reçoit chaque semaine des photos produits. Au lieu de garder &lt;code&gt;DSC_001.jpg&lt;/code&gt;, il veut obtenir &lt;code&gt;produit-001.jpg&lt;/code&gt;, &lt;code&gt;produit-002.jpg&lt;/code&gt;, etc. C'est plus lisible, plus facile à envoyer, et beaucoup moins stressant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce qu'il faut préparer
&lt;/h2&gt;

&lt;p&gt;Avant de toucher aux fichiers, faites une copie du dossier. C'est la règle numéro 1. Un script peut aller très vite, donc on teste toujours sur une copie avant de l'utiliser sur les vrais documents.&lt;/p&gt;

&lt;p&gt;Créez ensuite un dossier simple, par exemple &lt;code&gt;a-renommer&lt;/code&gt;, et mettez quelques fichiers dedans. Le script ci-dessous va seulement renommer les fichiers, pas les sous-dossiers. C'est volontaire : pour débuter, on reste simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le script Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;DOSSIER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a-renommer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;PREFIXE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;produit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;MODE_TEST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# Passez à False quand tout vous semble bon
&lt;/span&gt;
&lt;span class="n"&gt;fichiers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DOSSIER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;numero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fichier&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fichiers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;nouveau_nom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PREFIXE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numero&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;nouvelle_adresse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DOSSIER&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;nouveau_nom&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  -&amp;gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nouveau_nom&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;MODE_TEST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nouvelle_adresse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Terminé : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fichiers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; fichier(s) traité(s).&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comment le lire sans paniquer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Path("a-renommer")&lt;/code&gt; indique le dossier à traiter. Si votre dossier s'appelle autrement, changez seulement cette ligne. &lt;code&gt;PREFIXE&lt;/code&gt; est le début du nouveau nom. Ici, tous les fichiers deviendront &lt;code&gt;produit-001&lt;/code&gt;, &lt;code&gt;produit-002&lt;/code&gt;, et ainsi de suite.&lt;/p&gt;

&lt;p&gt;La ligne la plus importante est &lt;code&gt;MODE_TEST = True&lt;/code&gt;. Tant qu'elle reste à &lt;code&gt;True&lt;/code&gt;, Python affiche ce qu'il ferait, mais ne renomme rien. C'est comme une répétition générale. Vous pouvez vérifier tranquillement que le résultat vous plaît.&lt;/p&gt;

&lt;p&gt;Quand vous êtes sûr, remplacez &lt;code&gt;True&lt;/code&gt; par &lt;code&gt;False&lt;/code&gt;, relancez le script, et les fichiers seront renommés.&lt;/p&gt;

&lt;h2&gt;
  
  
  Les petits pièges à connaître
&lt;/h2&gt;

&lt;p&gt;Premier piège : deux fichiers ne peuvent pas avoir exactement le même nom dans le même dossier. Notre script évite ça grâce au numéro automatique. Deuxième piège : l'ordre. Ici, on utilise &lt;code&gt;sorted&lt;/code&gt;, donc Python trie les fichiers par nom avant de les renommer. Si vous voulez un ordre par date de création ou de modification, c'est possible, mais on garde ça pour un tuto plus avancé.&lt;/p&gt;

&lt;p&gt;Troisième piège : les extensions. Le script garde &lt;code&gt;.jpg&lt;/code&gt;, &lt;code&gt;.pdf&lt;/code&gt;, &lt;code&gt;.png&lt;/code&gt; ou autre, en minuscules. C'est pratique pour éviter les mélanges entre &lt;code&gt;.JPG&lt;/code&gt; et &lt;code&gt;.jpg&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi c'est utile dans la vraie vie
&lt;/h2&gt;

&lt;p&gt;Ce petit script peut servir à un photographe, une boutique en ligne, une association, un artisan, ou juste à quelqu'un qui veut ranger son ordinateur. Le vrai gain n'est pas seulement de gagner deux minutes. C'est surtout d'éviter les erreurs répétitives.&lt;/p&gt;

&lt;p&gt;Renommer 500 fichiers à la main, c'est fatigant. Et quand on est fatigué, on se trompe. Python, lui, répète la même règle sans râler. Vous décidez de la règle, il applique.&lt;/p&gt;

&lt;h2&gt;
  
  
  À retenir
&lt;/h2&gt;

&lt;p&gt;Un bon automatisme ne doit pas être compliqué. Il doit être clair, testable, et réversible si possible. Ici, le mode test vous protège. La copie du dossier vous protège aussi. Avec ces deux habitudes, vous pouvez commencer à automatiser des tâches simples sans peur.&lt;/p&gt;

&lt;p&gt;C'est exactement l'esprit Dev du Sud : utiliser Python comme un petit assistant de bureau, pas comme un truc réservé aux ingénieurs.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
