В этой статье я напишу небольшой веб-сканер. Я не был уверен, есть ли у моего веб-сайта хорошие заголовки страниц по всему сайту, и были ли у меня повторяющиеся заголовки, поэтому я написал эту небольшую утилиту, чтобы узнать.
Я начну с написания команды, которая принимает стартовую страницу из командной строки, иследует по любой ссылке, имеющей исходный URL-адрес в качестве основы.
Позже я добавлю необязательный флаг, чтобы определить, есть ли на сайтеповторяющиеся заголовки, что-то, что может быть полезно для целей SEO.
Представляемgolang.org/x/net/html
Вgolang.org/x
Пакеты - это пакеты, поддерживаемые командой Go, но они не являются частью стандартной библиотеки по разным причинам.
Возможно, они слишком специфичны и не будут использоваться большинством разработчиков Go. Возможно, они все еще находятся в стадии разработки или экспериментальной разработки, поэтому их нельзя включить в stdlib, который должен соответствовать обещанию Go 1.0 об отсутствии обратно несовместимых изменений - когда что-то попадает в stdlib, это «окончательно».
Один из этих пакетовgolang.org/x/net/html
.
Чтобы установить его, выполните
go get golang.org/x/net...
В этой статье я, в частности, буду использоватьhtml.Parse()
функция, аhtml.Node
структура:
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)
Перечислите ссылки на сайты и заголовки страниц
Первая программа, представленная ниже, принимает URL-адрес и вычисляет уникальные ссылки, которые она находит, давая следующий результат:
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
Начнем сmain()
, поскольку он показывает общий обзор того, что делает программа.
- получает
url
из аргументов CLI с помощью `os.Args [1] - создает экземпляры
visited
, карта с ключевыми строками и строкой значений, где мы будем хранить URL-адрес и заголовок страниц сайта. - звонки
analyze()
.url
передается 2 раза, так как функция является рекурсивной, а второй параметр служит базовым URL-адресом для рекурсивных вызовов повторяет
visited
карта, которая была передана по ссылке наanalyze()
и теперь все значения заполнены, поэтому мы можем их распечататьpackage 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) } }
Достаточно просто? Давай внутрьanalyze()
. Во-первых, он звонитparse()
, который с учетом строки, указывающей на URL-адрес, будет извлекать и анализировать его, возвращая указатель html.Node и ошибку.
func parse (url string) (* html.Node, ошибка)
После проверки на успех,analyze()
извлекает заголовок страницы, используяpageTitle()
, который дает ссылку на html.Node, сканирует его, пока не найдет тег заголовка, а затем возвращает его значение.
func pageTitle (n * html.Node) строка
Когда у нас есть заголовок страницы, мы можем добавить его вvisited
карта.
Затем мы получаем все ссылки на страницы, вызываяpageLinks()
, для которого задан узел начальной страницы, он рекурсивно просканирует все узлы страницы и вернет список найденных уникальных ссылок (без дубликатов).
func pageLinks (links [] строка, n * html.Node) [] строка
Получив фрагмент ссылок, мы перебираем их и делаем небольшую проверку: еслиvisited
еще не содержит страницу, значит, мы ее еще не посещали, а ссылка должна иметьbaseurl
как префикс. Если эти 2 утверждения подтвердятся, мы можем позвонитьanalyze()
с URL-адресом ссылки.
// 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()
используетgolang.org/x/net/html
API, которые мы представили выше. На первой итерацииn
это<html>
узел. Ищем тег заголовка. Первая итерация этого никогда не удовлетворяет, поэтому мы перебираем в цикле первого дочернего элемента<html>
сначала, а потом его братья и сестры, и мы называемpageTitle()
рекурсивно передать новый узел.
В конце концов мы перейдем к<title>
tag: anhtml.Node
экземпляр сType
равноhtml.ElementNode
(см. выше) иData
равноtitle
, и мы возвращаем его содержимое, обращаясь к егоFirstChild.Data
свойство
// 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()
не сильно отличается отpageTitle()
, за исключением того, что он не останавливается, когда находит первый элемент, а просматривает каждую ссылку, поэтому мы должны передатьlinks
slice как параметр для этой рекурсивной функции. Ссылки обнаруживаются путем проверкиhtml.Node
имеетhtml.ElementNode
Type
,Data
должно бытьa
а также они должны иметьAttr
сKey
href
, иначе это мог бы быть якорь.
// 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()
это служебная функция, вызываемаяpageLinks()
проверить уникальность в срезе.
// 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
}
Последняя функцияparse()
. Он используетhttp
stdlib для получения содержимого URL-адреса (http.Get()
), а затем используетgolang.org/x/net/html
html.Parse()
API для синтаксического анализа тела ответа из HTTP-запроса, возвращающегоhtml.Node
ссылка.
// 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
}
Обнаруживать повторяющиеся заголовки
Так как я хочуиспользовать флаг командной строкичтобы проверить наличие дубликатов, я собираюсь немного изменить способ передачи URL в программу: вместо использованияos.Args
, Я тоже передам URL с помощью флага.
Это модифицированныйmain()
функция с синтаксическим анализом флагов перед выполнением обычной работы по подготовкеanalyze()
оформление и печать ценностей. Кроме того, в конце есть чек наdup
логический флаг, и если истина, он запускаетсяcheckDuplicates()
.
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
берет карту URL -> заголовков и перебирает ее, чтобы построить свою собственнуюuniques
map, что на этот раз в качестве ключа используется заголовок страницы, поэтому мы можем проверитьuniques[title] == ""
чтобы определить, есть ли уже заголовок, и мы можем получить доступ к первой странице, которая была введена с этим заголовком, распечатавuniques[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>)
}
}
Кредиты
Язык программирования GoКнига Донована и Кернигана использует поискового робота в качестве примера на протяжении всей книги, изменяя его в разных главах, чтобы представить новые концепции. Код, представленный в этой статье, вдохновлен книгой.
Больше руководств по go:
- Использование обратного прокси NGINX для обслуживания сервисов Go
- Создание копии структуры в Go
- Основы веб-сервера Go
- Сортировка типа карты в Go
- Вкратце об указателях Go
- Объяснение тегов Go
- Форматирование даты и времени Go
- Обработка JSON с помощью Go
- Перейти к переменным функциям
- Шпаргалка по струнам Go
- Объяснение интерфейса Go Empty
- Отладка Go с помощью VS Code и Delve
- Именованный Go возвращает параметры
- Генерация случайных чисел и строк в Go
- Структура файловой системы проекта Go
- Алгоритм двоичного поиска, реализованный в Go
- Использование флагов командной строки в Go
- GOPATH объяснил
- Создайте приложение командной строки с помощью Go: lolcat
- Создание команды интерфейса командной строки с помощью Go: cowsay
- Использование Shell Pipes с Go
- Учебник по интерфейсу командной строки: Fortune Clone
- Перечислить файлы в папке с помощью Go
- Используйте Go, чтобы получить список репозиториев с GitHub
- Пойдите, добавьте фрагмент строк в файл
- Пойдите, преобразуйте строку в срез байтов
- Визуализируйте свой вклад в Git с помощью Go
- Начало работы с Go CPU и профилирование памяти
- Устранение ошибки "не поддерживает индексацию" в программе Go
- Измерение времени выполнения в программе Go
- Создание веб-краулера с Go для обнаружения повторяющихся заголовков
- Go Best Practices: указатель или приемники значений?
- Go Best Practices: следует ли использовать метод или функцию?
- Структуры данных Go: установить
- Шпаргалка по картам Go
- Создание реализаций для универсальных типов в Go
- Структуры данных Go: словарь
- Структуры данных Go: хеш-таблица
- Реализуйте прослушиватели событий в проходных каналах
- Структуры данных Go: стек
- Структуры данных Go: очередь
- Структуры данных Go: двоичное дерево поиска
- Структуры данных Go: график
- Структуры данных Go: связанный список
- Полное руководство по структурам данных Go
- Сравнение значений Go
- Является ли Go объектно-ориентированным?
- Работа с базой данных SQL в Go
- Использование переменных среды в Go
- Учебник: REST API на базе PostgreSQL
- Включение CORS на веб-сервере Go
- Развертывание приложения Go в контейнере Docker
- Почему Go - мощный язык для изучения PHP-разработчика
- Пойдите, удалите символ новой строки io.Reader.ReadString
- Идите, как посмотреть изменения и пересобрать вашу программу
- Иди, посчитай месяцы с даты
- Доступ к параметрам HTTP POST в Go