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


1. [要件定義〜python環境構築](https://qiita.com/kamyu1201@github/items/65a86ec1e28ec9969dcb)
1. [サイトのスクレイピング機構を作る](https://qiita.com/kamyu1201@github/items/a07c7d175c051b8ab4c0)
1. [ダウンロードしたファイル(xls)を加工し、最終成果物(csv)を作成するようにする](https://qiita.com/kamyu1201@github/items/925337a317dec3c6f5d0)
1. **S3からのファイルダウンロード / S3へのファイルアップロードをつくる**
1. 2captchaを実装
1. Dockerコンテナで起動できるようにする
1. 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は膨大なので、テスト時は実行時間が短くなるように「テスト実行モード」ができるようにしておきます。

```python: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はこんな感じです

```python: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`しか取得しないことにするのでちょっと冗長になっちゃうなあ。まあいいか。

```python: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にファイルを送る処理

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

```python: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を立ち上げっぱなしにする必要がある

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