AWS Lambda でWebスクレイピングできなかったサイトについて

AWS Lambda でWebスクレイピングできなかったサイトについて

ちょっと前に書いた以下のWebスクレイピングの記事に関してですが、結局AWS Lambdaでは動かすことができず、EC2で妥協したサイトが2種類ほどありましたので、情報を残しておきたいと思います。

メール認証が必要なサイト

ユーザー名とパスワードの認証が終わった後にSMSやメールに認証コードが送られてきて、それを入力しなければならないため、AWS Lambdaで動作させることは難しいと判断しました。Lambdaは毎回違うインスタンスが立ち上がるためパスワードを記憶させることは出来ないという結論に至りました。

キャプチャ認証が必要なサイト

seleniumでキャプチャ認証を突破する有料サービスはあるのですが、セキュリティ面と手間がかかることを考慮してLambdaで動かすことは断念しました。

キャプチャ認証を突破する方法は以下のサイトが分かりやすいと思います。

EC2で動作させるためのノウハウ

今回、自動化を難しくしているサイトの特徴として、ログイン認証が複雑なことがネックになることが分かりました。EC2ではLambdaとは違いGUIでブラウザのウィンドウを立ち上げてWebスクレイピングすることができますので、このGUIを初回だけ人が操作して認証を突破し、パスワードを記憶させて、2回目以降は認証なしでサイトにアクセス出来るようにしました。

パスワードをブラウザに記憶させるコツですが、初回用に以下のようなPythonプログラムを作って対応します。ログインページにアクセスした後、長時間スリープさせておいて、その間に各種の認証を突破します。

try:
    import unzip_requirements
except ImportError:
    pass
 
import sys
import os
import time
import calendar
import datetime
import logging
import subprocess
import post_to_chatwork
import boto3
import shutil
from glob import glob
from pathlib import Path
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import Select

mode2bucket = {
    'dev': 'sample-scraping-dev-us-east-1',
    'pro': 'sample-scraping'
}

def post_to_chat(title, body):
    ptc = post_to_chatwork.PostToChatwork(title, body)
    ptc.post_to_chatwork()
    del ptc
  
def main(event, context):
    print("main start") 

    # レベル
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    STAGE = os.environ['selected_stage']
    print("stage[%s]" % STAGE)

    MODE = os.environ['scraping_mode']
    print("mode[%s]" % MODE)

    prog_name = os.path.splitext(os.path.basename(__file__))[0]
    if context:
        print("start lambda_handler() [%s]" % context.function_name)

    #today
    d = datetime.datetime.today()
    #yesterday
    d2 = d - datetime.timedelta(days=1)

    target_url = 'https://sample.sample/'
    user_id = 'sample@sample.sample'
    password = os.environ['user_password']

    try:
        options = webdriver.ChromeOptions()
        options.binary_location = "/opt/google/chrome/chrome"
        # options.add_argument("--headless")
        # options.add_argument("--disable-gpu")
        # options.add_argument("--window-size=1280x1696")
        # options.add_argument("--disable-application-cache")
        # options.add_argument("--disable-infobars")
        # options.add_argument("--no-sandbox")
        # options.add_argument("--hide-scrollbars")
        # options.add_argument("--enable-logging")
        # options.add_argument("--log-level=0")
        # options.add_argument("--single-process")
        # options.add_argument("--ignore-certificate-errors")
        options.add_argument("--homedir=/tmp")
        options.add_argument("--user-data-dir=/home/ubuntu/.config/google-chrome/Default")
        driver = webdriver.Chrome(options=options, executable_path='./bin/chromedriver')
        driver.command_executor._commands["send_command"] = (
            "POST",
            '/session/$sessionId/chromium/send_command'
        )
        params = {
            'cmd': 'Page.setDownloadBehavior',
            'params': {
                'behavior': 'allow',
                'downloadPath': '/tmp'
            }
        }
        driver.execute("send_command", params=params)
        driver.implicitly_wait(10)
        driver.get(target_url)

        # login
        print("before log in")
        # driver.find_element_by_name("rememberMe").click()
        # driver.find_element_by_id("email").send_keys(user_id)
        # driver.find_element_by_id("password").send_keys(password)
        # driver.find_element_by_id("signIn").click()
        print("after log in")
        print(driver.current_url)
        time.sleep(1000)

        # 手動による認証が終わったらCtrl+Cなどでプロセスを終了させる
  • ヘッドレスchromeから普通の画面ありchromeに変更したため、options.binary_locationのパスを修正しました。
  • options.add_argument(“–headless”)以下のオプションは画面ありchromeで不要なため、削除しています。
  • パスワードを記憶させたいため、ユーザー情報保持用のディレクトリをoptions.add_argument(“–user-data-dir=??”)で指定しています。

まとめ

この2種類のサイトについては、もしREST APIが公開されていれば、使用して自動化も簡単にできたと思うのですが、残念ながら現状存在しませんでした。是非とも公開してもらえるとありがたいなぁと思う今日この頃です。

we are hiring

優秀な技術者と一緒に、好きな場所で働きませんか

株式会社もばらぶでは、優秀で意欲に溢れる方を常に求めています。働く場所は自由、働く時間も柔軟に選択可能です。

現在、以下の職種を募集中です。ご興味のある方は、リンク先をご参照下さい。