Python
Scrapy
docker-compose

docker-composeでScrapydサーバーを立てる

これはクローラー/Webスクレイピング Advent Calendar 2017の10日目の記事です。

はじめに

アドベントカレンダーに参加登録した時は、こんな感じでScrapydの紹介記事を書こうと思っていました。

Screen Shot 2017-12-10 at 12.41.11.png

そう。当日この軽めのスクレイピングプラットフォームを求めてScrapydを試してみたを見かけるまでは。。。
この記事の「2017年12月09日に更新」って昨日やん。。。ネタが。。。

ということで、同じこと書いてもつまらないので、Scrapydを実際にこんな感じで使っているというのを書きます。Scapyd自体の紹介は軽めのスクレイピングプラットフォームを求めてScrapydを試してみたをご覧くださいw

docker-composeを使う

開発はローカル、本番はGCEインスタンスを立てていますが、どちらもdocker-composeを使っています。ディレクトリ構成はこんな感じです。

.
├── .env  # ローカル用
├── db_init_local
│   └── db_init_local.sql  # ローカルで使うmysqlコンテナ初期化用
├── docker-compose.yml  # 本番(GCE)用
├── docker-compose_local.yml  # ローカル用
├── nginx  # nginxイメージ用。
│   ├── Dockerfile
│   └── default.template
└── scrapyd  # scrapydイメージ用。
    ├── Dockerfile
    ├── requirements.txt  # scrapyプロジェクトで必要なものすべて
    ├── scrapyd.conf  # bind_address = 0.0.0.0のみ記載
    └── start.sh

要点を列挙するとこんな感じです。

  • 変数は .env から読み込む。ローカルでは上に記載したディレクトリ一覧にある.envを、GCEインスタンスではAnsibleのtemplateを使って配置した.envファイルを利用しています。
  • DBの接続情報を環境変数で渡す。.envからの読み込み。
  • JSを使ったサイトもスクレイピングできるようにseleniumコンテナも利用
  • ずっと起動しておいてもらいたいので restart: unless-stopped を指定
  • ./scrapyd/Dockerfile ではCMD ["./start.sh"]し、その中でscrapydコマンドを実行しています。本番環境では、CloudSQLを利用するためのプロキシをstart.shの中で起動しているためです。
  • Nginxの設定ファイルは後述するようにenvsubstで書き換え。
  • これを下記のめちゃめちゃ長いコマンドで実行していますw
    • 長くなっているのはdocker-compose自体をdocker runで動かしているため。
    • これはContainer-Optimized OSというコンテナ実行用に最適化されたOSを利用しているからです。
  docker run --rm -d --name compose -v /var/run/docker.sock:/var/run/docker.sock -v "{{ docker_home }}:/rootfs{{ docker_home }}" -w="/rootfs{{ docker_home }}" docker/compose:1.13.0 up --build --force-recreate
docker-compose.yaml
version: '3'
services:
  scrapyd:
    environment:
      DB_HOST: ${DB_HOST}
      DB_NAME: ${DB_NAME}
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
    build:
      context: ./scrapyd
    image: scrapyd
    volumes:
    - ${LOGS}:/usr/src/app/logs
    - ${EGGS}:/usr/src/app/eggs
    - ${DBS}:/usr/src/app/dbs
    links:
    - selenium
    restart: unless-stopped
  selenium:
    image: selenium/standalone-chrome  # 公式イメージ
    ports:
    - "4444:4444"
    restart: unless-stopped
  nginx:
    environment:
      NGINX_SERVER_NAME: ${NGINX_SERVER_NAME}
    build:
      context: ./nginx
    links:
    - scrapyd
    ports:
    - "80:80"
    restart: unless-stopped
  • scrapyd/Dockerfile
    • 特に変わったことはしていないです。
FROM python:3.6
WORKDIR /usr/src/app
COPY ./scrapyd.conf ./requirements.txt ./start.sh ./
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 6800  # Scrapydのデフォルト値
CMD ["./start.sh"]
  • nginx/Dockerfile
    • nginxの設定ファイルでは環境変数をそのままでは利用できません。そこで、envsubstを使うことで、.envに記載したNGINX_SERVER_NAMEをnginxの設定で利用しています。
FROM nginx:latest
COPY default.template /etc/nginx/conf.d/default.template
CMD ["/bin/bash", "-c", "envsubst '$$NGINX_SERVER_NAME' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"]

ローカル用

ローカルでは下記のようにmysqlコンテナも利用しています。
実行コマンドは docker-compose -f docker-compose_local.yaml up です。

  • ローカルではCloudSQLを使わないので command: scrapyd で起動時の処理を上書き
  • mysqlコンテナは/docker-entrypoint-initdb.dに配置したsqlファイルやスクリプトを元にDBの初期化をします。必要なテーブルはここに置いたsqlで作成しています。
  version: '3'
  services:
    scrapyd:
++    command: scrapyd
      environment:
        DB_HOST: ${DB_HOST}
        DB_NAME: ${DB_NAME}
        DB_USER: ${DB_USER}
        DB_PASSWORD: ${DB_PASSWORD}
      build:
        context: ./scrapyd
      image: scrapyd
      volumes:
      - ${LOGS}:/usr/src/app/logs
      - ${EGGS}:/usr/src/app/eggs
      - ${DBS}:/usr/src/app/dbs
      links:
      - selenium
++    - mysqldb
    selenium:
      image: selenium/standalone-chrome
      ports:
      - "4444:4444"
    nginx:
      environment:
        NGINX_SERVER_NAME: ${NGINX_SERVER_NAME}
      build:
        context: ./nginx
      links:
      - scrapyd
      ports:
      - "80:80"
++  mysqldb:
++    image: mysql
++    ports:
++    - "3306:3306"
++    environment:
++      MYSQL_ROOT_PASSWORD: "password"
++      MYSQL_USER: ${DB_USER}
++      MYSQL_PASSWORD: ${DB_PASSWORD}
++      MYSQL_DATABASE: ${DB_NAME}
++    volumes:
++      - ./db_init_local:/docker-entrypoint-initdb.d

その他の運用について

  • Scrapyプロジェクトはリポジトリを分け、masterへのpush時に自動でScrapydサーバーにデプロイするようにしています。
  • クローリングの実行はJenkinsの定期実行でcurlを投げています。
    • こちらの記事にあるscrapyd-clientですが、現在のバージョンv1.1.0だとまだまだ機能が貧弱です。(こちらの記事でもmasterから持ってきてますね)
    • いまのmasterにあるものがデプロイされたらやっと使えるかなという状況です。
  • ScrapydはAnsibleでGCE上にデプロイしています。クローラ自体は日々動かしていますが、前回このコマンドを打ったのがいつか忘れてるくらいには安定的に稼働してくれています。