Retour au blog
Guides
Andrei OgiolanLast updated on May 7, 202612 min read

Comment récupérer des tableaux HTML en Golang avec Colly : Guide de bout en bout

Comment récupérer des tableaux HTML en Golang avec Colly : Guide de bout en bout
En bref : ce guide explique comment extraire des données de tableaux HTML en Go de A à Z : choisissez entre Colly, goquery et golang.org/x/net/html, cibler les bonnes <tbody>, modéliser les lignes sous forme de struct typée et exporter des fichiers JSON et CSV propres. Vous bénéficierez également de la pagination, de la protection anti-blocage et de modèles de tableaux rendus en JavaScript.

Si vous avez déjà essayé d'importer du code HTML <table> dans un entrepôt Postgres ou un fichier CSV destiné aux analystes, les données sont bien présentes dans le DOM, mais les extraire de manière fiable est un petit projet en soi. Ce guide explique comment extraire des tableaux HTML en Golang d’une manière qui fonctionne sur de vraies pages, et pas seulement sur des tutoriels épurés.

Un tableau HTML est une grille structurée de lignes (<tr>) et de cellules (<td> ou <th>). L'extraire signifie analyser le balisage, parcourir ces éléments et transformer chaque ligne en un enregistrement typé que votre code peut utiliser en aval. En Go, vous disposez de trois options sérieuses : Colly, goquery et le golang.org/x/net/html. Nous verrons dans quels cas chacune d'entre elles est adaptée, puis nous construirons un scraper fonctionnel autour de Colly v2.

Vous apprendrez à inspecter une page dans DevTools, à écrire un sélecteur CSS précis, à modéliser les lignes sous forme de structure, à exporter à la fois au format JSON et CSV, et à gérer la pagination, le rendu JavaScript et les blocages anti-bot. À la fin, vous disposerez d’un modèle prêt à copier-coller pour extraire des tableaux HTML en Golang.

Pourquoi apprendre à extraire des tableaux HTML en Golang en vaut la peine

Les données tabulaires sont omniprésentes : pages de tarifs, statistiques sportives, documents financiers, ensembles de données publics qui n’ont jamais bénéficié d’une véritable API. Si votre pipeline commence par <table> un balisage et se termine dans un entrepôt de données ou un notebook, vous avez besoin d’un moyen fiable d’extraire ces données. Go compile en un seul binaire, gère bien la concurrence et offre des performances prévisibles à grande échelle. Savoir extraire des tableaux HTML en Go signifie déployer ce pipeline sous la forme d’un service autonome, sans avoir besoin d’un runtime Python.

Quand utiliser Colly, goquery ou net/html

Choisissez la mauvaise bibliothèque et vous passerez plus de temps à vous battre avec l'API qu'à analyser des lignes. Voici un tableau de décision rapide.

Bibliothèque

Idéale pour

À éviter lorsque

Colly v2 (github.com/gocolly/colly/v2)

Exploration de nombreuses pages avec des callbacks de cycle de vie (OnRequest, OnHTML, OnError), cookies, limitation de débit, hooks proxy

Vous disposez déjà d'une chaîne HTML en mémoire et n'avez pas besoin de réseau

goquery (github.com/PuerkitoBio/goquery)

Sélection CSS de type jQuery sur un *goquery.Document que vous avez déjà récupérée

Vous avez également besoin de fonctionnalités de crawling, de limitation de débit et de gestion des proxys

golang.org/x/net/html

Parcours de tokens et de nœuds de bas niveau lorsque le CSS ne suffit pas

Vous pouvez exprimer ce que vous voulez en CSS ; goquery nécessite trois fois moins de code

Le fil de discussion de longue date sur Stack Overflow concernant l'analyse des tableaux HTML en Go est toujours bien classé pour cette requête, et ses meilleures réponses renvoient vers goquery et x/net/html. Les deux sont fiables. Colly les intègre à une ergonomie de crawling dont vous aurez besoin dès que vous aurez plus d’une page à parcourir.

