コース目次 / 第3章
成果物 — リンクの生死を調べる道具
URLの一覧を読んで、生きているかどうかをまとめて調べるCLIを作ります。
道具を作ります。URLの一覧を読んで、それぞれが生きているか調べるCLIです。ブックマークやREADMEのリンク切れ点検に、そのまま使えます。
今日のゴール
OK https://www.python.org/ (200)
OK https://go.dev/ (200)
OK https://tsumiki-key.pages.dev/ (200)
NG https://example.com/kono-page-wa-nai (404)
4件を 431ms で調べました1. 調べたいURLの一覧を作る
urls.txt を作ります。
# 1行に1つURLを書く。# で始まる行は飛ばす。
https://www.python.org/
https://go.dev/
https://tsumiki-key.pages.dev/
https://example.com/kono-page-wa-nai2. ミニ解説: 今日使う道具
| 書き方 | 何をするか |
|---|---|
os.Open |
ファイルを開く。失敗するかもしれないので err も返る |
defer f.Close() |
「この関数が終わるとき閉じる」を先に予約する |
bufio.Scanner |
1行ずつ読む |
http.Client + Get |
URLを見に行く |
res.StatusCode |
200なら生きている、404なら無い |
defer はGoらしい仕組みです。「あとで必ずやること」を、忘れないうちに先に書いておけます。ファイルを開いた直後に閉じる予約を書く——この並びに慣れると、閉じ忘れが起きなくなります。
そして、試食で予告した if err != nil が本当にたくさん出てきます。Goの挨拶です。
3. 書く
hello.go は役目を終えたので削除して、main.go を作ります。
ターミナル派の方
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
rm は PowerShell でもそのまま使えます(Remove-Item の別名です)。
// urls.txt に並べたURLを1つずつ見に行って、生きているかどうかを表示する。
package main
import (
"bufio"
"fmt"
"net/http"
"os"
"strings"
"time"
)
// 待ち続けないように、1件あたりの上限を決めておく。
var client = &http.Client{Timeout: 10 * time.Second}
// readURLs は、ファイルからURLを1行ずつ読む。空行と # で始まる行は飛ばす。
func readURLs(path string) ([]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var urls []string
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
urls = append(urls, line)
}
return urls, scanner.Err()
}
// check は1件のURLを見に行って、表示する1行を作る。
func check(url string) string {
res, err := client.Get(url)
if err != nil {
return fmt.Sprintf("NG %s (%v)", url, err)
}
defer res.Body.Close()
if res.StatusCode >= 400 {
return fmt.Sprintf("NG %s (%d)", url, res.StatusCode)
}
return fmt.Sprintf("OK %s (%d)", url, res.StatusCode)
}
func main() {
path := "urls.txt"
if len(os.Args) > 1 {
path = os.Args[1]
}
urls, err := readURLs(path)
if err != nil {
fmt.Println("URLの一覧が読めませんでした:", err)
os.Exit(1)
}
start := time.Now()
for _, url := range urls {
fmt.Println(check(url))
}
fmt.Printf("\n%d件を %v で調べました\n", len(urls), time.Since(start).Round(time.Millisecond))
}4. 走らせる
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
一覧が出れば成功です。最後の1件は NG になります。わざと存在しないURLを入れてあるからです。
いま気づいてほしいこと: 4件を調べるのに、それなりに待ちましたね。1件ずつ順番に見に行っているからです。10件なら10倍待ちます。
これを次の章で直します。Goがいちばん得意なやり方で。
よそのサイトを見に行く道具だ、ということを忘れないでください。
相手のサーバに毎回リクエストを送っています。同じサイトのURLを何十件も並べる(たとえば1つのサイトのページ一覧をまるごと貼る)と、相手にとっては短時間の集中アクセスになり、迷惑をかけたり、こちら側が一時的に締め出されたりします。
他人のサイトに向けるときは、件数を控えめに。次の章で、同時に投げる数を絞る方法も紹介します。
あなたの番
urls.txt を自分がよく見るサイトに書き換えて、もう一度実行してください。ブックマークから5つほど貼るだけです。まずは5〜10件くらいから。
ハマったら
会社や学校のネットワークで全部NGになる
プロキシ(社内ネットの関所)に阻まれている可能性が高いです。URLを http://localhost:... のような手元のものに変えるか、個人のネットワークで試してください。プログラムは正しく動いています。
NG に「no such host」と出る
そのURLのドメイン自体が見つかっていません。綴りを確認してください。これも道具としては正しい動作で、リンク切れを見つけたということです。
ZIPで戻したい
このページの下のZIPは、この章を終えた状態のフォルダです。解凍して、その中で作業を続けられます。自分で書き換えた部分は消えてしまうので、先に自分のファイルを別の場所へ待避してから使ってください。
こうなっていればOK
うまくいかないときは、この章の完成ファイル一式(ZIP)をダウンロードして続きから進めても大丈夫です。
卒業まであと1章です。
この章はまだ完了していません。