ここでは、Yahoo!Financeの株価時系列データをJavaCCで解析します。
JJT ---(JJTree)---> JJ --(JavaCC)--> Java JTB ---(JTB)------> JJ --(JavaCC)--> Java
| 終端記号。実際に文の上に出現する記号です。 | |
| <〜> | 非終端記号。配下に非終端記号と終端記号を持ちます。 |
| ::= | 定義(左辺は、右辺の内容に展開される) |
| + | 1回以上の繰り返し |
| * | 0回以上の繰り返し |
| ? | 0回または1回出現(有っても無くても良い) |
| | | or |
<長歌> ::= <57> <57> <57> <57>* <7>
<短歌> ::= <57> <57> <7>
<俳句> ::= <57> <5>
<57> ::= <5> <7>
<5> ::= <かな> <かな> <かな> <かな> <かな>
<7> ::= <かな> <かな> <かな> <かな> <かな> <かな> <かな>
<かな> ::= い | ろ | は | に | ほ | へ | と | ち | り | ぬ | る | を |
わ | か | よ | た | れ | そ | つ | ね | な | ら | む | う |
ゐ | の | お | く | や | ま | け | ふ | こ | え | て | あ |
さ | き | ゆ | め | み | し | ゑ | ひ | も | せ | す JavaCCプログラムは*.jjファイルに記述します。jjファイルは以下のような構成になっています。
<jjファイル> ::= <OPTION> <PARSER_CLASS> <TOKEN_DEF> <PARSER_METHOD>+ <JAVA_CODE>
<OPTION> ::= コンパイラ・コンパイラとなるクラスの設定をします。
<PARSER_CLASS> ::= コンパイラ・コンパイラとなるクラスを定義します
<TOKEN_DEF> ::= <SKIP>* <TOKEN>* <SPECIAL_TOKEN>*
<SKIP> ::= 無視して読み飛ばされる字句を定義します。
<TOKEN> ::= 構文を構成する字句を定義します。
<SPECIAL_TOKEN> ::= 文法に関係無くどこにでも現れる可能性のある特殊な字句を定義します。
<PARSER_METHOD> ::= 字句によって構成される構文を定義し、
見つかった構文に対する処理を記述します。
<JAVA_CODE> ::= 構文解析とは関係ないJava Codeを書きます
<TABLE><TR><TD>〜</TD></TR></TABLE>を切り出すjjファイルを作ります。
HTML文書のTABLEタグの内容を取得するHTMLTableParser?.jjを作成します。ここではとりあえず<TD>タグの中身をSystem.outに出力します(データの取り込みは後述するJTBを使ってこのプログラムを拡張して行います)。
/*****************************************************************************
* オプション定義 *
*****************************************************************************/
options {
STATIC=false;
JAVA_UNICODE_ESCAPE=false;
UNICODE_INPUT=true;
DEBUG_PARSER=false;
DEBUG_TOKEN_MANAGER=false;
}
| 項目 | 設定内容 | デフォルト値 |
| STATIC | trueにすると生成されるメソッドがstaticになります。当然マルチスレッドで使えないのでfalseにするのが吉 | true |
| JAVA_UNICODE_ESCAPE | trueにすると、ParserはUnicode Escapeされた文字が入力されると見なします。昔はASCII以外の文字をJavaCCで解析するにはこのオプションを使ったが、UNICODE_INPUTが使えるようになったのでfalseにしておく | false |
| UNICODE_INPUT | trueにすると、ParserはUCS2(つまりはJavaのString型)が入力されると見なします。日本語を使うならtrueにします | false |
| DEBUG_TOKEN_MANAGER | JavaCCによって生成されたParserにデバックトレースを入れる | false |
| DEBUG_PARSER | JavaCCのよって生成されたParserにデバックトレースを入れる | false |
/*****************************************************************************
* Javaクラスの生成 *
* このJavaクラスのメソッドとして構文解析プログラムが自動生成されます *
*****************************************************************************/
PARSER_BEGIN(HTMLTableParser)
package com.snail.parser.table;
/**
* HTML TABLE PARSER
* @author Hondoh Atsushi
* @version $Id$
*/
public class HTMLTableParser{
}
PARSER_END(HTMLTableParser)/**************************************************************/
/* SKIP : */
/* 無視して読み飛ばされるもの。空白、タブ、改行文字など。 */
SKIP : {
<SPACE : " " | "\t" | "\n" | "\r" | "\f">
}/**************************************************************/
/* SPECIAL_TOKEN : */
/* 文法に関係無くどこにでも現れる可能性のある特殊なトークン */
SPECIAL_TOKEN : {
<COMMENT : "<!" (~[">"])* ">" > |
<META : "<META" (~[">"])* ">" |
"<meta" (~[">"])* ">" > |
<BR : "<BR" (~[">"])* ">" |
"<br" (~[">"])* ">" > |
<HR : "<HR" (~[">"])* ">" |
"<hr" (~[">"])* ">" > |
<P : "<P>" | "</P>" |
"<p>" | "</p>" > |
<B : "<B>" | "</B>" |
"<b>" | "</b>"> |
<SCRIPT : "<SCRIPT" (~["<"])* "</SCRIPT>" |
"<script" (~["<"])* "</script>" > |
<NOSCRIPT: "<NOSCRIPT>" | "</NOSCRIPT>" |
"<noscript>" | "</noscript>" > |
<CENTER : "<CENTER>" | "</CENTER>" |
"<center>" | "</center>" > |
<FONT : "<FONT" (~[">"])* ">" | "</FONT>" |
"<font" (~[">"])* ">" | "</font>" > |
<SMALL : "<SMALL>" | "</SMALL>" |
"<small>" | "</small>" > |
<IMG : "<IMG" (~[">"])* ">" |
"<img" (~[">"])* ">" > |
<INPUT : "<INPUT" (~[">"])* ">" |
"<input" (~[">"])* ">" > |
<SELECT : "<SELECT" (~[">"])* ">" | "</SELECT>" |
"<select" (~[">"])* ">" | "</select>"> |
<OPTION : "<OPTION" (~[">"])* ">" | "</OPTION>" |
"<option" (~[">"])* ">" | "</option>"> |
<TBODY : "<TBODY>" | "</TBODY>" |
"<tbody>" | "</tbody>"> |
<FORM : "<FORM" (~[">"])* ">" | "</FORM>" |
"<form" (~[">"])* ">" | "</form>"> |
<DIV : "<DIV" (~[">"])* ">" | "</DIV>" |
"<div" (~[">"])* ">" | "</div>">
}/**************************************************************/
/* TOKEN: */
/* 構文を構成する要素 */
TOKEN : {
<STRING_LITERAL : (~["<","\n","\r","\f"] )+>
}
TOKEN : {
<HTML : "<HTML>" |
"<html>" > |
<EHTML : "</HTML>" |
"</html>" > |
<HEAD : "<HEAD>" |
"<head>" > |
<EHEAD : "</HEAD>" |
"</head>" > |
<TITLE : "<TITLE>" |
"<title>" > |
<ETITLE : "</TITLE>" |
"</title>" > |
<BODY : "<BODY>" |
"<body>" > |
<EBODY : "</BODY>" |
"</body>" >
}
TOKEN : {
<TABLE : "<TABLE" (~[">"])* ">" |
"<table" (~[">"])* ">" > |
<ETABLE : "</TABLE>" |
"</table>"> |
<TR : "<TR" (~[">"])* ">" |
"<tr" (~[">"])* ">" > |
<ETR : "</TR>" |
"</tr>" > |
<TD : "<TD" (~[">"])* ">" |
"<td" (~[">"])* ">" |
"<TH" (~[">"])* ">" |
"<th" (~[">"])* ">" > |
<ETD : "</TD>" |
"</td>" |
"</TH>" |
"</th>" > |
<LINK : "<A" (~[">"])* ">" |
"<a" (~[">"])* ">" > |
<ELINK : "</A>" | "</a>" >
}void ${メソッド名} : {
Javaコード
}{
TOKEN {TOKENを処理するJavaコード}
(構文)*
TOKEN
}
というように定義します。TOKENを受け取るにはTokenというオブジェクトを使います。void html() : {
}{
<HTML>
<HEAD>
<TITLE>
<STRING_LITERAL>
<ETITLE>
<EHEAD>
<BODY>
( table() | <STRING_LITERAL> | link() )*
<EBODY>
<EHTML>
<EOF>
}
void table() : {
}{
<TABLE>
(row())*
<ETABLE>
}
void row() : {
}{
<TR>
(col())*
<ETR>
}
void col() : {
Token token;
}{
<TD>
(token=<STRING_LITERAL>{System.out.println( token.image );} |
table() | link())*
<ETD>
}
void link() : {
Token token;
}{
token=<LINK>{System.out.println( token.image );}
(token=<STRING_LITERAL>{System.out.println( token.image );})*
(<ELINK>|<LINK>)
}
※ link ::= <LINK> <STRING_LITERAL> ( <ELINK> | <LINK> )となっているのは、Yahoo!の株価ページで "<a hrer="..."> ... <a>" となっている部分があったから/*****************************************************************************
* 任意のメソッドの定義。 *
* トークンとも構文とも無関係な任意のJavaコードを書き込みます。 *
*****************************************************************************/
JAVACODE
void skip_form(){
Token t;
do {
t = getNextToken();
} while ( t.kind != EOF );
}package com.snail.parser.table;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public final class HTMLTableReader {
public HTMLTableReader() {
super();
}
/**
* @param args
*/
public static void main(String[] args) {
URL url;
try {
String[] range = { "1980", "1", "1", "2020", "12", "31", "8035.t" };
StringBuffer sbuf = new StringBuffer();
sbuf.append("http://table.yahoo.co.jp/t");
sbuf.append("?c=" + range[0]);
sbuf.append("&a=" + range[1]);
sbuf.append("&b=" + range[2]);
sbuf.append("&f=" + range[3]);
sbuf.append("&d=" + range[4]);
sbuf.append("&e=" + range[5]);
sbuf.append("&s=" + range[6]);
sbuf.append("&y=0");
sbuf.append("&z=" + range[6]);
url = new URL(sbuf.toString());
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
HTMLTableParser parser = new HTMLTableParser(new InputStreamReader(
con.getInputStream(), "EUC_JP"));
parser.html();
con.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
}<a href="http://quote.yahoo.co.jp/"> <a href="http://www.yahoo.co.jp/"> Yahoo! JAPAN - <a href="http://my.yahoo.co.jp/"> My Yahoo! - <a href="http://help.yahoo.co.jp/help/jp/fin/"> ヘルプ (中略) 信用残時系列データ 日付 始値 高値 安値 終値 出来高 調整後終値* 2005年11月21日 6,780 6,830 6,700 6,730 1,892,000 6,730 2005年11月18日 6,680 6,820 6,680 6,710 3,025,800 6,710 2005年11月17日 6,500 6,650 6,390 6,600 2,270,800 6,600 2005年11月16日 6,430 (後略)
<JJTreeによる生成> <JavaCCによる生成>
HTMLTableParser.jjt -+--> HTMLTableParser.jj ----> 構文解析
| Javaプログラム
| :
| :(use)
| ↓
+---------------------------> 構文木生成
<JJTreeによる生成> Javaプログラムurl = new URL(sbuf.toString());
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
HTMLTableParser parser = new HTMLTableParser(new InputStreamReader(
con.getInputStream(), "EUC_JP"));
SimpleNode root = parser.parse();
con.disconnect();
root.dump(" "); parse
html
table
row
col
link
col
table
row
col
link
link
link
link
tablepublic class SimpleNode implements Node {
/** テーブル連番 */
protected int tableNo;
public void setTableNo(int tableNo) {
this.tableNo = tableNo;
}
public int getTableNo() {
return tableNo;
}
/** link() の場合には URLを格納する */
protected String url = "";
public void setURL(String pUrl) {
url = url + pUrl;
}
public String getURL() {
int first = url.indexOf("\"") + 1;
int last = url.indexOf("\"", first);
return url.substring(first, last); // <a href="〜"> の 〜のみを返す
}
/** col() / link() の場合には テキスト を格納する */
protected String text = "";
public void setText(String pText) {
text = text + pText;
}
public String getText() {
return text;
}
public String toString() {
String str = HTMLTableParserTreeConstants.jjtNodeName[id];
if( "table".equals(str) ){
str = str + "[" + tableNo + "]";
}
if (!text.equals("")) {
str = str + ":" + getText();
}
if (!url.equals("")) {
str = str + "(" + getURL() + ")";
}
return str;
}
(後略)PARSER_BEGIN(HTMLTableParser)
package com.snail.parser.table;
/**
* HTML TABLE PARSER
* @author Hondoh Atsushi
* @version $Id$
*/
public class HTMLTableParser{
private int tableNo = 0;
}
PARSER_END(HTMLTableParser)
(中略)
SimpleNode parse() : {
}{
html() <EOF>
{
return jjtThis;
}
}
void html() : {
}{
<HTML>
<HEAD>
<TITLE>
<STRING_LITERAL>
<ETITLE>
<EHEAD>
<BODY>
( table() | <STRING_LITERAL> | link() )*
<EBODY>
<EHTML>
<EOF>
}
void table() : {
jjtThis.setTableNo( ++tableNo );
}{
<TABLE>
(row())*
<ETABLE>
}
void row() : {
}{
<TR>
(col())*
<ETR>
}
void col() : {
Token token;
}{
<TD>
(token=<STRING_LITERAL>{jjtThis.setText( token.image );} |
table() | link())*
<ETD>
}
void link() : {
Token token;
}{
token=<LINK>{jjtThis.setURL( token.image );}
(token=<STRING_LITERAL>{jjtThis.setText( token.image );})*
(<ELINK>|<LINK>)
} parse
html
table[1]
row
col
link(http://quote.yahoo.co.jp/)
col
table[2]
row
(中略)
table[10]
row
col:東京エレクトロン(株)(東証1部: 8035.T)
(中略)
table[16]
row
col
link:信用残時系列データ(/bt?s=8035.t&a=1&b=1&c=1980&d=12&e=31&f=2020&g=d&k=20051122)
col
table[17]
row
col
table[18]
row
col
table[19]
row
col:日付
col:始値
col:高値
col:安値
col:終値
col:出来高
col:調整後終値*
row
col:2005年11月22日
col:6,820
col:7,000
col:6,810
col:6,890
col:4,322,300
col:6,890
row
col:2005年11月21日
col:6,780
col:6,830
col:6,700
col:6,730
col:1,892,000
col:6,730
row
(中略)
col:1,024,900
col:6,220
table[20]
row
col:* 分割による調整については、を参照してください。
link:こちら(http://help.yahoo.co.jp/help/jp/fin/quote/historical/his_07.html)
col:[]
link:次の50件(t?s=8035.t&a=1&b=1&c=1980&d=12&e=31&f=2020&g=d&q=t&y=50&z=8035.t&x=.csv)
table[21]
(後略)| テーブル番号 | 情報 |
| 10 | 銘柄名 |
| 16 | 信用取引時系列データへのリンク |
| 19 | 株価時系列データ |
| 20 | 株価時系列データ(次の50件)へのリンク |
package com.snail.stock;
import com.snail.parser.table.HTMLTableReader;
import com.snail.parser.table.Node;
import com.snail.parser.table.ParseException;
import com.snail.parser.table.SimpleNode;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
public final class YahooFinanceReader {
public YahooFinanceReader() {
super();
}
/**
* @param args
*/
public synchronized static void main(String[] args) {
String[] range = { "1980", "1", "1", "2020", "12", "31", "8035.t" };
Collection<Map> stockArray = new LinkedList<Map>();
for (int y = 0; y < 1000; y += 50) {
StringBuffer sbuf = new StringBuffer();
sbuf.append("http://table.yahoo.co.jp/t");
sbuf.append("?c=" + range[0]);
sbuf.append("&a=" + range[1]);
sbuf.append("&b=" + range[2]);
sbuf.append("&f=" + range[3]);
sbuf.append("&d=" + range[4]);
sbuf.append("&e=" + range[5]);
sbuf.append("&s=" + range[6]);
sbuf.append("&y=");
sbuf.append(y);
sbuf.append("&z=" + range[6]);
System.out.print("Reading " + sbuf + "...");
stockArray.addAll(readPage(sbuf.toString()));
System.out.println("done");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e=null;
}
}
for (Iterator it=stockArray.iterator();it.hasNext();){
System.out.println(it.next());
}
}
private static Collection<Map> readPage(String url) {
try {
HTMLTableReader reader = new HTMLTableReader(url, "EUC_JP");
// 銘柄名は10番目のテーブルに格納されている
// String name = getStockName(reader.getBranch(10));
// System.out.println(name);
// 信用残時系列情報へのリンクは16番目のテーブルに格納されている
// String balance = getBalanceLink(reader.getBranch(16));
// System.out.println(balance);
// 次の50件へのリンクは20番目のテーブルに格納されている
// nextLink = getNextLink(reader.getBranch(20));
// System.out.println(nextLink);
// 株価時系列情報は19番目のテーブルに格納されている
return getStockValues(reader.getBranch(19));
} catch (IOException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
return new LinkedList<Map>();
}
private static String getStockName(SimpleNode subtree) {
// subtree.dump(" ");
Node row = subtree.jjtGetChild(0);
Node col = row.jjtGetChild(0);
return ((SimpleNode) col).getText();
}
private static String getBalanceLink(SimpleNode subtree) {
// subtree.dump(" ");
Node row = subtree.jjtGetChild(0);
Node col = row.jjtGetChild(0);
Node link = col.jjtGetChild(0);
return ((SimpleNode) link).getURL();
}
private static String getNextLink(SimpleNode subtree) {
// subtree.dump(" ");
Node row = subtree.jjtGetChild(0);
Node col = row.jjtGetChild(1);
Node link = col.jjtGetChild(0);
return ((SimpleNode) link).getURL();
}
private static Collection<Map> getStockValues(SimpleNode subtree) {
// subtree.dump(" ");
Collection<Map> col = new LinkedList<Map>();
int rows = subtree.jjtGetNumChildren();
for (int cnt = 0; cnt < rows; cnt++) {
Node row = subtree.jjtGetChild(cnt);
Map<String,String> map = new LinkedHashMap<String,String>();
map.put("day", ((SimpleNode) row.jjtGetChild(0)).getText());
map.put("start", ((SimpleNode) row.jjtGetChild(1)).getText());
map.put("high", ((SimpleNode) row.jjtGetChild(2)).getText());
map.put("low", ((SimpleNode) row.jjtGetChild(3)).getText());
map.put("end", ((SimpleNode) row.jjtGetChild(4)).getText());
map.put("amount", ((SimpleNode) row.jjtGetChild(5)).getText());
map.put("adjEnd", ((SimpleNode) row.jjtGetChild(6)).getText());
col.add(map);
}
return col;
}
}Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=0&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=50&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=100&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=150&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=200&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=250&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=300&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=350&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=400&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=450&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=500&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=550&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=600&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=650&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=700&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=750&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=800&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=850&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=900&z=8035.t...done
Reading http://table.yahoo.co.jp/t?c=1980&a=1&b=1&f=2020&d=12&e=31&s=8035.t&y=950&z=8035.t...done
{day=日付, start=始値, high=高値, low=安値, end=終値, amount=出来高, adjEnd=調整後終値*}
{day=2005年11月30日, start=7,400, high=7,400, low=7,270, end=7,270, amount=1,432,200, adjEnd=7,270}
{day=2005年11月29日, start=7,350, high=7,460, low=7,300, end=7,360, amount=2,502,600, adjEnd=7,360}
{day=2005年11月28日, start=7,370, high=7,640, low=7,360, end=7,480, amount=4,686,200, adjEnd=7,480}
{day=2005年11月25日, start=7,110, high=7,260, low=7,060, end=7,240, amount=2,786,200, adjEnd=7,240}
{day=2005年11月24日, start=7,200, high=7,500, low=7,200, end=7,310, amount=14,894,600, adjEnd=7,310}
{day=2005年11月22日, start=6,820, high=7,000, low=6,810, end=6,890, amount=4,322,300, adjEnd=6,890}
(中略)
{day=2001年11月8日, start=5,510, high=5,620, low=5,330, end=5,590, amount=530,900, adjEnd=5,590}
{day=2001年11月7日, start=5,740, high=5,750, low=5,460, end=5,500, amount=880,900, adjEnd=5,500}
{day=2001年11月6日, start=5,670, high=5,860, low=5,600, end=5,840, amount=1,432,200, adjEnd=5,840}
{day=2001年11月5日, start=5,520, high=5,610, low=5,460, end=5,570, amount=910,200, adjEnd=5,570}
{day=2001年11月2日, start=5,430, high=5,450, low=5,310, end=5,420, amount=1,134,200, adjEnd=5,420}