はじめに
あっというまに夏休みが終わるね(-.-)
なんにもやってない夏休みはつまらないので、Webからデータを作るスクレイピング入門みたいなのやります。
日経競馬からデータを持ってきて、Microsoft Accessへデータをロードするまでの簡単なサンプルを説明します。スクレイピングやったことない人のために。ガンガン応用してください。
盛夏
前提
- OS : Windows7以上
- PoweShellのターミナルで実行
- VSCodeでコード編集
- node.js環境構築済み
- Microsoft Accessは2010以上
アプリ構成
№ | アプリ | 機能 |
---|---|---|
1 | jockey_leading_get.js | リーディング騎手データのスクレイピング |
2 | jockey_leading_tocsv.js | リーディング騎手データのCSVファイル変換 |
3 | jockey_leading_table.js | リーディング騎手データのAccessテーブル作成 |
4 | jockey_leading_load.js | リーディング騎手データのcsvのテーブルへロード |
jockey_leading_get.js
var request = require('request');
var Iconv = require('iconv').Iconv;
var fs = require('fs');
const file = ".\\jockey_leading.html";
var options = {
url: 'https://db.netkeiba.com/?pid=jockey_leading',
encoding: null,
method: 'GET'
}
/**
* AppendFile
* @param {String} path
* @param {String} data
*/
function AppendFile(path, data) {
fs.appendFile(path, data, function (err) {
if (err) {
throw err;
}
})
}
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
var iconv = new Iconv('EUC-JP', 'UTF-8//TRANSLIT//IGNORE');
body = iconv.convert(body).toString();
AppendFile(file,body);
}
})
説明
- リーディング騎手データを「jockey_leading.html」として保存。
- 日経競馬のページがEUC-JPのため、npm iconvをインストールしてUTF-8にコード変換。
iconv install
npm install iconv --save
node-gypのrebuildで失敗したら、ここを参照。
実行
node jockey_leading_get.js
結果
PS C:\Users\~> ls jockey_leading.html
ディレクトリ: C:\Users\~
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2019/08/15 15:22 97440 jockey_leading.html
jockey_leading_tocsv.js
/**
* @param {String} html
*/
function htmltocsv(html){
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
const http = new XMLHttpRequest();
const file = ".\\jockey_leading.csv";
UnlinkFile(file);
http.open("GET", html, true);
http.responseType = "document";
http.send(null);
var csv = [];
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status==200) {
var data = http.responseText;
var lines = data.split( '\n' );
var sw = 0;
var body = 0;
var output = '';
// header
const head1 = '順位,騎手名,所属,生年月日,1着,2着,3着,着外,重賞,,特別,,平場,,芝,,ダート,,勝率,連対率,複勝率,収得賞金(万円),代表馬,順位変動' + '\n';
const head2 = ',,,,,,,,出走,勝利,出走,勝利,出走,勝利,出走,勝利,出走,勝利,,,,,' + '\n';
csv.push(head1);
csv.push(head2);
for ( var i = 0; i < lines.length; i++ ) {
// 空行は無視する
if ( lines[i] == '' ) {
continue;
}
// <table 抽出
if ( sw == 0 & lines[i].substr(0,6) == '<table' ) { sw = -1; }
if ( sw == -1 ) {
// body start keyword
if (lines[i].substr(0,11) == '<td nowrap>') { body = -1; }
if ( body == -1 ) {
if ( lines[i] == '</tr><tr>' ) {
csv.push(output.substr(0, output.length - 1) + '\n');
var output = '';
} else {
output = output + '"' + body_htmltocsv(lines[i]) + '",';
}
}
}
if ( sw == -1 & lines[i].substr(0,8) == '</table>' ) {
csv.push(output.substr(0, output.length - 20) + '\n');
sw = 0;
}
}
}
for ( var i = 0; i < csv.length; i++) { AppendFileSync(file, csv[i]); }
}
}
/**
* AppendFileSync
* @param {Sting} path
* @param {Sting} data
*/
function AppendFileSync(path, data) {
const fs = require("fs");
fs.appendFileSync(path, data, function (err) {
if (err) {
throw err;
}
})
}
/**
* @param {String} path
*/
function UnlinkFile(path) {
const fs = require("fs");
fs.unlink(path, function(err) {
if (err) {
console.log(err);
}
})
}
/**
*
* @param {String} data
*
*/
function body_htmltocsv(data) {
var pos = data.indexOf("</a>");
// </a>がない
if ( pos == -1 ) {
var data = data.replace('<td nowrap>', '');
var data = data.replace('</td>', '');
} else {
var pos = data.indexOf('<td nowrap>');
// 先頭が<td nowrap>
if ( pos == 0 ) {
var pos = data.indexOf('>',12);
var pos2 = data.indexOf('<',13);
var data = data.substr( pos + 1, pos2 - pos -1 );
} else {
var pos = data.indexOf('<td class="txt_l" nowrap>');
// 先頭が<td class="txt_l" nowrap>
if ( pos == 0 ) {
var pos = data.indexOf('[東]');
var pos2 = data.indexOf('[西]');
// [東][西]を含む
if ( pos != -1 || pos2 != -1 ) {
var pos = data.indexOf('>',29);
var pos2 = data.indexOf('<',30);
if ( pos != -1 ) {
var data = '[東]' + data.substr( pos + 1, pos2 - pos -1 );
} else {
var data = '[西]' + data.substr( pos + 1, pos2 - pos -1 );
}
} else {
var pos = data.indexOf('>',25);
var pos2 = data.indexOf('<',26);
var data = data.substr( pos + 1, pos2 - pos -1 );
}
}
}
}
return data;
}
htmltocsv('file:' + '.\\jockey_leading.html');
説明
- npm xmlhttprequestをインストール。直接htmlをfsで読み込んでもbody_htmltocsv関数は動作可能ですが、あえて将来httpからダイレクトにcsv化をもくろんで。
- ヘッダー2行をスキップしてもいいかも、後続の「jockey_leading_load.js」ではヘッダー2行をスキップしてテーブルにロード。
- http.responseTextを'\n'でsplitして1行ずつ処理します。
- <table ~</table>を処理
- <td nowrap>~</tr>を処理
- jockey_leading.htmlのテーブルを解析して、各要素をbody_htmltocsv関数で取出し配列(csv[])に格納。
- 出力ファイル「jockey_leading.csv」
- 出力する前に「jockey_leading.csv」は削除、ファイル有無は判定しない。無くても続行。
- cvsの書き出しは同期処理にしなければ、並びがバラバラになるので、必ずappendFileSyncで書出します。
- ADODBでDB操作するので、node-adodbをインストールします。
- Microsoft Access のテーブル格納は必ずしもロード順にならないので、インデックスを設定します。
- AccessのMSysObjectsを読取、「jockey」テーブルの存在チェックを行います。
- 実行するまえに、「jra2019.accdb」を作成します。
- Microsoft Accessで「jra2019.accdb」を作成。
- 「jra2019.accdb」を開く。
- Ctl+gで「Microsoft Visual Basic for Application」を開く。
- イミディトウィンドウに以下のコマンド
- currentuserでaccdbのユーザを確認して、MSsysObjectsにアクセス権限を付与します。
- これを行わないと、外部からMSsysObjectsを読み取れません。
- Microsoft Accessが旧mdbの場合 "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=xxxxx.mdb;"
- node-adodbでCSVをテーブルにインサートします。
- loadcsvは同期処理(async~await)にします。これをやらないと、csvがバラバラに格納されます。
- jockey_leading.csvを同期(readFileSync)で読取、'\n'でsplitします。
- splitしたデータをロードします。
xmlhttprequest install
npm install xmlhttprequest -save
実行
node jockey_leading_tocsv.js
結果
PS C:\Users\~> ls jockey_leading.csv
ディレクトリ: C:\Users\~
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2019/08/15 15:23 9331 jockey_leading.csv
jockey_leading_table.js
const check_jockey = "SELECT Name FROM MSysObjects WHERE Name = 'jockey';"
const create_table_jockey = "CREATE TABLE jockey (" +
"順位 INT NOT NULL," +
"騎手名 char(20) NOT NULL," +
"所属 char(26)," +
"生年月日 DATE," +
"1着 INT," +
"2着 INT," +
"3着 INT," +
"着外 INT," +
"重賞出走 INT," +
"重賞勝利 INT," +
"特別出走 INT," +
"特別勝利 INT," +
"平場出走 INT," +
"平場勝利 INT," +
"芝出走 INT," +
"芝勝利 INT," +
"ダート出走 INT," +
"ダート勝利 INT," +
"勝率 DOUBLE," +
"連対率 DOUBLE," +
"複勝率 DOUBLE," +
"収得賞金 DOUBLE," +
"代表馬 char(20)," +
"順位変動 char(4)" +
");"
const create_index_jockey = "CREATE INDEX idxID ON jockey (順位 ASC);"
const ADODB = require('node-adodb');
const connection = ADODB.open('Provider=Microsoft.ACE.OLEDB.12.0;Data Source=jra2019.accdb;');
// Table Exist Check
connection
.query(check_jockey)
.then(data => {
// Create Table
if (data.length == 0) {
connection
.execute(create_table_jockey)
.then(data => {
// Create Index
connection
.execute(create_index_jockey)
.then(data => {
})
.catch(error => {
console.log(error);
});
})
.catch(error => {
console.log(error);
});
}
})
.catch(error => {
console.log(error);
});
説明
MSysObjectsへ読みとり権限を付与する
?currentuser()
Admin
CurrentProject.Connection.Execute "GRANT SELECT ON MSysObjects TO Admin;"
node-adodb install
npm install node-adodb --save
実行
node jockey_leading_table.js
結果
下記の通り、テーブルが作成されればOKです。
jockey_leading_load.js
async function loadcsv(table, data) {
const ADODB = require('node-adodb');
const connection = ADODB.open('Provider=Microsoft.ACE.OLEDB.12.0;Data Source=jra2019.accdb;');
try {
await connection.execute('INSERT INTO ' + table + ' VALUES(' + data + ');');
} catch (error) {
console.error(error);
}
}
function jockeyload() {
const fs = require('fs');
const file = ".\\jockey_leading.csv";
var data = new String();
data = fs.readFileSync(file, 'utf8');
var lines = data.split( '\n' );
for ( var i = 0; i < lines.length - 1; i++ ) {
if (i < 2) {
continue;
}
loadcsv('jockey', lines[i]);
}
}
jockeyload();
説明
実行
node jockey_leading_load.js
結果
下記のエラーコードは無視してください。
process:
{ code: -2147467259,
message: 'マシン \'xxxxxxx\' のユーザー \'Admin\' がデータベースを開けない状態、またはロックできない状態にしています。' },
exitCode: 0 }
jockey
まとめ
スクレイピングをnode.jsとJavaScriptで構築する入門をやってみました。
いわずもがな、初学者向けです。不明点が多々あると思います。
俺だったこうやる的なご意見もありそうです。
とりあえず、目的は達成しています。
AccessをMySQLに置き換えるとか、コードはALLフリーなので色々改造して、さらなるスクレイピングワールドへ。