mk-mode BLOG

このブログは自作の自宅サーバに構築した Debian GNU/Linux で運用しています。
PC・サーバ構築等の話題を中心に公開しております。(クローンサイト: GitHub Pages

ブログ開設日2009-01-05
サーバ連続稼働時間
Reading...
Page View 合計
Reading...
今日
Reading...
昨日
Reading...

Java - Tidy で Web スクレイピング!

[ プログラミング ] [ Java ]

【はじめに】
Yahoo!ファイナンス掲載情報の自動取得(スクレイピング)は禁止されています。(参照
以下の記事を参考にすることもお勧めしません。

こんばんは。

当方、最近は Ruby ばかりですが、ものによっては Ruby 以外も使います。

そこで、今回は Ruby + Hpricot で行っていた Web スクレイピングを Java + Tidy(JTidy) でやってみました。

テスト用ソースを公開します。 応用してください。 ※当方は実際には、MySQL を使ったりもっと複雑な処理をしています。

記録

今回テストしたのは、Yahoo!ファイナンスの業種別銘柄一覧から各銘柄の情報を取得する処理です。 全業種から全銘柄の情報を取得します。

0. 前提条件

  • Java のコンパイル・動作する環境が整っている。(当方は JDK 1.7.0 を使用)
  • HTML を解析するので HTML の(ちょっとした)知識が必要。

Java の環境周りについてはここでは述べません。必要な別途お調べください。

1. Tidy 導入

  • こちら から “jtidy-r938.jar”(当記事執筆時点) をダウンロードし、適当な場所に配置する。
  • クラスパスに追加する。

2. ソース作成

今回テストで使用したソースです。 存在する業種分ループ処理を行っています。 スクレイピングの中心部分は、以下のようになっています。

  • URLを取得
  • DOM document
  • XPath用のファクトリ生成
  • XPathオブジェクトの生成
  • データ取得

データ取得用 Expression では、どの HTML タグ、どの class 等を指定します。 また、データ取得用 Expression のそれぞれを2つに分けているのは、TABLE タグ内の何個目の TR タグかを指定する為です。(インデックスの前と後に分けている) さらに、1ページに最大20銘柄が表示されるので、1ページ20件読み込んだら次ページを読むようにし、銘柄コードが存在しなければループを終了するようにしています。

【ファイル名:TestTidy.java】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import java.net.URL;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.tidy.Tidy;
import org.w3c.dom.Document;

public class TestTidy {

    // 各種宣言
    // [ URL ]
    private static final String URL = "http://stocks.finance.yahoo.co.jp/stocks/qi/?ids=";
    // [ Expression ]
    private static final String expStockCode1 = "//table[@class='yjS']/tr[@class='yjM'][";
    private static final String expStockCode2 = "]/td[@class='center yjM']/a";
    private static final String expMarketName1 = "//table[@class='yjS']/tr[@class='yjM'][";
    private static final String expMarketName2 = "]/td[@class='center yjSt']";
    private static final String expStockName1 = "//table[@class='yjS']/tr[@class='yjM'][";
    private static final String expStockName2 = "]/td/strong/a";
    private static final String expCharacteristic1 = "//table[@class='yjS']/tr[@class='yjM'][";
    private static final String expCharacteristic2 = "]/td/span[@class='yjSt profile']";
    // [ ETC ]
    private static String[] aryCategory = null; // 業種コード一覧
    private static int intCountAll = 0; // 総読込数

    public static void main (String[] args) {

        try {

            // 読み込む業種コード一覧
            String[] aryCategory = {
                "0050", "1050", "2050", "3050", "3100",
                "3150", "3200", "3250", "3300", "3350",
                "3400", "3450", "3500", "3550", "3600",
                "3650", "3700", "3750", "3800", "4050",
                "5050", "5100", "5150", "5200", "5250",
                "6050", "6100", "7050", "7100", "7150",
                "7200", "8050", "9050"
            };

            // Webデータ読込
            for (int i = 0; i < aryCategory.length; i++) {
                intCountAll += readHtml(aryCategory[i]);
            }

            // 結果出力
            System.out.println("読込件数 : " + intCountAll);

        }
        catch( Exception e ) {
            e.printStackTrace();
        }
    }

    // HTML読込
    private static int readHtml(String strCatCode) {

        int intCount = 0; // 取得件数

        try {

            // Tidy設定
            Tidy tidy = new Tidy();
            tidy.setShowWarnings(false);
            tidy.setQuiet(true);
            tidy.setInputEncoding("utf-8");
            tidy.setOutputEncoding("utf-8");

            int intPage = 1; // ページ番号
            boolean blnEnd1 = false; // 終了フラグ(カテゴリー)
            while (!blnEnd1) {

                // URL
                URL url = new URL(URL + strCatCode + "&p=" + intPage);

                // DOM document
                Document doc = tidy.parseDOM(url.openStream(), null);

                // XPath用のファクトリ生成
                XPathFactory xpf = XPathFactory.newInstance();

                // XPathオブジェクトの生成
                XPath xpath = xpf.newXPath();

                int intRowCnt = 1; // ページ内読み込み行数
                boolean blnEnd2 = false; // 終了フラグ(ページ)
                while (!blnEnd2) {

                    // 銘柄コード
                    String expStockCode = expStockCode1 + intRowCnt + expStockCode2;
                    String strStockCode = (String) xpath.evaluate(expStockCode, doc, XPathConstants.STRING);

                    // 終了判定
                    if (strStockCode == "") {
                        blnEnd2 = true;
                        blnEnd1 = true;
                        break;
                    } else {
                        intCount++;
                    }

                    // 市場名
                    String expMarketName = expMarketName1 + intRowCnt + expMarketName2;
                    String strMarketName = (String) xpath.evaluate(expMarketName, doc, XPathConstants.STRING);

                    // 銘柄名
                    String expStockName = expStockName1 + intRowCnt + expStockName2;
                    String strStockName = (String) xpath.evaluate(expStockName, doc, XPathConstants.STRING);

                    // 特色
                    String expCharacteristic = expCharacteristic1 + intRowCnt + expCharacteristic2;
                    String strCharacteristic = (String) xpath.evaluate(expCharacteristic, doc, XPathConstants.STRING);

                    // コンソール出力
                    System.out.println(
                        "[" + strCatCode + "] " + strStockCode + "["
                        + strMarketName + "] " + strStockName + " : " + strCharacteristic );

                    // ページ内読込行数インクリメント
                    intRowCnt++;

                    // 終了判定(1ページ20件を超えたらこのページは終了)
                    if (intRowCnt > 20) {
                        blnEnd2 = true;
                        intPage++;
                    }

                }
            }

            return intCount;

        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

3. コンパイル&実行

作成したソールをコンパイルして実行します。 もちろん、コンパイルでエラーが出たなら修正が必要です。

1
2
3
4
5
6
7
8
9
10
# javac TestTidy.java
# java TestTidy
[0050] 1301[東証1部] (株)極洋 : 水産品の貿易、加工、買い付け主力。すしネタに強み。加工食品は業務用が軸。海外加工比率高い
[0050] 1332[東証1部] 日本水産(株) : 水産品に特化した食品会社を志向。家庭用冷食で成功。M&A駆使し世界的SCM構築目指す

====< 途中省略 >====

[9050] 9795[東証2部] (株)ステップ : 神奈川中西部で中学生主体の学習塾「ステップ」運営。難関国公立高に強み。配当性向30%メド
[9050] 9797[東証2部] 大日本コンサルタント(株) : 橋梁・道路に強み、中堅建設コンサル。官公庁取引多く収益計上は期終盤集中。札幌の同業と提携
読込件数 : 3580

参考サイト


Ruby の Hpricot のように、TR タグをまとめて配列にセットしてくれるともっと簡潔な処理になると思うのですが。。。

ちなみに、Ruby + Hpricot で Web スクレイピングしていた処理を、ほとんど同じアルゴリズムで Java + JTidy に移植して試してみましたが、実行速度はほとんど変わりませんでした。

また、今回の応用で株価を取得したり等色々できると思います。

以上。

Comments