Dans cet article, j'écrirai un petit robot d'exploration Web. Je n'étais pas sûr si mon site Web avait de beaux titres de page à l'échelle du site, et si j'avais des titres en double, j'ai donc écrit ce petit utilitaire pour le savoir.
Je vais commencer par écrire une commande qui accepte une page de départ à partir de la ligne de commande, etsuit tout lien qui a l'url d'origine comme base.
Plus tard, j'ajouterai un indicateur facultatif pour détecter si le site atitres en double, quelque chose qui pourrait être utile à des fins de référencement.
Présentationgolang.org/x/net/html
Legolang.org/x
Les packages sont des packages gérés par l'équipe Go, mais ils ne font pas partie de la bibliothèque standard pour diverses raisons.
Peut-être qu'ils sont trop spécifiques et ne seront pas utilisés par la majorité des développeurs Go. Peut-être qu'ils sont encore en cours de développement ou expérimentaux, donc ils ne peuvent pas être inclus dans la stdlib, qui doit tenir la promesse de Go 1.0 de ne pas faire de modifications rétrocompatibles - quand quelque chose entre dans la stdlib, c'est «final».
L'un de ces packages estgolang.org/x/net/html
.
Pour l'installer, exécutez
go get golang.org/x/net...
Dans cet article, je vais utiliser en particulierhtml.Parse()
fonction, et lehtml.Node
struct:
package html
type Node struct {
Type NodeType
Data string
Attr []Attribute
FirstChild, NextSibling *node
}
type NodeType int32
const (
ErrorNode NodeType = iota
TextNode
DocumentNode
ElementNode
CommentNode
DoctypeNode
)
type Attribute struct {
Key, Val string
}
func Parse(r io.Reader) (*Node, error)
Répertoriez les liens du site et les titres des pages
Le premier programme ci-dessous accepte une URL et calcule les liens uniques qu'il trouve, donnant une sortie comme celle-ci:
http://localhost:1313/go-filesystem-structure/ -> Filesystem Structure of a Go project
http://localhost:1313/golang-measure-time/ -> Measuring execution time in a Go program
http://localhost:1313/go-tutorial-fortune/ -> Go CLI tutorial: fortune clone
http://localhost:1313/go-tutorial-lolcat/ -> Build a Command Line app with Go: lolcat
Commençons parmain()
, car il montre un aperçu de haut niveau de ce que fait le programme.
- obtient le
url
à partir des arguments CLI en utilisant `os.Args [1] - instancie
visited
, une carte avec des chaînes de clé et une chaîne de valeur, où nous stockons l'URL et le titre des pages du site - appels
analyze()
.url
est passé 2 fois, car la fonction est récursive et le second paramètre sert d'URL de base pour les appels récursifs itère sur le
visited
map, qui a été transmise par référence àanalyze()
et a maintenant toutes les valeurs remplies, afin que nous puissions les imprimerpackage main
import ( “fmt” “net/http” “os” “strings”
<span style="color:#e6db74">"golang.org/x/net/html"</span>
)
func main() { url := os.Args[1] if url == “” { fmt.Println(“Usage:
webcrawler <url>
“) os.Exit(1) } visited := map[string]string{} analyze(url, url, &visited) for k, v := range visited { fmt.Printf(”%s -> %s\n”, k, v) } }
Assez simple? Rentrons à l'intérieuranalyze()
. Première chose, ça appelleparse()
, qui, étant donné une chaîne pointant vers une URL, la récupérera et l'analysera en retournant un pointeur html.Node et une erreur.
func parse (chaîne d'url) (* html.Node, erreur)
Après avoir vérifié le succès,analyze()
récupère le titre de la page en utilisantpageTitle()
, qui a donné une référence à un html.Node, le scanne jusqu'à ce qu'il trouve la balise de titre, puis il renvoie sa valeur.
func pageTitle (n * html.Node) chaîne
Une fois que nous avons le titre de la page, nous pouvons l'ajouter auvisited
carte.
Ensuite, nous obtenons tous les liens de page en appelantpageLinks()
, qui étant donné le nœud de la page de départ, il analysera récursivement tous les nœuds de la page et retournera une liste de liens uniques trouvés (pas de doublons).
func pageLinks (links [] chaîne, n * html.Node) [] chaîne
Une fois que nous avons obtenu la tranche de liens, nous les parcourons, et nous faisons un petit contrôle: sivisited
ne contient pas encore la page, cela signifie que nous ne l'avons pas encore visitée, et le lien doit avoirbaseurl
comme préfixe. Si ces 2 affirmations sont confirmées, nous pouvons appeleranalyze()
avec l'url du lien.
// analyze given a url and a basurl, recoursively scans the page
// following all the links and fills the `visited` map
func analyze(url, baseurl string, visited *map[string]string) {
page, err := parse(url)
if err != nil {
fmt.Printf("Error getting page %s %s\n", url, err)
return
}
title := pageTitle(page)
(*visited)[url] = title
<span style="color:#75715e">//recursively find links
links := pageLinks(nil, page)
for _, link := range links {
if (*visited)[link] == “” && strings.HasPrefix(link, baseurl) {
analyze(link, baseurl, visited)
}
}
}
pageTitle()
utilise legolang.org/x/net/html
API que nous avons présentées ci-dessus. À la première itération,n
est le<html>
nœud. Nous recherchons la balise de titre. La première itération ne satisfait jamais cela, nous allons donc faire une boucle sur le premier enfant de<html>
d'abord, et ses frères et sœurs plus tard, et nous appelonspageTitle()
en passant récursivement le nouveau nœud.
Finalement, nous arriverons à la<title>
étiquette: unhtml.Node
instance avecType
égal àhtml.ElementNode
(voir ci-dessus) etData
égal àtitle
, et nous retournons son contenu en accédant à sonFirstChild.Data
biens
// pageTitle given a reference to a html.Node, scans it until it
// finds the title tag, and returns its value
func pageTitle(n *html.Node) string {
var title string
if n.Type == html.ElementNode && n.Data == "title" {
return n.FirstChild.Data
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
title = pageTitle(c)
if title != "" {
break
}
}
return title
}
pageLinks()
n'est pas très différent depageTitle()
, sauf qu'il ne s'arrête pas lorsqu'il trouve le premier élément, mais recherche chaque lien, il faut donc passer lelinks
slice comme paramètre de cette fonction récursive. Les liens sont découverts en vérifiant lehtml.Node
ahtml.ElementNode
Type
,Data
doit êtrea
et aussi ils doivent avoir unAttr
avecKey
href
, sinon cela pourrait être une ancre.
// pageLinks will recursively scan a `html.Node` and will return
// a list of links found, with no duplicates
func pageLinks(links []string, n *html.Node) []string {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
if !sliceContains(links, a.Val) {
links = append(links, a.Val)
}
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
links = pageLinks(links, c)
}
return links
}
sliceContains()
est une fonction utilitaire appelée parpageLinks()
pour vérifier l'unicité de la tranche.
// sliceContains returns true if `slice` contains `value`
func sliceContains(slice []string, value string) bool {
for _, v := range slice {
if v == value {
return true
}
}
return false
}
La dernière fonction estparse()
. Il utilise lehttp
fonctionnalité stdlib pour obtenir le contenu d'une URL (http.Get()
) puis utilise legolang.org/x/net/html
html.Parse()
API pour analyser le corps de la réponse à partir de la requête HTTP, renvoyant unhtml.Node
référence.
// parse given a string pointing to a URL will fetch and parse it
// returning an html.Node pointer
func parse(url string) (*html.Node, error) {
r, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("Cannot get page")
}
b, err := html.Parse(r.Body)
if err != nil {
return nil, fmt.Errorf("Cannot parse page")
}
return b, err
}
Détecter les titres en double
Puisque je veuxutiliser un indicateur de ligne de commandepour vérifier les doublons, je vais modifier légèrement la façon dont l'URL est transmise au programme: au lieu d'utiliseros.Args
, Je transmettrai également l'URL en utilisant un indicateur.
Ceci est le modifiémain()
fonction, avec des indicateurs d'analyse avant de faire le travail habituel de préparation duanalyze()
exécution et impression des valeurs. De plus, à la fin, il y a un chèque pour ledup
indicateur booléen, et s'il est vrai, il s'exécutecheckDuplicates()
.
import (
"flag"
//...
)
func main() {
var url string
var dup bool
flag.StringVar(&url, “url”, “”, “the url to parse”)
flag.BoolVar(&dup, “dup”, false, “if set, check for duplicates”)
flag.Parse()
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">""</span> {
<span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">PrintDefaults</span>()
<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
}
<span style="color:#a6e22e">visited</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">string</span>{}
<span style="color:#a6e22e">analyze</span>(<span style="color:#a6e22e">url</span>, <span style="color:#a6e22e">url</span>, <span style="color:#f92672">&</span><span style="color:#a6e22e">visited</span>)
<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">link</span>, <span style="color:#a6e22e">title</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">visited</span> {
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">"%s -> %s\n"</span>, <span style="color:#a6e22e">link</span>, <span style="color:#a6e22e">title</span>)
}
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">dup</span> {
<span style="color:#a6e22e">checkDuplicates</span>(<span style="color:#f92672">&</span><span style="color:#a6e22e">visited</span>)
}
}
checkDuplicates
prend la carte des url -> titres et itère dessus pour construire la sienneuniques
map, que cette fois a le titre de la page comme clé, afin que nous puissions vérifieruniques[title] == ""
pour déterminer si un titre est déjà là, et nous pouvons accéder à la première page qui a été saisie avec ce titre en imprimantuniques[title]
.
// checkDuplicates scans the visited map for pages with duplicate titles
// and writes a report
func checkDuplicates(visited *map[string]string) {
found := false
uniques := map[string]string{}
fmt.Printf("\nChecking duplicates..\n")
for link, title := range *visited {
if uniques[title] == "" {
uniques[title] = link
} else {
found = true
fmt.Printf("Duplicate title \"%s\" in %s but already found in %s\n", title, link, uniques[title])
}
}
<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">found</span> {
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">"No duplicates were found 😇"</span>)
}
}
Crédits
Le langage de programmation Golivre de Donovan et Kernighan utilise un robot d'exploration comme exemple tout au long du livre, le changeant dans différents chapitres pour introduire de nouveaux concepts. Le code fourni dans cet article s'inspire du livre.
Plus de tutoriels go:
- Utilisation du proxy inverse NGINX pour servir les services Go
- Faire une copie d'une structure dans Go
- Les bases d'un serveur Web Go
- Trier un type de carte dans Go
- Allez les pointeurs en un mot
- Go Tags expliqué
- Aller au formatage de la date et de l'heure
- Traitement JSON avec Go
- Fonctions Go Variadic
- Fiche de triche Go Strings
- L'interface Go Empty expliquée
- Débogage Go avec VS Code et Delve
- Named Go renvoie des paramètres
- Générer des nombres et des chaînes aléatoires dans Go
- Structure du système de fichiers d'un projet Go
- Algorithme de recherche binaire implémenté dans Go
- Utilisation des indicateurs de ligne de commande dans Go
- GOPATH a expliqué
- Créez une application de ligne de commande avec Go: lolcat
- Création d'une commande CLI avec Go: cowsay
- Utilisation de Shell Pipes avec Go
- Tutoriel Go CLI: Clone de fortune
- Lister les fichiers dans un dossier avec Go
- Utilisez Go pour obtenir une liste des référentiels à partir de GitHub
- Allez, ajoutez une tranche de chaînes à un fichier
- Allez, convertissez une chaîne en une tranche d'octets
- Visualisez vos contributions Git locales avec Go
- Premiers pas avec Go CPU et profilage de la mémoire
- Résolution de l'erreur "ne prend pas en charge l'indexation" dans un programme Go
- Mesure du temps d'exécution dans un programme Go
- Création d'un robot d'exploration Web avec Go pour détecter les titres en double
- Go Best Practices: pointeur ou récepteurs de valeur?
- Go Best Practices: Devez-vous utiliser une méthode ou une fonction?
- Go Structures de données: définir
- Aide-mémoire Go Maps
- Générer des implémentations pour les types génériques dans Go
- Go Data Structures: Dictionnaire
- Structures de données Go: table de hachage
- Implémenter des écouteurs d'événements dans Passer par les canaux
- Go Structures de données: pile
- Go Structures de données: file d'attente
- Go Structures de données: arbre de recherche binaire
- Go Structures de données: graphique
- Structures de données Go: liste liée
- Le guide complet des structures de données Go
- Comparaison des valeurs Go
- Est-ce que Go est orienté objet?
- Travailler avec une base de données SQL dans Go
- Utilisation des variables d'environnement dans Go
- Tutoriel Go: API REST soutenue par PostgreSQL
- Activation de CORS sur un serveur Web Go
- Déployer une application Go dans un conteneur Docker
- Pourquoi Go est un langage puissant à apprendre en tant que développeur PHP
- Allez, supprimez le caractère de nouvelle ligne io.Reader.ReadString
- Allez, comment observer les changements et reconstruire votre programme
- Allez, comptez les mois depuis une date
- Accéder aux paramètres HTTP POST dans Go