jsoup使い方メモ

jsoup は Java で HTML の解析・編集を行うためのライブラリ。

URL を指定すれば実際の Web ページを解析のインプットに指定でき、タグの検索には CSS セレクタが使えるので、 Web スクレイピングをしたい時にとても便利。

# 導入
Maven のセントラルリポジトリに jar がある。

```groovy:build.gradle
dependencies {
    compile 'org.jsoup:jsoup:1.7.3'
}
```

# 解析する HTML の指定
## URL を指定して実際の Web ページをインプットにする
```java:
package sample.jsoup;

import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class Main {
    
    public static void main(String[] args) throws IOException {
        Document document = Jsoup.connect("http://www.google.co.jp").get();
        System.out.println(document.html());
    }
}
```

```text:実行結果
<!DOCTYPE html>
<html itemscope="" itemtype="http://schema.org/WebPage">
 <head>
  <meta content="世界中のあらゆる情報を検索するためのツールを提供しています。さまざまな検索機能を活用して、お探しの情報を見つけてください。" name="description" />
  <meta content="noodp" name="robots" />
  <meta itemprop="image" content="/images/google_favicon_128.png" />
  <title>Google</title>
（以下略）
```

`Jsoup.connect("<URL>").get()` で、指定した URL に GET メソッドでアクセスし、結果をパースした `Document` オブジェクトを取得できる。

あとは、この `Document` オブジェクトから必要なタグを検索する。

