12
8

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 5 years have passed since last update.

スクレイピングをAPI化したい人生だった

Last updated at Posted at 2015-11-10

前置き

前回の記事「Nightmareで郵便局から住所をスクレイピング」にて、スクレイピングの世界に入門してみました。せっかくなので流行りのAmazon API GatewayAWS LambdaでAPI化してみようと思ったのですが、これはうまくいきませんでした。。。
Lambdaは、コードの依存先もあわせてzipやjarに固めたのちアップロードする必要があります。ここで問題になるのが、依存ライブラリにネイティブ拡張が含まれている場合です。Nightmare(v2)はブラウザとしてElectronを使っていて、たとえばWindowsでnpm installするとelectron.exeが入ってくるのですが、LambdaはAmazon Linux上のサービスなのでこれは動かないのです……。
検索してみると、Amazon EC2やVagrantでAmazon Linux環境作って依存ライブラリをビルドなり解決なりしてねとの回答が出てくるのですが、お手軽にやりたいからLambda使うのにそこまで手をかけるのは……という感じです。

ということでNightmareはばっさり諦めて、SeleniumのJava版を使ってみることにしました。
言語もJavaScriptからScalaに切り替えです……!

コード

与えられた文字列を、Googleの検索フォームに入力して、検索結果のタイトルリストを取得するだけのコードです。見たまんまですね。
ブラウザにはHtmlUnitというJava実装のものを使っています。なので、ブラウザごとjarに固めることができます><b

src/main/scala/main.scala
import com.gargoylesoftware.htmlunit.BrowserVersion
//import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.htmlunit.HtmlUnitDriver

import scala.collection.JavaConversions._

object Main {
  def main(args: Array[String]) {
    println(awsLambda(args.head))
  }

  def awsLambda(str: String): java.util.List[String] = {
    //System.setProperty("webdriver.chrome.driver","C:\\path\\to\\chromedriver.exe")
    //val driver = new ChromeDriver()
    val driver = new HtmlUnitDriver(BrowserVersion.CHROME)
    driver.get("http://www.google.com")
    val query = driver.findElementByName("q")
    query.sendKeys(str)
    query.submit
    val results = driver.findElementsByClassName("r").map(_.getText)
    driver.quit()
    results
  }
}

ローカルでブラウザのGUIの様子を見たい時は、使いたいブラウザに応じたドライバをダウンロードして、コメントアウトしているように指定してください。

AWSの設定

sbt assemblyしてjarさえ作ってしまえば、あとは簡単です。
AWS Lambdaの「Code」からjarをアップロードして、「Configuration」にてMain::awsLambdaをHandler指定して、「API endpoints > Add API endpoint」から「API Gateway」を選んで、ぽちぽちPOSTメソッドのAPI設定をすれば完成。これが期待していたお手軽さ!
ただし注意点として、上記のコードでjarのサイズが25MBくらいになるので、ときどきアップロードが失敗します。「For .ZIP files larger than 10 MB, consider uploading via S3.」の注意書きどおりAmazon S3経由を検討しましょう。
あとタイムアウトした場合は、「Configuration > Advanced settings」で長めに設定しなおしてください。

実行結果

まずはAWS Lambdaのみでテストしてみます。
「Actions > Configure Test Event」にテスト用のリクエストボディ"Qiita"を入力して、「Test」すると、

[
  "Qiita - A technical knowledge sharing platform for programmers.",
  "タグ一覧",
  "Qiita:Team",
  "About Qiita",
  "Advent Calendar",
  "ログイン",
  "Organization一覧",
  "キータ (@Qiita) | Twitter",
  "Qiitaで何があったのか - はてな匿名ダイアリー - はてラボ",
  "qrank | Qiitaの人気記事ランキング",
  "Qiitaってなに?よく分かってなかった方のためのQiita基礎知識|ferret ...",
  "Qiita のニュース検索結果",
  "週刊 Qiita"
]

よしよしうまくいきました。消費リソースは20秒/126MBでした。
まぁスクレイピングなのでそのくらいは食いますよね。さてお待ちかねのAPI実行をば

> curl -X POST https://XXXXXXX.ap-northeast-1.amazonaws.com/prod/hello_api -H "x-api-key: XXXXXXX" -d "Qiita"
{"message": "Endpoint request timed out"}

おっとっと、API Gatewayのタイムアウト設定も長くしなきゃですね。えーと設定はどこで……

以下に示しているのは、API Gateway における現在の制限です。
...
AWS Lambda と HTTP バックエンド統合の両方で 10 秒のタイムアウト。

Amazon API Gateway における制限

_人人 人人_
> 突然の死 <
 ̄Y^Y^Y^Y ̄

結論

冷静に考えると、今回の用途では自分の管理下からのみ呼びだせればいいので、API Gateway使わずにAWS SDKで直接Lambda呼びだせばいいじゃんということに。それでもタイムアウト上限は60秒なので注意。

Q: AWS Lambda 関数はどれくらいの時間実行できますか?
AWS Lambda に対する同期呼び出しは、60 秒以内に完了する必要があります。デフォルトのタイムアウトは 3 秒ですが、1 秒から 60 秒までの任意のタイムアウト時間を設定できます。非同期リクエストも現在のところ、実行時間が 60 秒に制限されています。

よくある質問 - AWS Lambda

動作環境

build.sbt
name := "hello_api"
version := "1.0"
scalaVersion := "2.11.7"
libraryDependencies ++= Seq(
  "org.seleniumhq.selenium" % "selenium-java" % "2.48.2"
)
project/build.properties
sbt.version=0.13.8
project/plugins.sbt
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.0")
12
8
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
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?