Configurez votre projet Go et installez Colly

Créez un module et récupérez Colly v2 :

mkdir html-golang-scraper && cd html-golang-scraper
go mod init github.com/yourname/html-golang-scraper
go get github.com/gocolly/colly/v2

Notez le /v2 suffixe. L'importation github.com/gocolly/colly est la ligne v1, et la plupart des anciens tutoriels y font encore référence. Les nouveaux projets doivent utiliser la v2 pour bénéficier des corrections de bogues actuelles et de la prise en charge des modules Go.

Ajoutez un test de validité main.go:

package main

import "fmt"

func main() {
    fmt.Println("scraper booted")
}

Exécutez go run main.go. Si vous voyez scraper booted, la chaîne d'outils est configurée et Colly est dans go.sum. À partir de là, chaque extrait de code remplace le corps de main ou ajoute un type au niveau du paquet.

Inspectez le tableau cible avant d'écrire du code

Avant d'écrire du code Go, ouvrez la page cible dans votre navigateur et cartographiez le tableau souhaité. Nous utiliserons la démo DataTables disponible à l'adresse https://datatables.net/examples/styling/display.html comme exemple concret. Cliquez avec le bouton droit sur le tableau, choisissez « Inspecter », puis vérifiez trois éléments :

  1. Le sélecteur. Recherchez un id (la démo utilise #example) ou unique. Évitez table seule, car les pages intègrent souvent la mise en page dans des éléments de tableau imbriqués.
  2. Structure de l'en-tête. Vérifiez <thead> et <tbody> sont bien séparés. Sinon, vous sauterez la première ligne dans le code.
  3. Statique ou dynamique. Désactivez JavaScript et rechargez la page. Si les lignes disparaissent, le tableau est rendu côté client. Nous aborderons cette branche plus tard.

Cinq minutes dans DevTools valent mieux qu'une heure passée à déboguer une tranche vide. Notre aide-mémoire des sélecteurs CSS présente les modèles les plus utilisés par les scrapers de tableaux.

Configurer le collecteur et les callbacks de Colly

Colly's Collector est l'objet central : il émet des requêtes et distribue les callbacks du cycle de vie. Considérez les quatre callbacks ci-dessous comme un modèle que vous pouvez copier dans chaque projet.

package main

import (
    "fmt"
    "log"

    "github.com/gocolly/colly/v2"
)

func main() {
    c := colly.NewCollector()

    c.OnRequest(func(r *colly.Request) {
        fmt.Println("visiting:", r.URL.String())
    })

    c.OnResponse(func(r *colly.Response) {
        fmt.Println("status:", r.StatusCode)
    })

    c.OnError(func(r *colly.Response, err error) {
        log.Printf("failed %s: %v", r.Request.URL, err)
    })

    if err := c.Visit("https://datatables.net/examples/styling/display.html"); err != nil {
        log.Fatal(err)
    }
}

OnRequest se déclenche avant chaque appel réseau, OnResponse lorsque le serveur répond, et OnError intercepte les réponses non 2xx et les erreurs de transport, là où la plupart des scrapers de production échouent silencieusement. Nous ajouterons OnHTML ensuite la fonction de rappel où s'effectue l'analyse du tableau.

Ciblez le tableau avec un sélecteur CSS précis

Dans la démo DataTables, l'exécution de document.querySelectorAll('table') dans la console du navigateur renvoie plusieurs résultats, car le balisage de mise en page ailleurs utilise également des éléments de table. Sélectionner table seule permettrait de scraper les mauvaises lignes ; il faut donc toujours valider les sélecteurs dans la console avant d'écrire du code Go.

Le sélecteur fiable ici est table#example > tbody. Il se limite à un seul tableau en id et en ignorant le <thead> , ce qui vous évite de supprimer manuellement la ligne d'en-tête. Le widget DataTables insère également des lignes d'en-tête et de pied de page en miroir ; en limitant la sélection à > tbody les exclut de votre ensemble de données.

c.OnHTML("table#example > tbody", func(h *colly.HTMLElement) {
    // row loop goes here
})

OnHTML correspond aux éléments via un sélecteur CSS et appelle le gestionnaire pour chaque correspondance. Remplacez #example par ce que DevTools vous indique. Si vous hésitez entre CSS et XPath, notre comparaison des sélecteurs XPath et CSS présente les avantages et les inconvénients de chacun.

Parcourir les lignes et extraire chaque cellule

À l'intérieur du OnHTML gestionnaire, appelez h.ForEach("tr", ...) et récupérez chaque cellule avec el.ChildText("td:nth-child(N)"):

c.OnHTML("table#example > tbody", func(h *colly.HTMLElement) {
    h.ForEach("tr", func(_ int, el *colly.HTMLElement) {
        row := tableData{
            Name:      strings.TrimSpace(el.ChildText("td:nth-child(1)")),
            Position:  strings.TrimSpace(el.ChildText("td:nth-child(2)")),
            Office:    strings.TrimSpace(el.ChildText("td:nth-child(3)")),
            Age:       strings.TrimSpace(el.ChildText("td:nth-child(4)")),
            StartDate: strings.TrimSpace(el.ChildText("td:nth-child(5)")),
            Salary:    strings.TrimSpace(el.ChildText("td:nth-child(6)")),
        }
        employeeData = append(employeeData, row)
    })
})

Les cellules de tableau HTML ne contiennent presque jamais de class ou id , c'est donc nth-child(n) est la manière la plus propre de gérer les colonnes. Si la page réorganise les colonnes, il suffit de modifier un seul chiffre par champ au lieu de réécrire votre analyseur.

Une approche plus résiliente consiste à lire <thead> d'abord, de créer un map[string]int index des noms de colonnes, puis de rechercher les cellules par étiquette d'en-tête. Cela vaut la peine d'écrire ce code supplémentaire si la source réorganise les colonnes. Enveloppez toujours le texte dans strings.TrimSpace et analysez les colonnes de devises ou de dates avec strconv et time.Parse avant la sérialisation, afin que les consommateurs n'obtiennent pas des chaînes de caractères telles que "$320,800" alors qu'ils s'attendaient à des nombres.

Modélisez la ligne avec une structure Go et une tranche

Définissez le type de ligne au niveau du package afin que les balises JSON l'accompagnent :

type tableData struct {
    Name      string `json:"name"`
    Position  string `json:"position"`
    Office    string `json:"office"`
    Age       string `json:"age"`
    StartDate string `json:"start_date"`
    Salary    string `json:"salary"`
}

var employeeData []tableData

Pourquoi une struct typée plutôt qu'une map[string]string? Trois raisons :

  1. Des clés JSON stables. Les balises de structure contrôlent les noms de champs et la casse dans la sortie, au lieu d'hériter de ce que vous avez saisi lors de l'analyse.
  2. Sécurité à la compilation. Les fautes de frappe empêchent la compilation, au lieu de produire silencieusement des valeurs vides qui vous causeront des problèmes en environnement de test.
  3. Refactorisations faciles. Lorsque vous analysez des nombres et des dates, remplacez Age par int ou StartDate par time.Time et le compilateur vous guide à travers chaque correction.

Ajoutez chaque élément analysé row à employeeData à l'intérieur de la boucle de ligne. La tranche est prête à être marshallée une fois c.Visit retourne une valeur.

Exportez les résultats au format JSON (et CSV en bonus)

JSON est le format par défaut idéal pour les API et les services en aval ; le CSV est ce que recherchent les outils de BI et les analystes. Exporter les deux formats ne nécessite qu’une dizaine de lignes supplémentaires.

import (
    "encoding/csv"
    "encoding/json"
    "log"
    "os"
)

content, err := json.MarshalIndent(employeeData, "", "  ")
if err != nil {
    log.Fatal(err)
}
if err := os.WriteFile("employees.json", content, 0644); err != nil {
    log.Fatal(err)
}

f, err := os.Create("employees.csv")
if err != nil {
    log.Fatal(err)
}
defer f.Close()
w := csv.NewWriter(f)
defer w.Flush()
_ = w.Write([]string{"Name", "Position", "Office", "Age", "StartDate", "Salary"})
for _, r := range employeeData {
    _ = w.Write([]string{r.Name, r.Position, r.Office, r.Age, r.StartDate, r.Salary})
}

Les deux fichiers se retrouvent dans votre répertoire de travail. Garder les deux formats ouverts pour les pipelines en aval est l'une des habitudes les plus utiles lorsque l'on apprend à extraire des tableaux HTML en Golang.

Gérer la pagination et les pages multiples

La plupart des pages contenant des tableaux ne tiennent pas sur un seul écran. Deux modèles couvrent la plupart des cas.

Modèle A : Suivre le lien suivant.

c.OnHTML("a.next", func(e *colly.HTMLElement) {
    if next := e.Request.AbsoluteURL(e.Attr("href")); next != "" {
        _ = e.Request.Visit(next)
    }
})

Modèle B : itérer sur un modèle d'URL avec numéro de page.

for page := 1; page <= 20; page++ {
    _ = c.Visit(fmt.Sprintf("https://example.com/data?page=%d", page))
}

Associez l'un ou l'autre de ces modèles à colly.LimitRule pour limiter les requêtes et éviter de surcharger le serveur d'origine :

_ = c.Limit(&colly.LimitRule{
    DomainGlob:  "*example.com*",
    Parallelism: 2,
    RandomDelay: 1500 * time.Millisecond,
})

Cela permet de maintenir un trafic fluide et réduit le risque d'obtenir un code 429 à la page sept.

Éviter d'être bloqué : proxys, en-têtes et tentatives de reconnexion

Dès que vous dépassez quelques centaines de requêtes, les défenses anti-bot de base se déclenchent. Voici une liste de contrôle indépendante des fournisseurs pour scraper des tableaux HTML en Golang à grande échelle :

  1. Faites tourner les user agents. extensions.RandomUserAgent(c) Insérez un nouvel agent utilisateur dans chaque requête.
  2. Limitez le débit. colly.LimitRule Avec RandomDelay rend le trafic moins robotique.
  3. Réessayer en cas d'erreurs temporaires. À l'intérieur OnError, vérifiez le code d'état et appelez r.Request.Retry() pour les réponses 5xx et 429.
  4. Faites tourner les proxys. Transmettez une liste à proxy.RoundRobinProxySwitcher et joignez-la via c.SetProxyFunc(...). Les pools d'adresses IP résidentielles s'intègrent mieux que les plages de centres de données.
  5. Optimisez le transport. Un http.Transport avec un délai d'expiration de 60 à 90 secondes DialContext et optimisé MaxIdleConns réduit le taux de perte de connexion sur les cibles instables.
  6. Externalisez lorsque cela cesse d'être amusant. Une API de scraping gérée vaut mieux que des heures d'ingénierie une fois que les CAPTCHA et l'empreinte digitale deviennent le cœur du projet. Notre guide sur les astuces pour éviter d'être bloqué lors du scraping Web approfondit ce sujet sous un angle indépendant du langage.

Et si le tableau est généré par JavaScript ?

Ouvrez la page avec JavaScript désactivé. Si <tbody> est vide dans la réponse HTML brute, les lignes sont injectées par le JS côté client et Colly ne les verra pas à lui seul. Deux options :

  1. Navigateur headless en cours d'exécution. chromedp pilote une véritable instance de Chrome depuis Go, attend que le tableau s'affiche et vous transmet le DOM rendu.
  2. API de rendu sans interface graphique. Délestez le navigateur vers un point de terminaison géré qui renvoie le HTML post-JS, puis transmettez ce HTML à Colly ou goquery comme d'habitude.

Assemblage du tout : un scraper entièrement fonctionnel

La version minimale exécutable, prête pour un nouveau module :

package main

import (
    "encoding/csv"
    "encoding/json"
    "fmt"
    "log"
    "os"
    "strings"

    "github.com/gocolly/colly/v2"
)

type tableData struct {
    Name, Position, Office, Age, StartDate, Salary string
}

func main() {
    var rows []tableData
    c := colly.NewCollector()

    c.OnHTML("table#example > tbody", func(h *colly.HTMLElement) {
        h.ForEach("tr", func(_ int, el *colly.HTMLElement) {
            rows = append(rows, tableData{
                Name:      strings.TrimSpace(el.ChildText("td:nth-child(1)")),
                Position:  strings.TrimSpace(el.ChildText("td:nth-child(2)")),
                Office:    strings.TrimSpace(el.ChildText("td:nth-child(3)")),
                Age:       strings.TrimSpace(el.ChildText("td:nth-child(4)")),
                StartDate: strings.TrimSpace(el.ChildText("td:nth-child(5)")),
                Salary:    strings.TrimSpace(el.ChildText("td:nth-child(6)")),
            })
        })
    })

    if err := c.Visit("https://datatables.net/examples/styling/display.html"); err != nil {
        log.Fatal(err)
    }

    j, _ := json.MarshalIndent(rows, "", "  ")
    _ = os.WriteFile("employees.json", j, 0644)

    f, _ := os.Create("employees.csv")
    defer f.Close()
    w := csv.NewWriter(f)
    defer w.Flush()
    _ = w.Write([]string{"Name", "Position", "Office", "Age", "StartDate", "Salary"})
    for _, r := range rows {
        _ = w.Write([]string{r.Name, r.Position, r.Office, r.Age, r.StartDate, r.Salary})
    }
    fmt.Println("scraped:", len(rows), "rows")
}

Testé sur Go 1.22 avec Colly v2 au moment de la rédaction. Intégrez la limitation de débit, le commutateur de proxy et l'extension d'agent utilisateur une fois que vous aurez dépassé l'URL de démonstration. Notre guide plus complet sur le scraping web avec Go couvre la chaîne d'outils.

Conclusion et prochaines étapes

Vous disposez désormais du modèle complet pour extraire des tableaux HTML en Golang : choisissez la bonne bibliothèque, définissez un sélecteur précis, modélisez les lignes sous forme de struct, exportez vers JSON et CSV, et n'utilisez chromedp ou la rotation de proxy que lorsque la page l'exige.

La prochaine étape logique est la concurrence. Passez votre collecteur en mode asynchrone avec c.Async = true, déclenchez Parallelism dans votre colly.LimitRule, puis appelez c.Wait() après le dernier c.Visit() pour répartir le travail sur plusieurs pages.

Lorsque la cible se montre agressive en matière de blocage et que vous préférez expédier le pipeline plutôt que de maintenir une infrastructure de proxy, notre API Scraper chez WebScrapingAPI renvoie le code HTML rendu derrière un seul point de terminaison, de sorte que le code d'analyse Colly que vous avez écrit aujourd'hui continue de fonctionner.

Points clés

  • Choisissez l'outil adapté à la tâche. Colly v2 est idéal pour l'exploration et les callbacks, goquery est la solution la plus légère lorsque vous disposez déjà de HTML en mémoire, et golang.org/x/net/html est la solution de secours de bas niveau.
  • Limitez toujours votre sélecteur à un <tbody>. Un simple table sélectionneur capture généralement le balisage de mise en page ; table#id > tbody est la valeur par défaut sûre.
  • Modélisez les lignes sous forme de structure typée, et non de carte. Les balises de structure vous fournissent des clés JSON stables et permettent au compilateur de détecter les fautes de frappe avant la mise en production.
  • Fournissez à la fois JSON et CSV. Ces deux formats ne représentent qu’une dizaine de lignes supplémentaires et facilitent à la fois les workflows API et ceux des analystes.
  • Prévoyez les blocages dès le début. Alternez les agents utilisateurs, limitez le débit, réessayez en cas de codes 5xx et 429, et recourez à des proxys ou à une API gérée dès que la cible oppose une résistance.

FAQ

Ai-je besoin de Colly pour extraire des tableaux HTML en Go, ou puis-je utiliser goquery ou net/html à la place ?

Non, Colly n'est pas nécessaire. Utilisez goquery lorsque vous disposez déjà du code HTML et que vous avez uniquement besoin d'une sélection CSS de type jQuery sur un *goquery.Document. Optez pour golang.org/x/net/html lorsque vous avez besoin d'un contrôle au niveau des tokens. Choisissez Colly lorsque l'exploration, la limitation, les cookies et les hooks de proxy vous obligeraient autrement à les réinventer.

Comment exporter les lignes de tableau extraites au format CSV en Go plutôt qu'au format JSON ?

Utilisez le encoding/csv . Ouvrez un fichier avec os.Create, encadrez-le avec csv.NewWriter, écrivez un en-tête avec w.Write([]string{...}), puis parcourez vos structures de lignes et appelez w.Write pour chaque ligne. Toujours defer w.Flush() et defer f.Close() pour que le fichier soit enregistré sur le disque.

Comment extraire un tableau qui s'étend sur plusieurs pages paginées avec Colly ?

Deux modèles couvrent la plupart des cas. Si la page affiche un lien « Suivant », enregistrez un OnHTML gestionnaire sur son sélecteur et appelez e.Request.Visit(e.Request.AbsoluteURL(e.Attr("href"))). Si les pages suivent un paramètre de requête numérique, construisez l'URL avec fmt.Sprintf et bouclez c.Visit. Associez l'un ou l'autre de ces modèles à colly.LimitRule et RandomDelay afin que les récupérations simultanées restent courtoises.

Comment puis-je extraire un tableau HTML lorsque les lignes sont générées par JavaScript ?

Affichez d'abord la page, puis analysez-la. chromedp pilote un véritable Chrome sans interface utilisateur depuis Go, vous permet d’ WaitVisible sur le sélecteur cible et renvoie le DOM post-JS que vous pouvez passer à goquery. Si vous préférez éviter les opérations du navigateur, envoyez l'URL à une API de rendu sans interface graphique et analysez le code HTML renvoyé avec Colly comme s'il s'agissait d'une page statique.

Comment éviter d'être bloqué lors du scraping de nombreuses pages de données tabulaires en Go ?

Mettez en place plusieurs niveaux de défense. Randomisez les user agents avec extensions.RandomUserAgent, et réglez le débit via colly.LimitRule avec RandomDelay, réessayez les réponses 5xx et 429 transitoires à l'intérieur de OnError, et alternez les proxys résidentiels via proxy.RoundRobinProxySwitcher. Mettez les réponses en cache pendant le développement afin de ne pas effectuer de nouveaux tests sur l'origine en production. Si les CAPTCHA deviennent courants, déchargez la couche de requêtes vers un point de terminaison de scraping géré.

À propos de l'auteur
Andrei Ogiolan, Développeur Full Stack @ WebScrapingAPI
Andrei OgiolanDéveloppeur Full Stack

Andrei Ogiolan est développeur Full Stack chez WebScrapingAPI ; il participe à l'ensemble du produit et contribue à la mise au point d'outils et de fonctionnalités fiables pour la plateforme.

Commencez à créer

Prêt à faire évoluer votre système de collecte de données ?

Rejoignez plus de 2 000 entreprises qui utilisent WebScrapingAPI pour extraire des données Web à l'échelle de l'entreprise, sans aucun coût d'infrastructure.