Javaでスクレイピングを行う方法（jsoupライブラリを使用する）

# 初めに
本記事は、jsoupと呼ばれるライブラリを使用してJavaでスクレイピングを行う方法について記載しています。
また、公式サイトやネットで調べたことを投稿者なりの理解をまとめた記事となっているのでご注意ください。
以下は、jsoupの公式サイトです。正確な情報はこちらを確認してください。

https://jsoup.org/

# 前提事項
本記事は、Javaを使用するのでJavaの開発環境が必要です。Javaの開発環境が整っていない方は事前に準備をしてください。既に環境が整っている場合は問題ありません。もし、開発環境が整っていない方は、以下に以前投稿者が作成した「Docker、Java、Gradle、VSCode」を使用した開発環境の構築方法について記事がございますので参考にしてみてください。
もちろんEclipse等を使用する方法でも問題ないと思います。また、ビルド自動化システムはGradleを使用していますがMavenに慣れている場合はそちらでも問題ないと思います。(ご自身の慣れている方法で・・・)

https://qiita.com/T-H9703EnAc/items/002538a2b040fc188705

# 
| 項番 | ページ内リンク |
|:----:|:-------------|
| 1 | [1-jsoupライブラリを使用できるようにする](#1-jsoupライブラリを使用できるようにする)|
| 2 | [2-動作確認](#2-動作確認)|
| 3 | [3-htmlの復習](#3-htmlの復習)|
| 4 | [4-使い方](#4-使い方)|
| 5 | [5-さいごに](#5-さいごに) |

# 1-jsoupライブラリを使用できるようにする 
以下の公式サイトからjsoupをダウンロードしてください。

https://jsoup.org/download

投稿者の場合は、[前提事項](#前提事項)の開発環境構築手順を使用しているので、以下のようにしています。
```:build.gradle.kts
/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java application project to get you started.
 * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
 * User Manual available at https://docs.gradle.org/7.4.2/userguide/building_java_projects.html
 */

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    application
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit test framework.
    testImplementation("junit:junit:4.13.2")

    // This dependency is used by the application.
    implementation("com.google.guava:guava:30.1.1-jre")
    // 追加
    implementation ("org.jsoup:jsoup:1.15.1")
}

application {
    // Define the main class for the application.
    mainClass.set("app.App")
}
```
上記を追加しただけでは、読み込みがうまくいかないこともあると思うのでGradleを使用している場合は以下を実施。
```:# 実行コマンド
gradle clean
gradle build
```

# 2-動作確認
以下のようなメインクラスを作成してください。
```:App.java (Mainクラス)
package app;

import java.io.IOException;

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

public class App {
    public static void main(String[] args) throws IOException {
        String html = "<p>Hello, World</p>";
        Document doc = Jsoup.parse(html);
        System.out.println(doc.html());
    }
}
```
以下のように表示されれば成功です。
```:実行結果
<html>
 <head></head>
 <body>
  <p>Hello, World</p>
 </body>
</html>
```
# 3-htmlの復習
よくわからない用語がある場合、以下などを参考にしてください。

https://qiita.com/T-H9703EnAc/items/95981c2482d0e3042230

# 4-使い方
簡単な機能の使い方をいくつか紹介します。
## ①解析したいWebページ(HTML)を取得
まずは、そもそもの解析したいページ(HTML)を取得する方法についてです。
基本的には、文字列やファイル、Webページから取得し、Documentとして保持します。

いくつか方法を紹介します。
### (1) 文字列から取得
下記のコードは文字列のhtmlの文書を用意し、それをDocumentに変換して、再度htmlで出力しているプログラムです。
```:App.java
package app;

import java.io.IOException;

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

public class App {
    public static void main(String[] args) throws IOException {
        String html = "<p>Hello, World</p>";
        Document doc = Jsoup.parse(html);
        System.out.println(doc.html());
    }
}
```
上記の実行結果です。
```:実行結果
<html>
 <head></head>
 <body>
  <p>Hello, World</p>
 </body>
</html>
```
ここで、用意した文字列は「pタグ」だけなのに実行結果は「htmlタグ」や「bodyタグ」があることに気づくと思います。__Jsoup.parse__ を使用すると暗黙的なhtmlの整形を行なってくれます。
例えば、以下のようにあえて、「pタグ」の閉じタグを不正なものにするとします。
```:App.java
package app;

import java.io.IOException;

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

public class App {
    public static void main(String[] args) throws IOException {
        String html = "<p>Hello, World<p>";
        Document doc = Jsoup.parse(html);
        System.out.println(doc.html());
    }
}
```
上記の実行結果です。
```:実行結果
<html>
 <head></head>
 <body>
  <p>Hello, World</p>
  <p></p>
 </body>
</html>
```
上記のように閉じタグを自動で付与してくれていることがわかります。

### (2)ファイルから取得
以下のようなHTMLファイルを読み込むことを想定します。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p>Hello Hello</p>
</body>
</html>
```
以下は、上記のindex.htmlを読み込んでその内容をDocumentに変換して、再度htmlで出力しているプログラムです。
```:App.java
package app;

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

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

public class App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        System.out.println(doc.html());
    }
}
```
上記の実行結果です。
```:実行結果
<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
 </head>
 <body>
  <p>Hello Hello</p>
 </body>
</html>
```
当然といえば当然ですが、ほとんど違いはありませんね。一応、最初の「\<!DOCTYPE html>」が「\<!doctype html>」に変わっています。

### (3)指定したURLのWebページを取得
以下の「jsoupのWikipedia記事」の情報を取得することを想定します。

https://en.wikipedia.org/wiki/Jsoup

以下は、上記の「jsoupのWikipedia記事」を取得し、そのタイトルを表示するプログラムです。
```:App.java
package app;

import java.io.IOException;

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

public class App {
    public static void main(String[] args) throws IOException {
        Document doc = Jsoup.connect("https://en.wikipedia.org/wiki/Jsoup").get();
        String title = doc.title();
        System.out.println(title);
    }
}
```
上記の実行結果です。
```:# 実行結果
jsoup - Wikipedia
```
以下のようなタイトル部分が取得されていると思います。
![スクリーンショット 2022-05-27 3.41.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2663952/822d31b6-3842-0612-8e9a-b5dc0c19dacd.png)

## ②取得したドキュメントの一部を知りたい
今度は取得したドキュメントの一部を見れるようにしたいですね。例えば、①の(3)のようなタイトルやリンク先のURLなどを個別に調べる方法について紹介します。

### (1) タイトルを取得する
以下のようなhtmlから「」titileタグ」のタイトル、今回の場合は「Document」を取得する方法です。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
</body>
</html>
```
以下は、上記のindex.htmlを読み込んで「titleタグ」を表示するプログラムです。
```:
package app;

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

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

public class App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        String title = doc.title();
        System.out.println(title);
    }
}
```
上記の実行結果です。
```:# 実行結果
Document
```

### (2) タグ(Tag)名から取得する
#### 1.bodyタグの内容を取得
以下のようなhtmlから「bodyタグ」の内容を取得する方法についてです。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>あいさつ</h1>
    <p>おはよう</p>
    <p>こんにちは</p>    
    <p>さようなら</p>
</body>
</html>
```
以下は、上記のindex.htmlを読み込んで「bodyタグ」を取得し、「bodyタグ」の __html__ を表示するプログラムです。
```:App.java
package app;

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

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

public class App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Element body = doc.body();
        System.out.println(body.html());
    }
}
```
上記の実行結果です.
```:# 実行結果
<h1>あいさつ</h1>
<p>おはよう</p>
<p>こんにちは</p> 
<p>さようなら</p>
```
以下は、index.htmlを読み込んで「bodyタグ」を取得し、 「bodyタグ」の__Text__ を表示するプログラムです。
```:App.java
package app;

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

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

public class App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Element body = doc.body();
        System.out.println(body.text());
    }
}
```
上記の実行結果です.
```:# 実行結果
あいさつ おはよう こんにちは さようなら
```
#### 2.任意のタグの内容を取得
以下のようなhtmlから任意のタグの内容を取得する方法についてです。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>あいさつ</h1>
    <p>おはよう</p>
    <p>こんにちは</p>    
    <p>さようなら</p>
</body>
</html>
```
以下は、上記のindex.htmlを読み込み「bodyタグ」に存在する「h1タグ」を取得して、Textの内容を表示するプログラムです。
```:App.java
package app;

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 App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Element body = doc.body();
        Elements elements = body.getElementsByTag("h1");
        for(Element element : elements){
            // text()でもhtml()のどちらでもTextの内容を取得します。
            System.out.println(element.text());
            System.out.println(element.html());
        }
    }
}
```
上記の実行結果です。
```:# 実行結果
あいさつ
あいさつ
```
以下は、取得するタグを「h1タグ」から「pタグ」に変更したプログラムです。
※element.html()は削除しています。
```:
package app;

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 App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Element body = doc.body();
        Elements elements = body.getElementsByTag("p");
        for(Element element : elements){
            System.out.println(element.text());
        }
    }
}
```
上記の実行結果です。
```:# 実行結果
おはよう
こんにちは
さようなら
```


