En este artículo escribiré un pequeño rastreador web. No estaba seguro de si mi sitio web tenía buenos títulos de página en todo el sitio y si tenía títulos duplicados, así que escribí esta pequeña utilidad para averiguarlo.
Comenzaré escribiendo un comando que acepte una página de inicio desde la línea de comando, ysigue cualquier enlace que tenga la URL original como base.
Más tarde, agregaré una marca opcional para detectar si el sitio tienetítulos duplicados, algo que podría ser útil para fines de SEO.
Presentandogolang.org/x/net/html
losgolang.org/x
Los paquetes son paquetes mantenidos por el equipo de Go, pero no forman parte de la biblioteca estándar por varias razones.
Tal vez sean demasiado específicos y no serán utilizados por la mayoría de los desarrolladores de Go. Tal vez todavía estén en desarrollo o sean experimentales, por lo que no se pueden incluir en stdlib, que debe cumplir con la promesa de Go 1.0 de que no hay cambios incompatibles con versiones anteriores: cuando algo entra en stdlib, es "final".
Uno de estos paquetes esgolang.org/x/net/html
.
Para instalarlo, ejecute
go get golang.org/x/net...
En este artículo usaré en particularhtml.Parse()
función, y lahtml.Node
estructura:
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)
Enumere los enlaces del sitio y los títulos de las páginas.
El primer programa aquí abajo acepta una URL y calcula los enlaces únicos que encuentra, dando un resultado como este:
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
Empecemos pormain()
, ya que muestra una descripción general de alto nivel de lo que hace el programa.
- obtiene el
url
de los argumentos de CLI usando `os.Args [1] - instancia
visited
, un mapa con cadenas clave y cadena de valor, donde almacenaremos la URL y el título de las páginas del sitio. - llamadas
analyze()
.url
se pasa 2 veces, ya que la función es recursiva y el segundo parámetro sirve como la URL base para las llamadas recursivas itera sobre el
visited
mapa, que se pasó por referencia aanalyze()
y ahora tiene todos los valores llenos, por lo que podemos imprimirlospackage 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) } }
¿Suficientemente simple? Vamos a entraranalyze()
. Lo primero que llamaparse()
, que, dada una cadena que apunta a una URL, la buscará y analizará devolviendo un puntero html.Node y un error.
func parse (cadena de URL) (* html.Node, error)
Después de verificar el éxito,analyze()
obtiene el título de la página usandopageTitle()
, que da una referencia a un html.Node, lo escanea hasta que encuentra la etiqueta del título y luego devuelve su valor.
func pageTitle (n * html.Node) cadena
Una vez que tenemos el título de la página, podemos agregarlo alvisited
mapa.
A continuación, obtenemos todos los enlaces de la página llamandopageLinks()
, que dado el nodo de la página de inicio, escaneará recursivamente todos los nodos de la página y devolverá una lista de enlaces únicos encontrados (sin duplicados).
func pageLinks (enlaces [] cadena, n * html.Node) [] cadena
Una vez que obtenemos el segmento de enlaces, iteramos sobre ellos y hacemos una pequeña verificación: sivisited
aún no contiene la página, significa que aún no la visitamos y el enlace debe tenerbaseurl
como prefijo. Si se confirman esas 2 afirmaciones, podemos llamaranalyze()
con la URL del enlace.
// 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()
usa elgolang.org/x/net/html
API que presentamos anteriormente. En la primera iteración,n
es el<html>
nodo. Buscamos la etiqueta del título. La primera iteración nunca satisface esto, así que vamos y recorremos el primer hijo de<html>
primero, y sus hermanos después, y llamamospageTitle()
pasando de forma recursiva el nuevo nodo.
Eventualmente llegaremos al<title>
etiqueta: unhtml.Node
instancia conType
igual ahtml.ElementNode
(ver arriba) yData
igual atitle
, y devolvemos su contenido accediendo a suFirstChild.Data
propiedad
// 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()
no es muy diferente apageTitle()
, excepto que no se detiene cuando encuentra el primer elemento, sino que busca todos los enlaces, por lo que debemos pasar ellinks
slice como parámetro para esta función recursiva. Los enlaces se descubren marcando elhtml.Node
poseehtml.ElementNode
Type
,Data
debe sera
y también deben tener unAttr
conKey
href
, ya que de lo contrario podría ser un ancla.
// 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()
es una función de utilidad llamada porpageLinks()
para comprobar la unicidad en la rebanada.
// 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 última función esparse()
. Usa elhttp
funcionalidad stdlib para obtener el contenido de una URL (http.Get()
) y luego usa elgolang.org/x/net/html
html.Parse()
API para analizar el cuerpo de la respuesta de la solicitud HTTP, devolviendo unhtml.Node
referencia.
// 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
}
Detectar títulos duplicados
Ya que quierousar una bandera de línea de comandopara verificar si hay duplicados, voy a cambiar ligeramente la forma en que se pasa la URL al programa: en lugar de usaros.Args
, También pasaré la URL con una bandera.
Este es el modificadomain()
función, con banderas analizando antes de hacer el trabajo habitual de preparar elanalyze()
ejecución e impresión de valores. Además, al final hay un cheque para eldup
bandera booleana, y si es verdadera se ejecutacheckDuplicates()
.
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
toma el mapa de url -> títulos y lo itera para construir su propiouniques
map, que esta vez tiene el título de la página como clave, por lo que podemos verificaruniques[title] == ""
para determinar si un título ya está allí, y podemos acceder a la primera página que se ingresó con ese título imprimiendouniques[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éditos
El lenguaje de programación GoEl libro de Donovan y Kernighan usa un rastreador web como ejemplo a lo largo del libro, cambiándolo en diferentes capítulos para introducir nuevos conceptos. El código proporcionado en este artículo se inspira en el libro.
Más tutoriales de go:
- Uso de NGINX Reverse Proxy para brindar servicios de Go
- Hacer una copia de una estructura en Go
- Los conceptos básicos de un servidor web Go
- Ordenar un tipo de mapa en Go
- Ir consejos en pocas palabras
- Explicación de las etiquetas Go
- Ir al formato de fecha y hora
- Procesamiento JSON con Go
- Ir a funciones variadas
- Hoja de referencia de Go Strings
- Explicación de la interfaz Go Empty
- Depuración de Go con VS Code y Delve
- Parámetros de devoluciones de Go Named
- Generación de cadenas y números aleatorios en Go
- Estructura del sistema de archivos de un proyecto de Go
- Algoritmo de búsqueda binaria implementado en Go
- Uso de indicadores de línea de comando en Go
- GOPATH explicado
- Cree una aplicación de línea de comandos con Go: lolcat
- Construyendo un comando CLI con Go: cowsay
- Uso de Shell Pipes con Go
- Tutorial de Go CLI: clon de la fortuna
- Enumere los archivos en una carpeta con Go
- Use Ir para obtener una lista de repositorios de GitHub
- Ve, agrega un trozo de cadenas a un archivo
- Ve, convierte una cadena en un segmento de bytes
- Visualice sus contribuciones locales de Git con Go
- Introducción a la creación de perfiles de memoria y CPU de Go
- Resolver el error "no admite la indexación" en un programa Go
- Medir el tiempo de ejecución en un programa Go
- Creación de un rastreador web con Go para detectar títulos duplicados
- Siga las mejores prácticas: ¿puntero o receptores de valor?
- Siga las mejores prácticas: ¿Debería utilizar un método o una función?
- Ir a estructuras de datos: Establecer
- Hoja de referencia de Go Maps
- Genere implementaciones para tipos genéricos en Go
- Ir a estructuras de datos: diccionario
- Ir a estructuras de datos: tabla hash
- Implementar oyentes de eventos en canales de paso
- Ir a estructuras de datos: apilar
- Ir a estructuras de datos: cola
- Ir a estructuras de datos: árbol de búsqueda binaria
- Ir a estructuras de datos: gráfico
- Ir a estructuras de datos: lista vinculada
- La guía completa de Go Data Structures
- Comparación de valores de Go
- ¿Go está orientado a objetos?
- Trabajar con una base de datos SQL en Go
- Usar variables de entorno en Go
- Ir al tutorial: API REST respaldada por PostgreSQL
- Habilitación de CORS en un servidor web Go
- Implementación de una aplicación Go en un contenedor Docker
- Por qué Go es un lenguaje poderoso para aprender como desarrollador PHP
- Ve, elimina el carácter de nueva línea io.Reader.ReadString
- Ir, cómo ver los cambios y reconstruir su programa
- Ve, cuenta los meses desde una fecha
- Acceder a los parámetros HTTP POST en Go