Oct 31, 2020
ネットで検索というのは情報を得る手段として最も基本的だが, スクレイピングによって他のwebサイトからある情報だけを自動的に抽出することができ, 強力なツールとなり得よう. goqueryでお手軽スクレイピング! が参考になるが, ここでは一つ一つの関数を詳しく解説する.
例えば東京都港区の天気をスクレイピングしてcsvにデータを保存したいとする. https://tenki.jp/forecast/3/16/4410/13103/ のソースを見てみると, 次のようなタグから10日間の天気の情報がわかる.
<section class="forecast-point-week-wrap">
<h3 class="bottom-style date-set">港区の10日間天気</h3>
<table class="forecast-point-week forecast-days-long">
<tr>
<th class="citydate">日付</th> <td class="cityday">
<p class="date-box">01月02日</p>
<p class="youbi-box">(<span class="saturday">土</span>)</p>
</td>
<td class="cityday">
<p class="date-box">01月03日</p>
<p class="youbi-box">(<span class="sunday">日</span>)</p>
</td>
<td class="cityday">
<p class="date-box">01月04日</p>
<p class="youbi-box">(<span>月</span>)</p>
</td>
<td class="cityday">
<p class="date-box">01月05日</p>
<p class="youbi-box">(<span>火</span>)</p>
</td>
<td class="cityday">
<p class="date-box">01月06日</p>
<p class="youbi-box">(<span>水</span>)</p>
</td>
<td class="cityday">
<p class="date-box">01月07日</p>
<p class="youbi-box">(<span>木</span>)</p>
</td>
<td class="cityday">
<p class="date-box">01月08日</p>
<p class="youbi-box">(<span>金</span>)</p>
</td>
<td class="cityday">
<p class="date-box">01月09日</p>
<p class="youbi-box">(<span class="saturday">土</span>)</p>
</td>
<td class="cityday">
<p class="date-box">01月10日</p>
<p class="youbi-box">(<span class="sunday">日</span>)</p>
</td>
</tr>
<tr>
<th class="ships-info">天気</th> <td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/01.png" alt="晴" title="晴" width="47" height="30"><p>晴</p></td>
<td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/02.png" alt="晴時々曇" title="晴時々曇" width="47" height="30"><p>晴時々曇</p></td>
<td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/12.png" alt="曇のち晴" title="曇のち晴" width="47" height="30"><p>曇のち晴</p></td>
<td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/13.png" alt="曇のち雨" title="曇のち雨" width="47" height="30"><p>曇のち雨</p></td>
<td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/03.png" alt="晴一時雨" title="晴一時雨" width="47" height="30"><p>晴一時雨</p></td>
<td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/05.png" alt="晴のち曇" title="晴のち曇" width="47" height="30"><p>晴のち曇</p></td>
<td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/02.png" alt="晴時々曇" title="晴時々曇" width="47" height="30"><p>晴時々曇</p></td>
<td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/01.png" alt="晴" title="晴" width="47" height="30"><p>晴</p></td>
<td class="weather-icon"><img src="https://static.tenki.jp/images/icon/forecast-days-weather/01.png" alt="晴" title="晴" width="47" height="30"><p>晴</p></td>
</tr>
<tr>
<th class="ships-info">気温<br>(℃)</th> <td><p class="high-temp">10</p><p class="low-temp">1</p></td>
<td><p class="high-temp">8</p><p class="low-temp">1</p></td>
<td><p class="high-temp">9</p><p class="low-temp">1</p></td>
<td><p class="high-temp">8</p><p class="low-temp">2</p></td>
<td><p class="high-temp">11</p><p class="low-temp">4</p></td>
<td><p class="high-temp">9</p><p class="low-temp">1</p></td>
<td><p class="high-temp">5</p><p class="low-temp">1</p></td>
<td><p class="high-temp">4</p><p class="low-temp">-2</p></td>
<td><p class="high-temp">6</p><p class="low-temp">-2</p></td>
</tr>
<tr>
<th class="ships-info">降水<br>確率</th> <td><p class="precip">0<span class="unit">%</span></p> </td>
<td><p class="precip">30<span class="unit">%</span></p> </td>
<td><p class="precip">40<span class="unit">%</span></p> </td>
<td><p class="precip">80<span class="unit">%</span></p> </td>
<td><p class="precip">80<span class="unit">%</span></p> </td>
<td><p class="precip">40<span class="unit">%</span></p> </td>
<td><p class="precip">40<span class="unit">%</span></p> </td>
<td><p class="precip">20<span class="unit">%</span></p> </td>
<td><p class="precip">20<span class="unit">%</span></p> </td>
</tr>
</table><!-- /.forecast-point-week -->
</section>
タグを抽出するために, doc.Find()によってセレクションを取得する. セレクションとはあるタグで囲まれる部分全体(タグ自身も含める). セレクション.Find()によってさらに深いタグを取得していく. セレクション.Text()によってプレインテキストを取得する. タグの属性はまた別の関数で取得できる.
package main
import (
"github.com/PuerkitoBio/goquery"
"os"
"time"
"log"
"encoding/csv"
)
type Result struct {
days []string
youbis []string
weathers []string
hightemps []string
lowtemps []string
}
func GetPage(url string) Result {
doc, _ := goquery.NewDocument(url)
s := doc.Find("table.forecast-point-week").First()
tbodies := s.Find("tbody")
tbody := tbodies.First()
var days []string
var youbis []string
var weathers []string
var hightemps []string
var lowtemps []string
tbody.Find("td.cityday").Each(func(_ int, s *goquery.Selection) {
cityday := s.Find("p.date-box").First().Text()
youbi := s.Find("span").First().Text()
days = append(days, cityday)
youbis = append(youbis, youbi)
})
tbody.Find("td.weather-icon").Each(func(_ int, s *goquery.Selection) {
weather := s.Find("p").First().Text()
weathers = append(weathers, weather)
})
tbody.Find("p.high-temp").Each(func(_ int, s *goquery.Selection) {
hightemp := s.Text()
hightemps = append(hightemps, hightemp)
})
tbody.Find("p.low-temp").Each(func(_ int, s *goquery.Selection) {
lowtemp := s.Text()
lowtemps = append(lowtemps, lowtemp)
})
return Result{days,youbis,weathers, hightemps,lowtemps}
}
func main() {
urls := []string{"https://tenki.jp/forecast/1/2/1400/1100/", "https://tenki.jp/forecast/2/7/3410/4100/", "https://tenki.jp/forecast/3/16/4410/13103/"}
locations := []string{"sapporo", "sendai", "minatoku", }
for index, url := range urls {
result := GetPage(url)
f ,err := os.Create(time.Now().Local().Format("2006010215") + locations[index])
if err != nil {
log.Fatal(err)
}
defer f.Close()
w := csv.NewWriter(f)
w.Write(result.days)
w.Write(result.youbis)
w.Write(result.weathers)
w.Write(result.hightemps)
w.Write(result.lowtemps)
w.Flush()
}
}
スクレイピングは自動的処理なので, 当然これを実行するのも自動的に行いたい場合も多い. systemdのデーモンに次のように登録する.