質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.09%

stockedge/netkeiba-scraperの動作の終盤で、エラーが発生して、困っております。

受付中

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,276

score 4

前提・実現したいこと

scala初心者でございます。  
stockedge/netkeiba-scraperで競馬情報をスクレイピングしておりますが、終盤の動作の途中で、以下のエラーメッセージが発生しました。

発生している問題・エラーメッセージ

[error] (run-main-0) java.lang.RuntimeException: class not found:サラ系3歳以上1勝クラス[指](定量)
[error] java.lang.RuntimeException: class not found:サラ系3歳以上1勝クラス[指](定量)
[error]     at scala.sys.package$.error(package.scala:26)
[error]     at Util$.str2cls(Main.scala:1855)
[error]     at FeatureGenerator.$anonfun$gradeChanged$1(Main.scala:1521)
[error]     at scala.runtime.java8.JFunction1$mcII$sp.apply(JFunction1$mcII$sp.java:12)
[error]     at scala.Option.map(Option.scala:146)
[error]     at FeatureGenerator.<init>(Main.scala:1519)
[error]     at FeatureGenerator$.$anonfun$iterator$2(Main.scala:662)
[error]     at scala.collection.Iterator$$anon$10.next(Iterator.scala:455)
[error]     at scala.collection.Iterator$GroupedIterator.takeDestructively(Iterator.scala:1155)
[error]     at scala.collection.Iterator$GroupedIterator.go(Iterator.scala:1170)
[error]     at scala.collection.Iterator$GroupedIterator.fill(Iterator.scala:1207)
[error]     at scala.collection.Iterator$GroupedIterator.hasNext(Iterator.scala:1211)
[error]     at scala.collection.Iterator.foreach(Iterator.scala:937)
[error]     at scala.collection.Iterator.foreach$(Iterator.scala:937)
[error]     at scala.collection.AbstractIterator.foreach(Iterator.scala:1425)
[error]     at FeatureDao$.$anonfun$rr2f$1(Main.scala:1740)
[error]     at FeatureDao$.$anonfun$rr2f$1$adapted(Main.scala:1737)
[error]     at scalikejdbc.DBConnection.readOnly(DBConnection.scala:202)
[error]     at scalikejdbc.DBConnection.readOnly$(DBConnection.scala:200)
[error]     at scalikejdbc.DB.readOnly(DB.scala:60)
[error]     at scalikejdbc.DB$.$anonfun$readOnly$1(DB.scala:175)
[error]     at scalikejdbc.LoanPattern.using(LoanPattern.scala:18)
[error]     at scalikejdbc.LoanPattern.using$(LoanPattern.scala:16)
[error]     at scalikejdbc.DB$.using(DB.scala:140)
[error]     at scalikejdbc.DB$.readOnly(DB.scala:174)
[error]     at FeatureDao$.rr2f(Main.scala:1737)
[error]     at Main$.main(Main.scala:1995)
[error]     at Main.main(Main.scala)
[error]     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error]     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error]     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error]     at java.lang.reflect.Method.invoke(Method.java:498)
[error] Nonzero exit code: 1
[error] (Compile / run) Nonzero exit code: 1
[error] Total time: 5435 s, completed 2019/08/28 2:51:53

該当のソースコード

[info] Loading project definition from /Users/AAA/Desktop/netkeiba-scraper-master/project
[info] Loading settings for project netkeiba-scraper-master from build.sbt ...
[info] Set current project to netkeiba-scraper-master (in build file:/Users/AAA/Desktop/netkeiba-scraper-master/)
[info] Running Main genfeature
01:21:20.287 [run-main-0] DEBUG scalikejdbc.ConnectionPool$ - Registered connection pool : ConnectionPool(url:jdbc:sqlite:race.db, user:null) using factory : <default>
01:21:20.291 [run-main-0] DEBUG scalikejdbc.ConnectionPool$ - Registered singleton connection pool : ConnectionPool(url:jdbc:sqlite:race.db, user:null)

試したこと

当たり前のことではございますが、stockedge/netkeiba-scraperの使い方の通りに最初からコマンドを実行してみました。 
(以下マニュアル引用)

以下のコマンドを上から順に実行していけば最後に素性が作成される。

①sbt "run collecturl" 
レース結果が載っているURLを収集して「race_list.txt」に保存する。

②sbt "run scrapehtml" 
レース結果のHTMLをスクレイピングしてhtmlフォルダに保存する。HTMLをまるごとスクレイピングするので結構時間がかかる。

③sbt "run extract" 
HTMLからレース結果を抜き出しSQLiteに保存する。

④sbt "run genfeature"
レース結果を元にして素性を作りSQLiteに保存する。

この④のコマンドを実行後に上記のエラーが発生いたしました。 

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

0

エラーがでているのは下の文字列が含まれていないからですが、何をしようとしているのかわからないので作者に聞いて見てはどうですか_

  val str2clsMap =
    Array(
      "オープン",
      "1600万下",
      "1000万下",
      "500万下",
      "未勝利",
      "新馬").zipWithIndex

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