### (3)属性(Attribute)から取得する
以下のようなhtmlからaタグのリンク先(href)を取得する方法についてです。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <a href="/hello">helloへ</a>
</body>
</html>
```
以下は、上記のindex.htmlを読み込み「bodyタグ」に存在する「aタグ」を取得して、Textの内容とリンク属性(href)の値を表示するプログラムです。
```:App.java
package app;

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 App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Element body = doc.body();
        Elements elements = body.getElementsByTag("a");
        for(Element element : elements){
            System.out.println(element.text());
            System.out.println(element.attr("href"));
        }
    }
}
```
上記の実行結果です。
```:# 実行結果
helloへ
/hello
```
### (4)idの範囲を指定して取得する
以下のようなhtmlから特定のidを指定して、その範囲を取得する方法についてです。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="morning">
        <h1>朝のあいさつ</h1>
        <p>おはよう</p>
        <p>Good Morning</p>
    </div>
    <div id="noon">
        <h1>昼のあいさつ</h1>
        <p>こんにちは</p>
        <p>Good Afternoon</p>
    </div>
    <div id="evening">
        <h1>夜のあいさつ</h1>
        <p>こんばんは</p>
        <p>Good Evening</p>
    </div>
</body>
</html>
```
以下は、上記のindex.htmlを読み込み、idが「morning」のdivタグの範囲を取得し、htmlの表示する機能とpタグのTextを表示するプログラムです。
```:App.java
package app;

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 App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Element morning = doc.getElementById("morning");
        System.out.println(morning.html());
        Elements elements = morning.getElementsByTag("p");
        for(Element element : elements){
            System.out.println(element.text());
        }
    }
}
```
上記の実行結果です。
```:# 実行結果
<h1>朝のあいさつ</h1>
<p>おはよう</p>
<p>Good Morning</p>
おはよう
Good Morning
```
### (5)classの範囲を指定して取得する
以下のようなhtmlから特定のclassを指定して、その範囲を取得する方法についてです。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="a_class">
        <div id="boy">
            <p>10人</p>
        </div>
        <div id="girl">
            <p>15人</p>
        </div>
    </div>
    <div class="b_class">
        <div id="boy">
            <p>15人</p>
        </div>
        <div id="girl">
            <p>10人</p>
        </div>
    </div> 