※HTTP のメソッドは GET と POST しか無いようなので、 RESTful API のクライアントとしては利用できないっぽい。（[Connection.Method (jsoup 1.7.4-SNAPSHOT API)](http://jsoup.org/apidocs/org/jsoup/Connection.Method.html)）

### リクエストパラメータを設定する
```java:
package sample.jsoup;

import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class Main {

    public static void main(String[] args) throws IOException {
        Document document = Jsoup.connect("http://qiita.com/search")
                                 .data("q", "java")
                                 .get();
        
        Elements elements = document.select(".brand, .page-title, .stats");
        
        for (Element element : elements) {
            System.out.println(element.text());
        }
    }
}
```

```text:実行結果
Qiita - プログラマの技術情報共有サービス
javaの検索結果
856投稿 • 7163フォロワー
```

`data(String, String)` メソッドでリクエストパラメータを設定できる。
URL エンコードは内部でしてくれるので、全角文字もそのまま指定できる。

## HTML 形式の文字列をインプットにする
```java:
package sample.jsoup;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class Main {
    
    public static void main(String[] args) {
        String html = "<div><span>hoge</span><p>fuga</p></div>";
        
        Document document = Jsoup.parse(html);
        System.out.println(document.html());
    }
}
```

```text:実行結果
<html>
 <head></head>
 <body>
  <div>
   <span>hoge</span>
   <p>fuga</p>
  </div>
 </body>
</html>
```

## ローカルのファイルをインプットにする
```html:input.html
<html>
  <head>
    <title>test page</title>
  </head>
  <body>
    <h1>テストページです。</h1>
  </body>
</html>
```

```java:
package sample.jsoup;

import java.io.File;
import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class Main {
    
    public static void main(String[] args) throws IOException {
        Document document = Jsoup.parse(new File("input.html"), "UTF-8");

        System.out.println(document.html());
    }
}
```

```text:実行結果
<html>
 <head> 
  <title>test page</title> 
 </head> 
 <body> 
  <h1>テストページです。</h1>  
 </body>
</html>
```

# テキスト・ HTML を取得する
```html:input.html
<div id="hoge">
  <h1>Hoge</h1>
  <ul id="fuga">
    <li>Fuga</li>
    <li id="piyo">Piyo</li>
  </ul>
</div>
```

```java:
package sample.jsoup;

import java.io.File;
import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class Main {
    
    public static void main(String[] args) throws IOException {
        Document document = Jsoup.parse(new File("input.html"), "UTF-8");
        
        Element hoge = document.getElementById("hoge");
        System.out.println("[hoge.html()]\r\n" + hoge.html() + "\r\n");
        
        Element fuga = document.getElementById("fuga");
        System.out.println("[fuga.text()]\r\n" + fuga.text() + "\r\n");
        
        Element piyo = document.getElementById("piyo");
        System.out.println("[piyo.outerHtml()]\r\n" + piyo.outerHtml() + "\r\n");
    }
}
```

```text:実行結果
[hoge.html()]
<h1>Hoge</h1> 
<ul id="fuga"> 
 <li>Fuga</li> 
 <li id="piyo">Piyo</li> 
</ul>

[fuga.text()]
Fuga Piyo

[piyo.outerHtml()]
<li id="piyo">Piyo</li>
```

- `html()` メソッドで、そのタグの中身を HTML 形式の文字列で取得できる。
- `text()` メソッドで、そのタグの中身のうち、テキスト部分だけを抽出した文字列を取得できる。
- `outerHtml()` メソッドで、そのタグ自身を HTML 形式の文字列で取得できる。


# タグの検索
## ID 指定で検索する
```html:input.html
<h1 id="hoge">Hoge</h1>
<h1 id="fuga">Fuga</h1>
<h1 id="piyo">Piyo</h1>
```

```java:
package sample.jsoup;

import java.io.File;
import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class Main {
    
    public static void main(String[] args) throws IOException {
        Document document = Jsoup.parse(new File("input.html"), "UTF-8");

        Element element = document.getElementById("hoge");
        System.out.println(element.outerHtml());
    }
}
```

```text:実行結果
<h1 id="hoge">Hoge</h1>
```

### その他検索用メソッド
ID 以外にも、 class や属性指定、兄弟タグ、親タグ、タグ内のテキスト、など様々な検索用メソッドが用意されている。

とりあえず、 Element クラスの API はざっと目を通しておいた方がよさげ。

[Element (jsoup 1.7.4-SNAPSHOT API)](http://jsoup.org/apidocs/org/jsoup/nodes/Element.html)


## CSS セレクタを使って検索する
```html:input.html
<div id="hoge">
  <ul>
    <li class="error">hoge</li>
    <li class="success">fuga</li>
    <li class="error">piyo</li>
  </ul>
  
  <span class="error">ERROR</span>
</div>

<span id="fuga">Fuga</span>
```

```java:
package sample.jsoup;

import java.io.File;
import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class Main {
    
    public static void main(String[] args) throws IOException {
        Document document = Jsoup.parse(new File("input.html"), "UTF-8");

        Elements elements = document.select("#hoge ul .error");
        for (Element element : elements) {
            System.out.println(element.outerHtml());
        }
    }
}
```

```text:実行結果
<li class="error">hoge</li>
<li class="error">piyo</li>
```

`select(String cssQuery)` メソッドで、 CSS セレクタを使用した検索が可能。

使用できる CSS セレクタについては、以下の Selector クラスの API ドキュメントで説明されている。

[Selector (jsoup 1.7.4-SNAPSHOT API)](http://jsoup.org/apidocs/org/jsoup/select/Selector.html)

ほとんどの場合普通の CSS セレクタと同じように使えるが、 `E[foo="bar"]` タイプのセレクタを使用するときだけ、以下のような違いがある。

```html:input.html
<input id="hoge" name="HOGE" />
<input id="fuga" name="FUGA" />
```

```java:
package sample.jsoup;

import java.io.File;
import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

public class Main {
    
    public static void main(String[] args) throws IOException {
        Document document = Jsoup.parse(new File("input.html"), "UTF-8");

        Elements elements = document.select("[name=HOGE]");
        System.out.println(elements.outerHtml());
    }
}
```

```text:実行結果
<input id="hoge" name="HOGE" />
```

属性の値は __ダブルクォーテーション `"` で括ってはいけない__ 。
（ `"[name=\\"HOGE\\"]"` ではなく、 `"[name=HOGE]"` が正）

おそらく、 Java のコード上ではダブルクォーテーションにエスケープが必要になるので、より簡潔に書けるようにこのような違いがあると思われる。

# プロキシ環境下で使う
## 認証が不要な場合
```java:
package jsoup;

import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class Main {
    
    public static void main(String[] args) throws IOException {
        System.setProperty("http.proxyHost", "プロキシホスト名");
        System.setProperty("http.proxyPort", "プロキシポート");
        
        Document doc = Jsoup.connect("http://www.google.co.jp").get();
    }
}
```

認証が不要な場合は簡単で、システムプロパティにプロキシのホストとポートを設定してあげればいい。

## 認証が必要な場合

```java:
package jsoup;

import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.net.Authenticator;
import java.net.PasswordAuthentication;

public class Main {
    
    public static void main(String[] args) throws IOException {
        // プロキシの設定（ホストとポート）
        System.setProperty("http.proxyHost", "プロキシホスト名");
        System.setProperty("http.proxyPort", "プロキシポート");

        // 認証情報の設定
        Authenticator.setDefault(new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("ユーザー名", "パスワード".toCharArray());
            }
        });
        
        // Jsoup 取得し
        Document doc = Jsoup.connect("http://www.google.co.jp").get();
    }
}
```


# 参考
- [jsoup Java HTML Parser, with best of DOM, CSS, and jquery](http://jsoup.org/)
- [Java内でjQueryを使う！jsoup ::: Serendipity 2 future lies'n sundome. (´・ω・)](http://www.h3.dion.ne.jp/~alpha-pz/misc2763.html)
- [jsoupでHTMLをパースする - するめとめがね](http://tm8r.hateblo.jp/entry/2013/11/26/125937)
- [ウェブスクレイピング - Wikipedia](http://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A7%E3%83%96%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0)
- [Selectors Level 3 | W3C](http://www.w3.org/TR/css3-selectors/#selectors)
- [java - How can I configure HTTPClient to authenticate against a SOCKS proxy? - Stack Overflow](http://stackoverflow.com/questions/1388822/how-can-i-configure-httpclient-to-authenticate-against-a-socks-proxy)
- [Jakarta Commons によるHTTP処理（HttpClient） - Java入門](http://www.syboos.jp/java/doc/jakarta-commons-httpclient.html)
- [java - How to add proxy support to Jsoup (HTML parser)? - Stack Overflow](http://stackoverflow.com/questions/7482748/how-to-add-proxy-support-to-jsoup-html-parser)
- [Java Proxy Authentication - Stack Overflow](http://stackoverflow.com/questions/8669726/java-proxy-authentication)