私も先ほど、まったく同様のエラーが出ました。

scalaはちょっとソースの意味がわからない部分がありましたが、若干修正を加えたり、DBをいじることで問題を回避することができました。
他の作業の合間に2,3時間でやったことを箇条書きでまとめたことを書いておきます。
本来ならば、ソースを移植してpythonで素性データを作成するようなことをしたいのですが、ちょっと忙しく手をつけられないので、とりあえずの措置です。

まず、基本的なことからわかる範囲で説明します。以下、万一間違いがあったら、どなたかご指摘ください。

素性(そせい)とは、特徴量のことです。特徴量を使って機械学習させ、予測なりなんなりするのが目的です。このデータを得るためにwebスクレイピングします。ただし、webから取得した生データはそのまま扱えないので、数値変換などを行うことで、特徴量をまとめたテーブルを作成するということをsbt "run genfeature"で行います。そのコマンドで作成されるテーブルは、featureテーブルです。

上の方(rysh様)が書かれたエラーの原因箇所ですが、これはクラス(グレード)になります。
競馬のグレードと、Main.scalaのソース内のstr2clsMapのArray配列の中身が対応しています。
競馬のルールレースのクラス分け
を参照するとわかりますが、

オープン:0
1600万下:1
1000万下:2
500万下:3
未勝利:4
新馬:5

といったように、sbt "run genfeature"を実行すると、数値に変換されてfeatureテーブルに格納されます。

イメージ説明

エラーの出た箇所は、サラ系3歳以上1勝クラス[指](定量)となっており、1勝クラスであれば500万下になりますが、500万下という文字列が含まれないためにエラーとなっています(図は、race_infoテーブルのrace_classカラムの、エラーの出たのと同様のデータですが、すでにSQL文で置換してしまったものです)。

エラーを回避するには、ソースを修正するか、テーブルを修正するかします。

まず、準備として、DB Browser for SQLiteをインストールしてください。このアプリは、プラットフォームを選ばない上、使い方が簡単なので、もし導入されていないならば、是非お使いください。
インストールや使い方の説明は長くなるので省かせていただきますが、DB操作がわからないと話にならないので、もしご存知ないならば、調べるなり勉強するなりして覚えてください。

以下、私が行ったことを書きます。

DB Browserでの作業

①DBのバックアップ
race.dbをコピーしてバックアップ用として別のフォルダへ貼り付けてください。

DB Browser for SQLiteを起動し、File>Open DataBaseを選択して、race.dbを開いてください。

②SQL文を発行し、データを修正(その1)

UPDATE race_info SET race_class=REPLACE(race_class,"サラ系3歳以上1勝クラス","サラ系3歳以上1勝クラス500万下");

Execute SQLタブを開き、上記のコードを貼り付けて、実行してください。
イメージ説明
▶のアイコンを押し、successと下段(結果出力)に表示されれば、無事、500万下が挿入されています。エラーになった場合は、結果出力が赤くなります。

③SQL文を発行し、データを修正(その2)
サラ系3歳以上2勝クラスでもエラーが出たので、

UPDATE race_info SET race_class=REPLACE(race_class,"サラ系3歳以上2勝クラス","サラ系3歳以上2勝クラス1000万下");

を実行する。

④SQL文を発行し、データを修正(その3)
サラ系2歳1勝クラスでもエラーが出たので、

UPDATE race_info SET race_class=REPLACE(race_class,"サラ系2歳1勝クラス","サラ系2歳1勝クラス500万下");


を行う。

⑤各テーブルをCSV出力
イメージ説明
Browse Dataでテーブル(payoff,race_info,race_resultの各テーブル)を選択し、File>ExportでCSV形式でエクスポートする。

⑥エラー箇所のrace_idの行番号を確認
イメージ説明
featureテーブルの最後のrace_idに何頭立てのデータが入っているか確認する。
ここでエラーが出た。実際の頭数を満たしていないので、最後のrace_idを持つ行をすべて手動で削除する。
featureテーブルのrace_idが31581を右クリックでDelete Recordし、すべて削除する。

⑦sqlite_sequenceテーブル
sqlite_sequenceテーブルのseqの値を、seqの値(34390)-31580=2810にする。
もしかしたら、必要ないかも。

⑧payoff,race_info,race_resultテーブルのデータの削除
payoff,race_info,race_resultの各テーブルをそれぞれ、sql文でrace_idが1~31580までのデータを削除する。
上から順番に行うこと(そうしないと、外部キー参照エラーになる)

DELETE FROM payoff WHERE race_id < 31581;
DELETE FROM race_result WHERE race_id < 31581;
DELETE FROM race_info WHERE id < 31581;

⑨sbt "run genfeature"を再実行
以上で準備ができたので、DB Browserを閉じ、閉じる際に保存するかどうか聞いてくるので、Saveを必ず行う。
再度、sbt "run genfeature"を実行する。

⑩重複行の確認

SELECT * FROM feature WHERE (race_id, horse_id) in ( SELECT race_id, horse_id FROM feature WHERE race_id > 31580 GROUP BY race_id, horse_id HAVING COUNT(*) > 1 );


