##はじめに##
JSoupをYahoo NewsのHTMLから内容を取得する方法を説明します。
ちなみにKotlinのようなスッキリ感はないがJAVAでも同様です。
先ずはMavenやGradleでJsoupを導入
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
必要ではないが、僕は”これがあれば便利”の拡張機能とNULL POINTER防止の為を以下を作成した。
JSoupのAPIはElements.first(), Elements.last(), Element.selectFirst()などはNULL POINTER発生します。
そのまま使うと細かくチェックしないとNPE発生するので厄介です。
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.*
import java.util.function.Consumer
import kotlin.streams.toList
class JSoupDoc(private val document: Optional<Document>) {
companion object {
const val CONNECTION_TIMEOUT: Int = 150000
var errorHandler: Consumer<Throwable> = Consumer { }
@JvmStatic
fun fromUrl(url: String): JSoupDoc {
val htmlDoc = kotlin.runCatching {
Jsoup.connect(url).ignoreHttpErrors(true).timeout(CONNECTION_TIMEOUT).get()
}.fold(
onSuccess = { Optional.ofNullable(it) },
onFailure = {
errorHandler.accept(it)
Optional.empty()
}
)
return JSoupDoc(htmlDoc)
}
}
val isEmpty = document.isEmpty
val isPresent = document.isPresent
val body: Optional<Element> = document.map { it.body() }
fun listElement(query: String): List<Element> {
return document.map { it.select(query).stream().toList() }.orElse(listOf())
}
}
fun Element.selectFirstOpt(query: String): Optional<Element> {
return Optional.ofNullable(this.select(query).first())
}
fun Element.textWithout(text: String): String = this.text().replace(text, "")
fun Element.href(): String = this.attr("href")
fun Element.absHref(): String = this.attr("abs:href")
fun Element.firstText(query: String): String = this.selectFirstOpt(query).map { it.text() }.orElse("")
fun Element.firstHref(query: String): String = this.selectFirstOpt(query).map { it.href() }.orElse("")
fun Element.firstAbsHref(query: String): String = this.selectFirstOpt(query).map { it.absHref() }.orElse("")
##例1:Yahoo News##
###取得したい内容を定義###
例として、news.yahoo.co.jp/ranking/access/newsの内容を取得するプログラムをつくります。
パッと見れば一つのニュースでは以下の構成
- タイトル
- 内容の連結
- 新聞の元
- 時間
- 画像
class NewsItem(
val title: String,
val url: String,
val source: String,
val date: String,
val imageUrl: String
)
まず、一つのニュース内容は全部newsFeed_item_linkというaの中にいます(赤の部分)。
この中で、欲しい内容は何処にいるかを探します(緑の部分)。
それぞれの唯一無二の特徴を探します。
- タイトル ー newsFeed_item_titleというdiv内
- 内容の連結 ー 最初のaのhref
- 新聞の元 ー newsFeed_item_mediaというspan内
- 時間 ー newsFeed_item_dateというtime内
- 画像の連結 ー 唯一のimg内のsrc
###Coding###
object YahooNewsParser {
fun get() {
val doc = JSoupDoc.fromUrl("https://news.yahoo.co.jp/ranking/access/news")
val newsList: List<NewsItem> = doc.listElement("a.newsFeed_item_link").map { parseSingleItem(it) }
val gson = GsonBuilder()
.setPrettyPrinting()
.create()
gson.toJson(newsList, FileWriter(Paths.get("news.json").toFile()));
}
private fun parseSingleItem(element: Element): NewsItem {
val url = element.href()
val title = element.firstText("div.newsFeed_item_title")
val source = element.firstText("span.newsFeed_item_media")
val date = element.firstText("time.newsFeed_item_date")
val imageUrl = element.selectFirstOpt("img")
.map { it.attr("src") }.orElse("").substringBefore("?")
return NewsItem(title, url, source, date, imageUrl)
}
}
##例2:楽天市場##
###取得したい内容とHTML分析###
一つの内容 ー dui-card searchresultitemというdiv
- タイトル ー content titleというdiv > h2 > aの文字
- 内容の連結 ー content titleというdiv > h2 > aのhref
- 価格 ー content description priceというdiv > spanの文字
- 商店 ー content merchant _ellipsisというdiv > aの文字
- 画像の連結 ー imageというdiv > a > imgのsrc
###Coding###
object RakutenShoppingParser {
class ShopItem(
val title: String,
val url: String,
val price: String,
val shop: String,
val imageUrl: String
)
fun get(keyWord: String) {
val doc = JSoupDoc.fromUrl("https://search.rakuten.co.jp/search/mall/$keyWord/")
val itemList: List<ShopItem> =
doc.listElement("div[class=dui-card searchresultitem]").map { parserSingleItem(it) }
val gson = GsonBuilder()
.setPrettyPrinting()
.create()
gson.toJson(itemList, FileWriter(Paths.get("items.json").toFile()));
}
private fun parserSingleItem(element: Element): ShopItem {
val titleElement = element.selectFirstOpt("div[class=content title] h2 a")
val title = titleElement.map { it.text() }.orElse("")
val url = titleElement.map { it.href() }.orElse("")
val price = element.firstText("div[class=content description price] span")
val shop = element.firstText("div[class=content merchant _ellipsis] a")
val imageUrl = element.selectFirstOpt("div.image a img").map { it.attr("src") }
.orElse("").substringBefore("?")
return ShopItem(title, url, price, shop, imageUrl)
}
}