5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【SwiftSoup】ADVENTARから取得したHTMLをパースしてアプリに表示する

Last updated at Posted at 2022-12-25

ADVENTARから25日分の記事情報を取得して、アプリに表示したいというケースがありました。

とここで悲報ですが、ADVENTARには公式で公開されているAPIがありません。。なのでスクレイピングしました。普段HTMLをあまり見ていないのと、スクレイピング自体が初めてだったこともあってなかなか大変でした。 :sweat:

せっかく頑張ったので記録を残しておこうと思います。
当然のことですが、HTMLの構造が変わったら使えなくなる可能性が高いので、その点ご了承ください。 :pray:

環境

  • Xcode14.2
  • SwiftSoup2.4.3

完成したもの

以下のデータを取得します。タイトルが設定されていなかった場合は、コメントをタイトルとして使用します。

  • 日付
  • 投稿者名
  • 投稿者のプロフィール画像
  • 記事タイトル
  • コメント
  • 記事URL
ファイル名

実装

前述したようにADVENTARにはAPIがないので、SwiftSoupというライブラリを使ってHTMLをパースすることにしました。流れとしては以下です。

  1. ADVENTARの特定のURLからHTMLを取得する(今回は「https://adventar.org/calendars/7577 」を使いました)
  2. SwiftSoupを使って頑張ってパースする

2つ目の頑張ってパースするところについて少し説明します。

まず取得したString文字列を、SwiftSoupが提供しているDocument型に変換します。
以下はコードの一部を取り出していますが、tryがついていることからもわかるように例外が投げられることがありますので、実際に書くときはdo catchで囲う必要があります。

let doc: Document = try SwiftSoup.parse(htmlString)

この後は、HTMLの要素を指定して、欲しいデータを取得していくことになります。
がその前に、パースしたいHTMLの構造をよく見て、どの要素に欲しいデータが入っているかを特定します。

まず今回はADVENTARのカレンダー表示の下にあるリスト?からデータを取得することにしました。
構造としては、EntryListというクラスのdivタグの中に、itemクラスのliタグがずらっと並んでいることがわかります。

image.png

これを取得するためには、これもSwiftSoupが提供しているElements.select(_ selector: String)メソッドを使います。
これは引数にStringでセレクターを指定することで、該当のデータを取得します。

let entries = try doc.select("ul.EntryList").select("li.item")

entriesはこれもSwiftSoupが提供するElements型を返します。

ここからは、日付や投稿者名など一つ一つの投稿情報を取得していきます。

スクリーンショット 2022-12-25 16.50.45.png

基本的には先ほど使った、Elements.select(_ selector: String)メソッドと要素のテキストを返すElement.text()メソッド、属性を指定するNode.attr(_ String key)メソッドを使いました。

entryIndexは、entriesの要素番号が入ります。(今回は0~24)

// 日付
let date = try entries[safe: entryIndex]?.select("div.date").text()
// 投稿者名
let name = try entries[safe: entryIndex]?.select("div.user").text()
// 記事URL
let url = try entries[safe: entryIndex]?.select("div.article").select("div.link").first()?.text()
// 投稿者プロフィール画像URL
let iconImage = try entries[safe: entryIndex]?.select("div.user").select("img").attr("src")
// コメント
let comment = try entries[safe: entryIndex]?.select("div.comment").text()

タイトルだけ、divタグを特定しやすいクラスが設定されておらず、取得の仕方を少し工夫しました。
構成としてはdivタグのleftクラスに2つのdivタグがあり、その2つ目が取得したいタイトルです。

// 1. divタグのleftを取得する
let leftHTMLElements = try entries[safe: entryIndex]?.select("div.left")
// 2. Stringに変換する
let leftHTMLString = try leftHTMLElements?.html() ?? ""
// 3. StringをDocument型に変換し、divタグを指定して、2つ目の要素を取得する -> それがタイトル
try SwiftSoup.parse(leftHTMLString).select("div")[safe: 1]?.text()

もっといいやり方があるかもしれないのですが、なかなかできなかった結果、こうなりました・・・(誰か教えて。。)

おわり

最初にも書いたように、HTMLの構造が変わったら役に立たないコードなのですが、
スクレイピングって大変だ・・・ということを実感できたのでいいかなと思いました。 :sweat:

SwiftSoupは使いやすかったです。

ADVENTARさん、公開APIお待ちしております。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?