</body>
</html>
```
以下は、上記のindex.htmlを読み込み、idが「a_class」のdivタグの範囲を取得し、htmlの表示する機能と性別が「man」の場合はその範囲内のhtmlを、性別が「woman」の場合はその範囲内のTextを表示するプログラムです。
```:App.java
package app;

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 App {
    public static void main(String[] args) throws IOException {
        File file = new File("/Volumes/hdd/pg/Java/Java_SCP/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Elements a_class = doc.getElementsByClass("a_class");
        System.out.println(a_class.html());
        System.out.println(); // 上記で表示するhtmlと下のhtmlを区切るための改行なので気にしないでください
        for(Element element : a_class){
            System.out.println(element.getElementById("man"));
            System.out.println(element.getElementById("woman").text());
        }
    }
}
```
上記の実行結果です。
```:# 実行結果
<div id="man">
 <p>10人</p>
</div>
<div id="woman">
 <p>15人</p>
</div>

<div id="man">
 <p>10人</p>
</div>
15人
```
### (6)セレクタを指定して取得する
以下のようなhtmlから特定のセレクタを指定して、その範囲を取得する方法についてです。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <a href="/hello"></a>
    <div class="a_class">
        <div id="man">
            <p>10人</p>
        </div>
        <div id="woman">
            <p>15人</p>
        </div>
    </div>
    <div class="b_class">
        <div id="man">
            <p>15人</p>
        </div>
        <div id="woman">
            <p>10人</p>
        </div>
    </div> 
</body>
</html>
```
以下は、セレクタを使用してクラスやリンクのhtmlを取得してその内容を表示するプログラムです。
```:App.java
package app;

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 App {
    public static void main(String[] args) throws IOException {
        File file = new File("???/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Elements a_class = doc.select("div.a_class");
        System.out.println(a_class.html());       
        Elements links = doc.select("a[href]");
        for(Element element : links){
            System.out.println(element.attr("href"));
        }
    }
}
```
上記の実行結果です。
```:# 実行結果
<div id="man">
 <p>10人</p>
</div>
<div id="woman">
 <p>15人</p>
</div>
/hello
```

### (7) Elementの指定
毎回for文で中身を確認するのは面倒臭いですよね。次は、以下のようなhtmlから特定のタグ(今回はpタグ)の集合体Elementsを取得し、その集合体の任意の単体の要素指定する(最初とか、次のとか最後などを指定する)方法についてです。
```:index.html (読み込むファイル)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>あいさつ</h1>
    <p>おはよう</p>
    <p>こんにちは</p>    
    <p>さようなら</p>
</body>
</html>
```
以下は、pタグの集合体(Elements)を任意の単体要素(Element)として指定し、そのhtmlを表示するプログラムです。
```
package app;

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

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

public class App {
    public static void main(String[] args) throws IOException {
        File file = new File("/Volumes/hdd/pg/Java/Java_SCP/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Elements tags = doc.getElementsByTag("p");
        
        System.out.println(tags.first()); // 最初を指定
        System.out.println(tags.next().first()); // 次を指定
        System.out.println(tags.last()); // 最後を指定
    }
}
```
上記の実行結果です。
```:# 実行結果
<p>おはよう</p>
<p>こんにちは</p>
<p>さようなら</p>
```
ここで、「次」の指定の仕方に疑問を持つ方もいるかもしれません。以下のように「__.next()__」だけで指定してみます。
```:
package app;

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

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

public class App {
    public static void main(String[] args) throws IOException {
        File file = new File("/Volumes/hdd/pg/Java/Java_SCP/index.html"); // ファイルの取得場所は任意に変更してください
        Document doc = Jsoup.parse(file);
        Elements tags = doc.getElementsByTag("p");
        System.out.println(tags.next()); // 次を指定
    }
}
```
上記の実行結果です。
```:# 実行結果
<p>こんにちは</p>
<p>さようなら</p>
```
上記のように、「__.next()__」だけで指定すると次以降の要素の集合体を指定することになるので注意してください。

# 最後に
ここまでお読みいただきありがとうございます。他にもいろいろなことができると思いますので公式サイトを見て色々試してみてください。

https://jsoup.org/

また、Webサイトにアクセスを行う際は色々注意しないといけないことがあるので気をつけてください。

https://qiita.com/nezuq/items/c5e827e1827e7cb29011

# ※追記
　jsoupは静的解析しか行えません。(レンダリングした後のhtmlの解析などは行えいない)
（例）　Google翻訳で、翻訳したい言語と翻訳後の言語取得するスクレイピングを実施
この時、翻訳したい言語の取得はできますが、翻訳後の言語はJavaScriptのレンダリング後の結果を表示しているためjsoupでは取得できません。

# 参考記事

https://qiita.com/opengl-8080/items/d4864bbc335d1e99a2d7

https://java-skill.com/scraping/

https://yamadatips.wordpress.com/2019/10/06/jsup%E3%81%A7%E3%83%AA%E3%83%B3%E3%82%AF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B/