ここでは、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 table
public 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}