DB Browserを起動し、念のため、race_idが31581以上の行で重複行がないか確認。何も表示されていなければOK

⑪出力したcsvのインポート
DB Browserから各テーブルのcsvをインポートして、元に戻す。
この場合は、⑧とは逆に、race_infoを一番先にインポートする。

*注意:現時点までのスクレイピングは、sbt "run scrapehtml"を実行することで完了しているので、再び最初からnetkeiba-scraperのコマンドを行う必要はなく、上記の通りに行った時点で、素性データまで最新のものが作られている。

次からのスクレイピングの方法

一定期間ごとにスクレイピングするとして、時間短縮のため、過去1年以内のURLデータ(html自体ではなく、sbt "run collecturl"で行う分)を取得し、スクレイピングを取得済みの一番番号が上のもの以上にする・・・ちょっと何言ってるかわからないw

以下の通りにしてください。

①race_url.txt、race.db、htmlを削除する
race_url.txt、race.dbは、別フォルダにバックアップをコピーしておいてください。
htmlフォルダ内のファイル名で、一番最近のものをメモしておく(私の場合、201910021212.htmlだった)。このファイル名は、②で使う。
htmlを空にする。時間がかかるようであれば、コマンドプロンプトから
イメージ説明

のようにdel *でフォルダ内のファイルをすべて削除する。

なぜrace_url.txt、race.dbを削除するかというと、単純に、netkeiba-scraperの処理を行うと、race_url.txt、race.dbがすでに存在している場合、エラーになり停止するからです。

②Main.scalaを修正する

Main.scalaを2か所修正する。以降、秀丸のようなgrepか正規表現で置換できるエディタがあると便利。
まず、143行目を下記のように書き換える

      //タイム指数が表示される2006年以降のデータだけ使う
      //filter(file => FilenameUtils.getBaseName(file.getName).take(4) >= "2006").
    filter(file => FilenameUtils.getBaseName(file.getName) > "201910021212").


//は、コメントアウトの意味。
ここの処理は、①でメモしたファイル名を拡張子なしで書いているが、そのファイルよりも新しいもののみ、sbt "run scrapehtml"の対象とするということ。よって、それ以前のファイルがあっても、処理されないし、すでにrace.dbに最新の状態でデータが格納されているわけだから、問題はない。

次に、1993行目あたりを次のように書き換える。

        //過去10年分のURLを収集する
        //RaceListScraper.scrape(period = 12 * 10)
        RaceListScraper.scrape(period = 12 * 1)


すでに取得している過去10年分のurlを再び取得するのは馬鹿げているので、上記のように1年分にする。
これより短い期間を指定したかったが、わからなかった。

③sbt "run collecturl"を実行する
1年分のURLがrace_url.txtに格納される。
私の環境は、仮想環境内にubuntuを入れてwindows10との共有フォルダ内にnetkeiba-scraper一式を置いて実行しているが、デフォルトのCPU1個とメモリの割り当てで、3分ぐらいで終了した。

④race_url.txtから最新分だけ抜き出す
ある文字が含まれない行を削除するを参考にして、秀丸で、①でメモしたファイル名の201910021212の201910が含まれる行だけ一覧として取り出す(201910となっていると、2019年10月かと思うが、実際は、9月。それ以降のものを取得したいわけ)。
一覧を修正して、race_url.txtの書式通りに直し、race_url.txtでファイル保存する。要するに、1年分のurlを再びスクレイプするのも面倒なので、取得したファイルよりも新しいものだけをスクレイプするということ。
秀丸がなければ、"ある文字が含まれない行を削除する"で検索して、適当なエディタなり正規表現を使うなりして、race_url.txtを加工すると良い。

⑤sbt "run scrapehtml"を実行する
④で加工したにもかかわらず、480個ものurlを取得。20分ほどで処理が終了。

⑥sbt "run extract"を実行する
race.dbが作成されるが、取得済みのurlよりも新しいものがない場合は、データは空である。
DB Browserで確認のこと。

⑥バックアップしたrace.dbから、csv出力し、新しく生成されたrace.dbへインポート
⑥で新しく生成されたrace.dbにデータがなかった場合は、以降、やる必要なし。
①でバックアップを取っておいたrace.dbをDB Browserで開き、payoff,race_info,race_result各テーブルをcsv出力する。
新しく生成されたrace.dbに、バックアップしたrace.dbから出力された各テーブルのcsvを読み込む。
この際、『DB Browserでの作業』の⑪に書いた通り、race_id.csvから読み込む。

⑦重複行の確認
⑩重複行の確認で書いたのを参考に、各テーブルで重複行がないか確認する。
テーブルごとに、どのカラムがあれば一意にデータが決まるかを考えて、SQL文を発行して確認する。
ある行を特定できればよいので、なんなら、すべてのカラムをSQL文に書いてもよい。重複行があるなら、複数行結果表示されるはず。
重複行があったら、削除する。

⑧sbt "run genfeature"を実行する

最新のデータとの差分だけの処理となるので、短時間で済むはず。

以上です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.09%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る