この記事では、小さなWebクローラーを作成します。私のウェブサイトにサイト全体で素敵なページタイトルがあるかどうか、そして重複したタイトルがあるかどうかわからなかったので、この小さなユーティリティを書いて調べました。
コマンドラインから開始ページを受け入れるコマンドを作成することから始めます。元のURLをベースとするリンクをたどる。
後で、サイトにあるかどうかを検出するためのオプションのフラグを追加します重複するタイトル、SEOの目的に役立つかもしれない何か。
紹介golang.org/x/net/html
ザ・golang.org/x
パッケージはGoチームによって管理されているパッケージですが、さまざまな理由から標準ライブラリの一部ではありません。
たぶん、それらはあまりにも具体的で、Go開発者の大多数によって使用されることはないでしょう。おそらくそれらはまだ開発中または実験中であるため、stdlibに含めることはできません。これは、後方互換性のない変更がないというGo 1.0の約束を果たす必要があります。stdlibに何かが入ると、それは「最終」です。
これらのパッケージの1つは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
`os.Args [1]を使用してCLI引数から - インスタンス化
visited
、キー文字列と値文字列を含むマップ。ここにURLとサイトページのタイトルを保存します - 呼び出し
analyze()
。url
関数は再帰的であり、2番目のパラメーターは再帰呼び出しのベースURLとして機能するため、は2回渡されます。 繰り返します
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、error)
成功を確認した後、analyze()
を使用してページタイトルを取得しますpageTitle()
は、html.Nodeへの参照を指定し、titleタグが見つかるまでスキャンしてから、その値を返します。
func pageTitle(n * html.Node)文字列
ページタイトルを取得したら、それをに追加できますvisited
地図。
次に、を呼び出してすべてのページリンクを取得しますpageLinks()
、開始ページノードを指定すると、すべてのページノードを再帰的にスキャンし、見つかった一意のリンクのリストを返します(重複はありません)。
func pageLinks(links [] string、n * html.Node)[] string
リンクスライスを取得したら、それらを繰り返し処理し、少しチェックします。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>
タグ:html.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
この再帰関数のパラメーターとしてのスライス。リンクはチェックすることによって発見されます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
URLのコンテンツを取得するstdlib機能(http.Get()
)そして、golang.org/x/net/html
html.Parse()
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
ブールフラグ。trueの場合は実行されます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
今回はページタイトルがキーになっているので、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プログラミング言語DonovanとKernighanによる本は、本全体の例としてWebクローラーを使用し、新しい概念を導入するためにさまざまな章で変更しています。この記事で提供されるコードは、本からインスピレーションを得ています。
その他のチュートリアル:
- NGINXリバースプロキシを使用してGoサービスを提供する
- Goで構造体のコピーを作成する
- GoWebサーバーの基本
- Goでのマップタイプの並べ替え
- 一言で言えばポインタを移動します
- タグの説明に行く
- 日付と時刻のフォーマットに移動
- Goを使用したJSON処理
- 可変個引数関数に移動
- GoStringsチートシート
- GoEmptyインターフェイスの説明
- Go withVSCodeとDelveのデバッグ
- NamedGoはパラメータを返します
- Goで乱数と文字列を生成する
- Goプロジェクトのファイルシステム構造
- Goに実装された二分探索アルゴリズム
- Goでのコマンドラインフラグの使用
- GOPATHの説明
- Goでコマンドラインアプリを作成する:lolcat
- Goを使用したCLIコマンドの作成:cowsay
- Goでのシェルパイプの使用
- CLIチュートリアルに移動:フォーチュンクローン
- Goを使用してフォルダ内のファイルを一覧表示します
- Goを使用して、GitHubからリポジトリのリストを取得します
- 移動し、文字列のスライスをファイルに追加します
- 文字列をバイトスライスに変換します
- GoでローカルGitの貢献を視覚化する
- GoCPUとメモリプロファイリングの開始
- Goプログラムの「インデックス作成をサポートしていません」エラーを解決する
- Goプログラムでの実行時間の測定
- 重複するタイトルを検出するためにGoを使用してWebクローラーを構築する
- ベストプラクティスに進む:ポインターまたは値のレシーバー?
- ベストプラクティスに進む:メソッドまたは関数を使用する必要がありますか?
- データ構造の移動:設定
- マップのチートシートに移動
- Goでジェネリック型の実装を生成する
- Goデータ構造:辞書
- Goデータ構造:ハッシュテーブル
- Go throughChannelsにイベントリスナーを実装する
- Goデータ構造:スタック
- データ構造の移動:キュー
- Goデータ構造:二分探索木
- Goデータ構造:グラフ
- Goデータ構造:リンクリスト
- Goデータ構造の完全ガイド
- Go値の比較
- Goはオブジェクト指向ですか?
- GoでのSQLデータベースの操作
- Goでの環境変数の使用
- チュートリアルに進む:PostgreSQLに裏打ちされたREST API
- GoWebサーバーでのCORSの有効化
- DockerコンテナへのGoアプリケーションのデプロイ
- GoがPHP開発者として学ぶための強力な言語である理由
- 移動し、io.Reader.ReadString改行文字を削除します
- 移動、変更を監視してプログラムを再構築する方法
- 行って、日付からの月を数えます
- GoでHTTPPOSTパラメーターにアクセスする