Help us understand the problem. What is going on with this article?

reCAPTCHAのサイトを毎日自動スクレイピングする (4/7: S3ファイル処理)

  1. 要件定義〜python環境構築
  2. サイトのスクレイピング機構を作る
  3. ダウンロードしたファイル(xls)を加工し、最終成果物(csv)を作成するようにする
  4. S3からのファイルダウンロード / S3へのファイルアップロードをつくる
  5. 2captchaを実装
  6. Dockerコンテナで起動できるようにする
  7. AWS batchに登録

S3の操作

ファイル構成の変更

前回までで スクレイピング〜ファイルの加工までをだらだら書いていきましたが、このタイミングでファイルの分割をしました。

S3につなげる=Inputとなるwordsが大量になる=テスト実行に時間がかかるということで、テストモードを作るのがひとつの目的です。

今回はこんな感じに分けてみました。

ファイル構成
├── app
│   ├── drivers          seleniumドライバーを置く
│   └── source           
│       ├── run.py           メイン実行ファイル
│       ├── scraping.py      スクレイピング処理(第2回)
│       ├── make_outputs.py  ダウンロードしたファイルの加工処理(第3回)
│       ├── s3_operator.py   S3に関する処理(今回:第4回)
│       └── configs.py        環境変数やパスワードなど
└── tmp
    ├── files
    │   ├── fromS3       メイン実行
    │   ├── toS3         メイン実行
    │   └── download     スクレイピングでダウンロードしたファイルを置く
    └── logs             ログ(seleniumログなど)

メインの実行ファイル=run.py から、各ファイルの処理を呼び出します。
S3から取ってくるwordsは膨大なので、テスト時は実行時間が短くなるように「テスト実行モード」ができるようにしておきます。

run.py
if __name__ == '__main__':
  #テスト実行モードを作れるようにしておく
  parser = argparse.ArgumentParser(description='scraping batch')
  parser.add_argument('--run_mode', dest='run_mode', default = 'normal', help='run_mode: test | normal')
  args = parser.parse_args()

  # 環境ごとの変数を取ってくる
  env = os.getenv('BATCH_ENV', 'LOCAL')
  config = configs.load(env)

  #メイン処理  
  words = s3_operator.getFromS3(config) #S3からファイルを取得し、INPUTたるwordsを得る
  scraping.main(config,args,words) #スクレイピング処理(テスト実行時は2件で終わるようにしている)
  make_outputs.main(config,words) #ファイル処理
  s3_operator.sendToS3(config) #S3にファイルを送る

ちなみに、configs.pyはこんな感じです

configs.py
class Config:

    login_url = "XXXXXXXXXX"
    login_id =  "XXXXXXXXXX"
    login_password = "XXXXXXXXX"

    #S3
    region_name ="XXXXXXXXXX"
    input_path = "XXXXXXXXXXX"
    output_path = "XXXXXXXXXX"

    @staticmethod
    def load(env_name):
        if env_name == 'PRD':
            return PrdConfig()
        elif env_name == 'STG':
            return StgConfig()
        elif env_name == 'DEV':
            return DevConfig()
        else:
            return LocalConfig()

class LocalConfig(Config):
    access_key_id = 'XXXXXXXXX'
    secret_access_key = 'XXXXXXXXXX'
    bucket_name = 'XXXXX'

#...以下 DevConfig,StgConfig,PrdConfigも同様

S3からファイルを取得する処理

S3からファイルを取得する処理を書いていきます。

  • ダウンロードファイルの格納先を作る
  • S3のファイル名の一覧を取得
  • 一つずつダウンロード
  • ダウンロードしたファイルから取得

※実際にはファイルが複数あったので上記の流れにしたのですが…この記事では/YYYYMMDD/words.csvしか取得しないことにするのでちょっと冗長になっちゃうなあ。まあいいか。

s3_operator
def getFromS3(config):
    #S3からダウンロードしたファイルの格納先を作る
    date = datetime.now().strftime('%Y%m%d')
    dldir_name = os.path.abspath(__file__ + '/../../../tmp/files/fromS3/'.format(date))
    dldir_path = Path(dldir_name)
    dldir_path.mkdir(exist_ok=True)
    download_dir = str(dldir_path.resolve())

    #S3に接続するクライアント情報を定義
    s3_client = boto3.client(
      's3',
      region_name=config.region_name,
      aws_access_key_id=config.access_key_id,
      aws_secret_access_key=config.secret_access_key,
    )

    #指定したbucket内の指定パスなファイルの一覧を取得
    key_prefix = config.output_path + '/{}/'.format(date)
    response = s3_client.list_objects_v2(Bucket=config.bucket_name,Prefix=key_prefix)
    if response["KeyCount"] == 0:
        print('オブジェクトが存在しません:{}'.format(key_prefix))
        return

    #ファイルを一つずつダウンロード
    for object in response["Contents"]:
        object_name = object["Key"]
        download_file_name = os.path.basename(object["Key"])
        if len(download_file_name) == 0:
            continue
        download_file_name = download_dir + '/' + download_file_name
        s3_client.download_file(Bucket=config.bucket_name, Key=object_name, Filename=download_file_name)
        print('オブジェクト「{}」をダウンロードしました。ダウンロード先:{}'.format(object_name,download_file_name))

    #wordsを得る
    download_file_name = download_dir + '/words.csv'
    return pd.read_csv(download_file_name).values.tolist()

S3にファイルを送る処理

こちらは、ファイルを送る処理。こっちは作ったものを送るだけなので簡単です

s3_operator
def exportToS3(config):
    date = datetime.now().strftime('%Y%m%d')
    s3_client = boto3.client(
        's3',
        region_name=config.region_name,
        aws_access_key_id=config.access_key_id,
        aws_secret_access_key=config.secret_access_key,
    )

    upfiles = glob.glob(os.path.abspath(__file__ + '/../../../tmp/files/toS3/{}/*'.format(date)))
    for f in upfiles :
        filename = os.path.split(f)[1]
        object_name = config.input_path + "/{}/{}".format(date,filename)
        s3_client.upload_file(f,config.bucket_name,object_name)

完成

これで、python run.pyすれば、目的を達成できるプログラムになりました。

が…

  • 毎週実行するには自分のPCで叩かなければならない
  • reCAPTCHAを自分で解除しなければならない
  • 実行中はPCを立ち上げっぱなしにする必要がある

という状況なので、次回からはそれをなんとかしていきます。

kamyu1201@github
javaエンジニア6年 → SE兼エンジニアMgr2年 → 現在は Product Manager の3年目です。 `ちゃんと使われるシステム` にこだわりたい。 モダンなシステム開発ができるよう、エンジニア再修行中。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした