crawler_by_aws_and_kotlin

E48409e5c7d5b8919b03d3d3c4a40da7?s=47 azihsoyn
February 26, 2019

 crawler_by_aws_and_kotlin

## AWSとKotlinで作るクローラー

azihsoyn

2019/02/26

scouty Crawler Night 2019

---

## 自己紹介

[![azihsoyn](img/twitter_icon.jpg)](https://twitter.com/azihsoyn)
{:.left-column}

* [ふそやん@azihsoyn](https://twitter.com/azihsoyn)
* 趣味
* 釣り
* アニメ
* 今期はかぐや様は告らせたい
* 上野さんは不器用
* 業務ではサーバーサイドエンジニア
* Go
* Kotlin
* [rehash.fm](http://rehash.fm)っていうポッドキャストやってます
{:.right-column .medium}

---

---

[グノスポ](https://gunosy-sports.com/)もあります

---

---

テックブログもあるので是非読んでみてください
* [導入編](https://tech.gunosy.io/entry/gunosy-sports1)
* [AppSync編](https://tech.gunosy.io/entry/gunosy-sports2)
* [デザイン編](https://tech.gunosy.io/entry/gunosy-sports3)
* [サーバー編](https://tech.gunosy.io/entry/gunosy-sports4)
* [インフラ編](https://tech.gunosy.io/entry/gunosy-sports5)

---

## 今回話すこと

* グノスポで作ったクローラーのレシピ
* クローラーの知見

---

## 今回話すクローラーの定義

* クローラーは提携メディアのフィードを定期的にクロール
* 提携メディアはグノスポが定めたフォーマット(Atom/RSS2.0)でフィードを作成

(google botみたいに無差別にクロールするわけではない)

---

#### グノスポのサーバーサイドのアーキテクチャ

![server-side-architecture](img/server-side-architecture.001.jpeg)

---

#### クローラーはこれだけ

![crawler](img/crawler.001.jpeg)

---

## 使っているライブラリ

---

## 使っているライブラリ
## XML parser + http client
---

## XML parser + http client

## Retrofit + JAXB

---

## XML Parser + http client

retrofit公式のconverterがある
[retrofit-conberers/jaxb](https://github.com/square/retrofit/tree/master/retrofit-converters/jaxb)

---

## XML Parser + http client

```kotlin
private val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl("https://dummy.gunosy.sport/")
.addConverterFactory(JaxbConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
```

```kotlin
interface FeedClient {
@GET
fun getRSS(@Url url: String): Observable

@GET
fun getAtom(@Url url: String): Observable
}
```

---

## XML Parser + http client

data classでxmlの構造を定義

```kotlin
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
@JaxbPojo // noarg用annotation
data class RSS(
@XmlAttribute
val version: String,
@JaxbPojo
val channel: Channel
)
```

---

## noargとは

kotlinのdata classにデフォルト引数を書かなくて済むようになるplugin

[no-arg-compiler-plugin](https://kotlinlang.org/docs/reference/compiler-plugins.html#no-arg-compiler-plugin)

---

## XML Parser + http client

呼び出し

```kotlin
retrofit.create(FeedClient::class.java).getRSS(url).subscribe({ rss ->
/* なにか処理 */
}, { error ->
error.printStackTrace()
})
```

---

## その他検討したXML parser

* [retrofit-converter-simplexml](https://github.com/square/retrofit/tree/master/retrofit-converters/simplexml)
* 最近`Deprecated`になった
* [FasterXML/jackson-module-kotlin](https://github.com/FasterXML/jackson-module-kotlin)
* 一部のxmlがパースできなかったため不採用
* javax.xml.parsers
* 一部のxmlがパースできなかったため不採用

---

## HTML parser

---

## HTML parser

## [jsoup](https://mvnrepository.com/artifact/org.jsoup/jsoup)

これはほぼ一択
特にハマることもなく使えた

html内の画像のパスを書き換えたりするのに利用

---

## HTML parser

```kotlin
// 本文内の画像アップロード
val doc = Jsoup.parse(rawArticle.content, "UTF-8")
doc.select("img").map { img ->
try {
val image = uploadImage(img.attr("src").toString())
img.attr("src", image.url)
img.attr("data-gs-width", image.width.toString())
img.attr("data-gs-height", image.height.toString())
} catch (e: Exception) {
e.printStackTrace()
}
}
```

---

## ファイル形式検出

---

## ファイル形式検出

## [mime-util](https://mvnrepository.com/artifact/eu.medsea.mimeutil/mime-util)

* 画像のファイル識別に利用
* アプリが対応している画像だけをサーバーに保存
* メディアが間違えておかしなファイルを指定してしまったときに弾く

---
## 使ってるAWSサービス
---

## クローリング編

---

## lambda

* クロールする処理をlambda関数として実行
* java 8 ランタイム
* メトリクスも自動で取れるので監視などが楽

---

## rekognition

* 画像をアプリのリストに表示する際に選手の顔がちゃんと表示されるようにする
* 認識に1秒もかからないぐらい早い

---

## DynamoDB

* クロールした各種情報の保存
* 提携メディアのfeedテーブル
* クロールした記事のarticleテーブル
* 試合情報
* チーム情報
* ...etc

---

## DynamoDB

* 現在lambdaから気軽に使えるデータ永続化サービスは実質DynamoDBしか選択肢がない
* Data API for Aurora Serverless が東京リージョンにきたら一部乗り換える予定🙏

参考: https://dev.classmethod.jp/cloud/aws/amazon-aurora-serverless-avaible-http-endpoint/

---

## DynamoDB

* DynamoDBを使う上で問題になるのはどうやってユニークなIDを発番するか
* articleごとに一意なキーが必要

今回は[snowflake](https://www.slideshare.net/moaikids/20130901-snowflake)をカスタマイズして利用

---

## snowflakeとは

* twitterが採用している(いた?) id生成ロジック
* グノスポではtimestamp部 + feed_id部 + incr部から生成
* lambdaは必ずfeed_id毎に実行
* 同じfeed_idでlambdaが同時に走らないことが前提

---

## S3
画像や記事本文を保存

## CloudWatch Logs
* lambdaのログはすべてcloudwatch logsに流す
* [CloudWatch Logs Insight](https://aws.amazon.com/jp/blogs/news/new-amazon-cloudwatch-logs-insights-fast-interactive-log-analytics/)が出たのでとても便利

---

## スケジューリング編

---

## CloudWatch Events
* クローラー毎に異なるスケジュールを設定
* コンテンツの特性に合わせる
* 試合中の情報は1分(最短)間隔
* 試合のスケジュールは1時間
* 記事の更新は10分
とか

---

## SQS
* 同じlambdaを異なるパラメータで同時に実行したいときに利用
* feed毎の記事の収集
* 試合中のデータの更新
* cludwatch events → lambda(ジョブをキューイング) → SQS → lambda(クロール)
* [Amazon SQS FIFO](https://aws.amazon.com/jp/blogs/news/amazon-sqs-fifo-tokyo/)を使うと[メッセージ重複排除](https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagededuplicationid-property.html)ができる

---

### 作り方終わり

---

### 知見の話

---

### クローラー、肥大化してませんか?

---

### サービスに新しい機能やコンテンツを追加するためにクローラーは改修が多くなりがち

---

* 増えるif文
* 増えるフラグ
* 増えるエンティティ
* 増える依存関係
* 増えるデータベース
* ...etc

---

### 管理できていれば問題ない

---

今流行りのクリーンアーキテクチャ

![clean-architecture](img/clean-architecture.jpg)

---

### いい本なので読みましょう
(クリーンアーキテクチャでクローラーを作りましょうという話じゃないです)

---

## 責務を分ける

---

#### もう一度サーバーサイドのアーキテクチャの確認

![crawler](img/crawler.001.jpeg)
---

### こうしてしまいがち

![crawler-anti-pattern](img/crawler-anti-pattern.001.jpeg)
---

### 全部クローラー!!

---

* 今回設計の段階でクロールした生データとアプリケーションから参照されるデータは別にしようというルールにした
(深く考えていたわけではないがAppSyncを使う上でこうなった)
* アプリケーションから参照されるデータはpre processというフローで生成する
* クローラーはxmlをパースして画像をS3にアップロードしてDynamoDBにデータを保存することに専念する

---

### クローラーはxmlをパースして画像をS3にアップロードしてDynamoDBにデータを保存することに専念する

---

# シンプル!!

---

# まとめ

---

## 使ってるライブラリ
* Retrofit + JAXB
* Jsoup
* mime-util
* noarg

---

## 使ってるAWSサービス
* lambda
* SQS
* CloudWatch Events
* CloudWatch Logs
* S3
* DynamoDB
* rekognition

---

## 責務を分ける

---

## Thanks!

---

E48409e5c7d5b8919b03d3d3c4a40da7?s=128

azihsoyn

February 26, 2019
Tweet

Transcript

  1. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 1/56 AWS と KOTLIN で作るクローラー

    AWS と KOTLIN で作るクローラー azihsoyn 2019/02/26 scouty Crawler Night 2019
  2. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 2/56 趣味 釣り アニメ 今期はかぐや様は告らせたい

    上野さんは不器用 業務ではサーバーサイドエンジニア Go Kotlin っていうポッドキャストやってます 自己紹介 自己紹介 ふそやん@azihsoyn rehash.fm
  3. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 3/56

  4. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 4/56 もあります! グノスポ

  5. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 5/56

  6. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 6/56 テックブログもあるので是非読んでみてください 導入編 AppSync 編

    デザイン編 サーバー編 インフラ編
  7. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 7/56 今回話すこと 今回話すこと グノスポで作ったクローラーのレシピ クローラーの知見

  8. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 8/56 今回話すクローラーの定義 今回話すクローラーの定義 クローラーは提携メディアのフィードを定期的 にクロール

    提携メディアはグノスポが定めたフォーマット (Atom/RSS2.0) でフィードを作成 (google bot みたいに無差別にクロールするわけでは ない)
  9. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 9/56 グノスポのサーバーサイドのアーキテクチャ グノスポのサーバーサイドのアーキテクチャ

  10. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 10/56 クローラーはこれだけ クローラーはこれだけ

  11. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 11/56 使っているライブラリ 使っているライブラリ

  12. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 12/56 使っているライブラリ 使っているライブラリ XML PARSER

    + HTTP CLIENT XML PARSER + HTTP CLIENT
  13. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 13/56 XML PARSER + HTTP

    CLIENT XML PARSER + HTTP CLIENT RETROFIT + JAXB RETROFIT + JAXB
  14. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 14/56 XML PARSER + HTTP

    CLIENT XML PARSER + HTTP CLIENT retrofit 公式のconverter がある retrofit-conberers/jaxb
  15. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 15/56 XML PARSER + HTTP

    CLIENT XML PARSER + HTTP CLIENT private val retrofit: Retrofit by lazy { Retrofit.Builder() .baseUrl("https://dummy.gunosy.sport/") .addConverterFactory(JaxbConverterFactory.create() .addCallAdapterFactory(RxJava2CallAdapterFactory.c .build() interface FeedClient { @GET fun getRSS(@Url url: String): Observable<RSS> @GET fun getAtom(@Url url: String): Observable<Atom> }
  16. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 16/56 XML PARSER + HTTP

    CLIENT XML PARSER + HTTP CLIENT data class でxml の構造を定義 @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement @JaxbPojo // noarg用annotation data class RSS( @XmlAttribute val version: String, @JaxbPojo val channel: Channel )
  17. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 17/56 NOARG とは NOARG とは

    kotlin のdata class にデフォルト引数を書かなくて済 むようになるplugin no-arg-compiler-plugin
  18. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 18/56 XML PARSER + HTTP

    CLIENT XML PARSER + HTTP CLIENT 呼び出し retrofit.create(FeedClient::class.java).getRSS(url).subscribe( /* 処理 */ }, { error -> error.printStackTrace() })
  19. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 19/56 その他検討した XML PARSER その他検討した

    XML PARSER 最近 になった 一部のxml がパースできなかったため不採用 javax.xml.parsers 一部のxml がパースできなかったため不採用 retrofit-converter-simplexml FasterXML/jackson-module-kotlin
  20. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 20/56 HTML PARSER HTML PARSER

  21. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 21/56 HTML PARSER HTML PARSER

    これはほぼ一択 特にハマることもなく使えた html 内の画像のパスを書き換えたりするのに利用 JSOUP JSOUP
  22. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 22/56 HTML PARSER HTML PARSER

    // 本文内 画像 val doc = Jsoup.parse(rawArticle.content, "UTF-8") doc.select("img").map { img -> try { val image = uploadImage(img.attr("src").toString()) img.attr("src", image.url) img.attr("data-gs-width", image.width.toString()) img.attr("data-gs-height", image.height.toString()) } catch (e: Exception) { e.printStackTrace() } }
  23. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 23/56 ファイル形式検出 ファイル形式検出

  24. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 24/56 ファイル形式検出 ファイル形式検出 画像のファイル識別に利用 アプリが対応している画像だけをサーバーに保

    存 メディアが間違えておかしなファイルを指定 してしまったときに弾く MIME-UTIL MIME-UTIL
  25. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 25/56 使ってる AWS サービス 使ってる

    AWS サービス
  26. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 26/56 クローリング編 クローリング編

  27. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 27/56 LAMBDA LAMBDA クロールする処理をlambda 関数として実行

    java 8 ランタイム メトリクスも自動で取れるので監視などが楽
  28. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 28/56 REKOGNITION REKOGNITION 画像をアプリのリストに表示する際に選手の顔 がちゃんと表示されるようにする

    認識に1 秒もかからないぐらい早い
  29. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 29/56 DYNAMODB DYNAMODB クロールした各種情報の保存 提携メディアのfeed

    テーブル クロールした記事のarticle テーブル 試合情報 チーム情報 …etc
  30. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 30/56 DYNAMODB DYNAMODB 現在lambda から気軽に使えるデータ永続化サー

    ビスは実質DynamoDB しか選択肢がない Data API for Aurora Serverless が東京リージョンに きたら一部乗り換える予定 参考: https://dev.classmethod.jp/cloud/aws/amazon- aurora-serverless-avaible-http-endpoint/
  31. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 31/56 DYNAMODB DYNAMODB DynamoDB を使う上で問題になるのはどうやって

    ユニークなID を発番するか article ごとに一意なキーが必要 今回は をカスタマイズして利用 snowflake
  32. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 32/56 SNOWFLAKE とは SNOWFLAKE とは

    twitter が採用している( いた?) id 生成ロジック グノスポではtimestamp 部 + feed_id 部 + incr 部か ら生成 lambda は必ずfeed_id 毎に実行 同じfeed_id でlambda が同時に走らないことが 前提
  33. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 33/56 S3 S3 画像や記事本文を保存 CLOUDWATCH

    LOGS CLOUDWATCH LOGS lambda のログはすべてcloudwatch logs に流す が出たのでとても便利 CloudWatch Logs Insight
  34. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 34/56 スケジューリング編 スケジューリング編

  35. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 35/56 CLOUDWATCH EVENTS CLOUDWATCH EVENTS

    クローラー毎に異なるスケジュールを設定 コンテンツの特性に合わせる 試合中の情報は1 分( 最短) 間隔 試合のスケジュールは1 時間 記事の更新は10 分 とか
  36. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 36/56 SQS SQS 同じlambda を異なるパラメータで同時に実行し

    たいときに利用 feed 毎の記事の収集 試合中のデータの更新 cludwatch events → lambda( ジョブをキューイン グ) → SQS → lambda( クロール) を使うと が できる Amazon SQS FIFO メッセージ重複排除
  37. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 37/56 作り方終わり 作り方終わり

  38. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 38/56 知見の話 知見の話

  39. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 39/56 クローラー、肥大化してませんか? クローラー、肥大化してませんか?

  40. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 40/56 サービスに新しい機能やコンテンツを サービスに新しい機能やコンテンツを 追加するためにクローラーは改修が多 追加するためにクローラーは改修が多

    くなりがち くなりがち
  41. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 41/56 増えるif 文 増えるフラグ 増えるエンティティ

    増える依存関係 増えるデータベース …etc
  42. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 42/56 管理できていれば問題ない 管理できていれば問題ない

  43. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 43/56 今流行りのクリーンアーキテクチャ

  44. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 44/56 いい本なので読みましょう いい本なので読みましょう ( クリーンアーキテクチャでクローラーを作りまし

    ょうという話じゃないです)
  45. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 45/56 責務を分ける 責務を分ける

  46. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 46/56 もう一度サーバーサイドのアーキテクチャの確認 もう一度サーバーサイドのアーキテクチャの確認

  47. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 47/56 こうしてしまいがち こうしてしまいがち

  48. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 48/56 全部クローラー!! 全部クローラー!!

  49. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 49/56 今回設計の段階でクロールした生データとアプ リケーションから参照されるデータは別にしよ うというルールにした (

    深く考えていたわけではないがAppSync を使う 上でこうなった) アプリケーションから参照されるデータはpre process というフローで生成する クローラーはxml をパースして画像をS3 にアップ ロードしてDynamoDB にデータを保存することに 専念する
  50. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 50/56 クローラーは XML をパースして画像を クローラーは

    XML をパースして画像を S3 にアップロードして DYNAMODB にデ S3 にアップロードして DYNAMODB にデ ータを保存することに専念する ータを保存することに専念する
  51. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 51/56 シンプル!! シンプル!!

  52. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 52/56 まとめ まとめ

  53. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 53/56 使ってるライブラリ 使ってるライブラリ Retrofit +

    JAXB Jsoup mime-util noarg
  54. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 54/56 使ってる AWS サービス 使ってる

    AWS サービス lambda SQS CloudWatch Events CloudWatch Logs S3 DynamoDB rekognition
  55. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 55/56 責務を分ける 責務を分ける

  56. 2019/2/26 AWS とKotlin で作るクローラー http://localhost:10000/?print-pdf#/ 56/56 THANKS! THANKS!