私も先ほど、まったく同様のエラーが出ました。
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)
SQLite
1UPDATE race_info SET race_class=REPLACE(race_class,"サラ系3歳以上1勝クラス","サラ系3歳以上1勝クラス500万下");
Execute SQLタブを開き、上記のコードを貼り付けて、実行してください。
▶のアイコンを押し、successと下段(結果出力)に表示されれば、無事、500万下が挿入されています。エラーになった場合は、結果出力が赤くなります。
③SQL文を発行し、データを修正(その2)
サラ系3歳以上2勝クラスでもエラーが出たので、
SQLite
1UPDATE race_info SET race_class=REPLACE(race_class,"サラ系3歳以上2勝クラス","サラ系3歳以上2勝クラス1000万下");
を実行する。
④SQL文を発行し、データを修正(その3)
サラ系2歳1勝クラスでもエラーが出たので、
SQLite
1UPDATE 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までのデータを削除する。
上から順番に行うこと(そうしないと、外部キー参照エラーになる)
SQLite
1DELETE FROM payoff WHERE race_id < 31581;
2DELETE FROM race_result WHERE race_id < 31581;
3DELETE FROM race_info WHERE id < 31581;
⑨sbt "run genfeature"を再実行
以上で準備ができたので、DB Browserを閉じ、閉じる際に保存するかどうか聞いてくるので、Saveを必ず行う。
再度、sbt "run genfeature"を実行する。
⑩重複行の確認
SQLite
1SELECT * 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行目を下記のように書き換える
SQLite
1 //タイム指数が表示される2006年以降のデータだけ使う
2 //filter(file => FilenameUtils.getBaseName(file.getName).take(4) >= "2006").
3 filter(file => FilenameUtils.getBaseName(file.getName) > "201910021212").
//は、コメントアウトの意味。
ここの処理は、①でメモしたファイル名を拡張子なしで書いているが、そのファイルよりも新しいもののみ、sbt "run scrapehtml"の対象とするということ。よって、それ以前のファイルがあっても、処理されないし、すでにrace.dbに最新の状態でデータが格納されているわけだから、問題はない。
次に、1993行目あたりを次のように書き換える。
SQLite
1 //過去10年分のURLを収集する
2 //RaceListScraper.scrape(period = 12 * 10)
3 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"を実行する
最新のデータとの差分だけの処理となるので、短時間で済むはず。
以上です。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。