転職ドラフトに参加してみたけど
いつ指名がくるんだろう、とそわそわ...
使ったことある人に聞くと最終日にどっと来るらしい
本当に?
調べてみよう
スクレイピングどうしよう
調べてみたけどNodeとかpythonとかGoがいいとか?
特にメリットはないんだろうけど最近ちょっと興味のあるGoでググりながらやってみる
Goでスクレイピング
goqueryを使ってみる
go get github.com/PuerkitoBio/goquery
過去の開催回全部集めるのは一旦あきらめて
第10回に絞ってみる
ソース
draft.go
package main
import (
"github.com/PuerkitoBio/goquery"
"fmt"
"regexp"
"time"
"strings"
"strconv"
)
func main() {
baseUrl := "https://job-draft.jp"
userOfferInfo := []string{}
offerTimes := []string{}
// リンク集取得
// fesLinks := getFestivalLinks()
// fmt.Println(fesLinks)
// 要素取得
// fesLinksをforeachしてbaseUrlとくっつける
fesInfo := getFesInfo(baseUrl + "/festivals/10")
fmt.Println(fesInfo[0])
fmt.Println("参加人数 : " + fesInfo[1])
fmt.Println("総指名数 : " + fesInfo[3])
// ユーザ一覧取得
userLinks := getUserInfo(baseUrl, "/festivals/10/users")
fmt.Println("取得したユーザ数 : " + strconv.Itoa(len(userLinks)))
// 各ユーザのオファー情報まとめて取得
fmt.Println("各ユーザのオファー情報取得")
for i := 0; i < len(userLinks); i++ {
userOfferInfo_tmp := getUserOfferLinks(baseUrl + userLinks[i], "10")
userOfferInfo = append(userOfferInfo, userOfferInfo_tmp...)
fmt.Println(strconv.Itoa(i) + " / " + strconv.Itoa(len(userLinks)) + "人")
}
// オファー情報から時間取得
for i := 0; i < len(userOfferInfo); i++ {
offerTime := getOfferTime(baseUrl + userOfferInfo[i])
offerTimes = append(offerTimes, offerTime)
fmt.Println(strconv.Itoa(i) + " / " + strconv.Itoa(len(userOfferInfo)) + "オファー")
}
fmt.Println("全オファー時間")
fmt.Println(offerTimes)
}
/**
* 過去の転職ドラフト開催回リンク集配列を取得
*/
func getFestivalLinks() ([]string) {
time.Sleep(1 * time.Second)
url := "https://job-draft.jp/festivals/"
fesLinks := []string{}
doc, err := goquery.NewDocument(url)
if err != nil {
panic(err)
}
// /festivals/[数値] のリンクを探す
reg := regexp.MustCompile(`\/festivals\/[0-9]+\z`)
doc.Find("a").Each(func(_ int, sel *goquery.Selection) {
url, _ := sel.Attr("href")
if reg.MatchString(url) {
fesLinks = append(fesLinks, url)
}
})
return fesLinks
}
// 開催回の情報取得
func getFesInfo(fesUrl string) ([]string) {
time.Sleep(1 * time.Second)
fesInfo := []string{}
doc, err := goquery.NewDocument(fesUrl)
if err != nil {
panic(err)
}
// 開催期間
doc.Find(".ibox-content").Each(func(_ int, sel *goquery.Selection) {
text := sel.Find("h3").Text()
if (strings.Contains(text, "開催日")) {
fesInfo = append(fesInfo, text)
}
})
// 参加人数/参加者数/総指名数/提示年収総額
doc.Find(".col-sm-3.col-with-border__col").Each(func(_ int, sel *goquery.Selection) {
text := sel.Find("span.emphasis-40").Text()
fesInfo = append(fesInfo, text)
})
return fesInfo
}
// ユーザ集取得
func getUserInfo(baseUrl string, usersPath string) ([]string) {
time.Sleep(1 * time.Second)
userLinks := []string{}
nextUrl := ""
usersUrl := baseUrl + usersPath
fmt.Println(usersUrl)
doc, err := goquery.NewDocument(usersUrl)
if err != nil {
panic(err)
}
// ユーザリンクと次ページリンクあれば再帰的に探す
reg := regexp.MustCompile(`\/users\/[0-9]+\z`)
doc.Find("a").Each(func(_ int, sel *goquery.Selection) {
// ユーザリンク探す
url, _ := sel.Attr("href")
if reg.MatchString(url) {
userLinks = append(userLinks, url)
}
// 次ページリンク探す
rel, _ := sel.Attr("rel")
if rel == "next" {
nextUrl = url
}
})
// 再帰的に探す
if nextUrl != "" {
nextUserLinks := getUserInfo(baseUrl, nextUrl)
userLinks = append(userLinks, nextUserLinks...)
}
return userLinks
}
// ユーザのオファー詳細へのリンク取得
func getUserOfferLinks(userUrl string, fesNum string) ([]string) {
time.Sleep(1 * time.Second)
offerLinks := []string{}
doc, err := goquery.NewDocument(userUrl)
if err != nil {
panic(err)
}
// 各回オファー取得
doc.Find("div.ibox").Each(func(_ int, sel *goquery.Selection) {
text := sel.Text()
// 指定回のオファーの中でオファーのリンク取得
if (strings.Contains(text, "第" + fesNum + "回 指名")) {
sel.Find("a").Each(func(_ int, sel2 *goquery.Selection) {
url, _ := sel2.Attr("href")
if strings.Contains(url, "biddings") {
offerLinks = append(offerLinks, url)
}
})
}
})
return offerLinks
}
// オファーの時間を取得
func getOfferTime(offerUrl string) (string){
time.Sleep(1 * time.Second)
offerTime := ""
doc, err := goquery.NewDocument(offerUrl)
if err != nil {
panic(err)
}
doc.Find("p > span.u-m-r-25").Each(func(_ int, sel *goquery.Selection) {
offerTime = sel.Text();
})
return offerTime
}
データをパース
goが最後に配列をどかんと吐いてくれるのでそれをテキストに写して
行単位のデータにする
offertimes.txt
2018.01.31 15:34
2018.01.20 13:35
2018.01.29 17:59
2018.01.31 18:54
2018.01.31 11:50
2018.01.20 15:01
2018.01.31 09:22
2018.01.31 11:56
2018.01.23 12:20
2018.01.31 14:54
2018.01.31 12:07
2018.01.31 22:57
2018.01.30 17:53
2018.01.29 15:17
2018.01.31 17:33
2018.01.31 22:53
2018.01.31 21:35
2018.01.31 11:32
.
.
.
あとは使い慣れてるphpで処理
offerTimeParse.php
<?php
$parseData = array();
$file = file_get_contents("./offertimes.txt");
$offerTimeList = explode(PHP_EOL, $file);
foreach ($offerTimeList as $key => $offerTime) {
// 年月日がドット区切りなのでスラッシュへ変換
$offerTime = str_replace(".", "/", $offerTime);
$timekey = date("Y-m-d H:00", strtotime($offerTime));
$parseData[$timekey] = isset($parseData[$timekey]) ? $parseData[$timekey] + 1 : 1;
}
ksort($parseData);
foreach ($parseData as $key => $value) {
echo $key . "," . $value . "\n";
}
php offerTimeParse.php > time.csv
集計したデータ
ってことでデータを見ると
日時 | オファー数 |
---|---|
2018-01-17 15:00 | 4 |
2018-01-17 16:00 | 4 |
2018-01-17 17:00 | 6 |
2018-01-18 09:00 | 3 |
2018-01-18 18:00 | 1 |
2018-01-18 21:00 | 1 |
2018-01-19 06:00 | 1 |
2018-01-19 13:00 | 2 |
2018-01-19 14:00 | 2 |
2018-01-19 15:00 | 1 |
2018-01-19 16:00 | 1 |
2018-01-19 17:00 | 3 |
2018-01-19 18:00 | 1 |
2018-01-20 13:00 | 2 |
2018-01-20 14:00 | 1 |
2018-01-20 15:00 | 1 |
2018-01-21 10:00 | 2 |
2018-01-22 15:00 | 1 |
2018-01-22 16:00 | 3 |
2018-01-22 17:00 | 1 |
2018-01-23 10:00 | 2 |
2018-01-23 11:00 | 5 |
2018-01-23 12:00 | 5 |
2018-01-23 13:00 | 2 |
2018-01-23 15:00 | 4 |
2018-01-23 16:00 | 4 |
2018-01-23 17:00 | 2 |
2018-01-23 18:00 | 3 |
2018-01-23 19:00 | 1 |
2018-01-23 20:00 | 1 |
2018-01-24 15:00 | 1 |
2018-01-24 17:00 | 2 |
2018-01-24 18:00 | 2 |
2018-01-25 11:00 | 2 |
2018-01-25 12:00 | 3 |
2018-01-25 13:00 | 1 |
2018-01-25 16:00 | 2 |
2018-01-25 17:00 | 1 |
2018-01-25 18:00 | 7 |
2018-01-26 11:00 | 1 |
2018-01-26 12:00 | 1 |
2018-01-26 14:00 | 2 |
2018-01-26 15:00 | 5 |
2018-01-26 17:00 | 20 |
2018-01-26 18:00 | 7 |
2018-01-26 20:00 | 7 |
2018-01-27 03:00 | 1 |
2018-01-27 19:00 | 1 |
2018-01-28 03:00 | 1 |
2018-01-28 04:00 | 1 |
2018-01-28 11:00 | 1 |
2018-01-28 12:00 | 1 |
2018-01-29 02:00 | 1 |
2018-01-29 04:00 | 1 |
2018-01-29 11:00 | 2 |
2018-01-29 12:00 | 7 |
2018-01-29 13:00 | 10 |
2018-01-29 14:00 | 17 |
2018-01-29 15:00 | 13 |
2018-01-29 16:00 | 10 |
2018-01-29 17:00 | 8 |
2018-01-29 18:00 | 8 |
2018-01-29 19:00 | 10 |
2018-01-29 20:00 | 4 |
2018-01-29 21:00 | 13 |
2018-01-29 22:00 | 3 |
2018-01-30 00:00 | 1 |
2018-01-30 01:00 | 3 |
2018-01-30 02:00 | 7 |
2018-01-30 03:00 | 7 |
2018-01-30 04:00 | 4 |
2018-01-30 10:00 | 9 |
2018-01-30 11:00 | 4 |
2018-01-30 12:00 | 10 |
2018-01-30 13:00 | 16 |
2018-01-30 14:00 | 10 |
2018-01-30 15:00 | 13 |
2018-01-30 16:00 | 31 |
2018-01-30 17:00 | 20 |
2018-01-30 18:00 | 38 |
2018-01-30 19:00 | 20 |
2018-01-30 20:00 | 18 |
2018-01-30 21:00 | 19 |
2018-01-30 22:00 | 5 |
2018-01-30 23:00 | 4 |
2018-01-31 00:00 | 4 |
2018-01-31 01:00 | 4 |
2018-01-31 09:00 | 25 |
2018-01-31 10:00 | 44 |
2018-01-31 11:00 | 44 |
2018-01-31 12:00 | 20 |
2018-01-31 13:00 | 27 |
2018-01-31 14:00 | 15 |
2018-01-31 15:00 | 33 |
2018-01-31 16:00 | 29 |
2018-01-31 17:00 | 23 |
2018-01-31 18:00 | 36 |
2018-01-31 19:00 | 44 |
2018-01-31 20:00 | 50 |
2018-01-31 21:00 | 39 |
2018-01-31 22:00 | 19 |
結論
最終日の夜 > 最終日の朝 > 最終日の前日
といった感じですかね
開催期間が終わるまで諦めず待ちましょう
注意
スクレイピングのお作法には要注意です
ガンガンアクセスしないように