20211031のdockerに関する記事は7件です。

GASのローカル環境開発 1.コンテナ作成

GASのローカル環境開発 GASのローカルで管理(実行、デプロイ)できるようにすることを目的とします。 3部構成で記事を書くつもりでいます。  1. コンテナでの環境開発構築 2. GASとGCPの連携 3. ローカルからGASを動かす 初投稿で至らない点もあるかと思いますが、よろしくお願いします。 目次 1.概要 2.必要なツールと拡張機能 3.目標とするディレクトリ構成 4.今回の記事で作られるディレクトリの構成 5.devcontainer.jsonの作成 6.Dockerfileの編集 7.docker-compose.ymlの作成 8.devcontainer.jsonの編集 9.コンテナのビルド 10.確認 1. 概要 今回は、コンテナでの環境開発構築についてまとめてみました。 2. 必要なツールと拡張機能 VS Code Remote Container Docker Desktop 自分の環境 MacOS : Big Sur ver:11.6 vscode : 1.61.2 docker : 20.10.8 Windowsでの、実行も確認済みです。 ※ Windowsの場合はwsl2の環境を作成しておく 3. 目標とするディレクトリ構成 gas-tutorial | |- .devcontainer | |- devcontainer.json | |- node.dockerfile |- gas | |- src | | |- test.js | | | |- .clasp.json | |- .clasprc.json | |- creds.json | |- docker-compose.yml 4. 今回の記事で作られるディレクトリの構成 gas-tutorial | |- .devcontainer | |- devcontainer.json | |- node.dockerfile | |- docker-compose.yml 5. devcontainer.jsonの作成 vs codeでコンテナを作成するディレクトリを開く devcontainer.jsonを作成 F1キー or Clt + Shift + P Remote-Containers:Rebuild and Reopen in Container Select a container configuration definition Node.js Node.js version 16-bullseye Select additional features to install 選択なし 上記のステップを終えると、Dockerfileが自動で作成され、buildが行われる。 build終了後、左下の >< Dev Container: Node.jsを選択し, Reopen Folder Locally(WSL2) を選択して、元のディレクトリに戻る ディレクトリ構造を確認してみると、ディレクトリ配下に.devcontainerが作成される。 すると以下のような、ディレクトリ構成となる。 gas-tutorial | |-.devcontainer | |-devcontainer.json | |-DockerfileS 6. Dockerfileの編集 最終行に、 RUN npm install @google/clasp -gを追加 Dockerfile(node.dockerfile) # RUN su node -c "npm install -g <your-package-list-here>" + RUN npm install @google/clasp -g やらなくても可 ファイル名を Dockerfile → node.dockerfileに変更 7. docker-compose.ymlの作成 docker-compose.ymlを作成する docker-compose.yml version: "3" services: gas: build: context: . dockerfile: ./.devcontainer/node.dockerfile tty: true volumes: - "./:/opt" working_dir: /opt ※ Dockerfileの名前を変更していない場合は./.devcontainer/node.dockerfileを./.devcontainer/Dockerfileに変更 現在のディレクトリ構成 ここで、本記事の今回の記事で作られるディレクトリの構成が完成 gas-tutorial | |- .devcontainer | |- devcontainer.json | |- node.dockerfile | |- docker-compose.yml 8. devcontainer.jsonの編集 docker-compose.ymlのサービス名を元にコンテナを作成するように編集する (5-12行目を)以下のように編集 devcontainer.json "dockerComposeFile": "../docker-compose.yml", "service": "gas", "workspaceFolder": "/opt", 相違点 devcontainer.json "name": "Node.js", - "runArgs": ["--init"], - "build": { - "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick a Node version: 16, 14, 12. - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local arm64/Apple Silicon. - "args": { "VARIANT": "16-bullseye" } - }, + "dockerComposeFile": ../docker-compose.yml, + "service": "gas", + "workspaceFolder": "/opt", dockerComposeFile 参照するdocker-compose.ymlを指定 service docker-compose.ymlに記載しているサービス名を指定 workspaceFolder docker-compose.ymlのworking_dirの値を指定 コンテナ内で使用する、VS codeの拡張機能を指定する 下は、個人的にオススメの拡張機能 devcontainer.json // Add the IDs of extensions you want installed when the container is created. "extensions": [ "dbaeumer.vscode-eslint", + "mhutchie.git-graph", + "yzhang.markdown-all-in-one" ], "mhutchie.git-graph" gitのネットワークが見れる yzhang.markdown-all-in-one markdownの保管機能が働く 拡張機能の入れ方 方法1 ⚙マーク > 拡張機能のIDをコピー を選択し、上記のように"extentions"に記述する。 方法2 ※.devcontainerを開いている状態であれば、 ⚙マーク > Add to devcontainer.json で自動的に指定した拡張機能がdevcontainer.jsonに追加される。 9. コンテナのビルド vs codeでコンテナを作成するディレクトリを開く devcontainer.jsonを作成 F1キー or Clt + Shift + P Remote-Containers:Rebuild and Reopen in Container 上記のステップでコンテナが立ち上がり、以下のようにコンテナ内へ移動する。 10. 確認 clasp command claspコマンドが入っているかを確認。 ターミナルにてclasp --helpを入力後、以下のように出力されればOK 拡張機能の確認 devcontainer.jsonのextentionsに記述した、拡張機能が入っているかを確認。 DEV CONTAINERに指定した、拡張機能が入っていることが確認できればOK 次回 GASをローカルから実行を行うには、GCPとの連携が必須のため、次回は、GCPとGASを連携を行います。 読んでいただき、ありがとうございます。 それでは、また次回の記事でお会いしましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker環境のJupyterのMatplotlibで日本語フォントを使用する

動機 タイトルがややこしい・・。最新の情報ではMatplotlibに日本語フォントがバンドルされているので、オプションを変更したら使えるよという記事などで試すも、うまく動作せず旧来の方法で普通にインストールすることにしました。 Exactryな記事がなかった気がするのでDockerコンテナで開発環境を整えていて、かつ簡単に解決したい方にお届けします。 ご注意 動作を確認したDockerイメージはcontinuumio/anaconda3なのでdebian系です。他のOSではフォントの格納場所の都合で動作しない可能性があります。報告いただくか、自分で試したら追記していきます。 Dockerファイルへの追記内容 以下をDockerfileのどこかに記載してください。Matplotlibはご自身でインストールしている前提となります。 # フォントインストール RUN wget https://fonts.google.com/download?family=Noto%20Sans%20JP -O /tmp/fonts_noto.zip && \ mkdir -p /usr/share/fonts &&\ unzip /tmp/fonts_noto.zip -d /usr/share/fonts 使い方 参考までにJupyterでの使用例を紹介します。 import matplotlib.pyplot as plt plt.rcParams['font.family'] = 'Noto Sans JP' plt.plot([1,2],label="ラベル") plt.title("日本語が使えます") plt.legend()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails+React+MySQL+nginx+puma on Docker

はじめまして。 初投稿です。 よろしくお願いします。 概要 Docker 自己紹介 初投稿なので軽く自己紹介します。 現在横浜の大学に通うB3で絶賛就活中です。 Qiitaの方々の記事に大変お世話になっているのでぜひ自分も書いて誰かの役に立てればと思い書いてみました。 誰かのtipsになれば幸いです。 承認欲求強めです。いいね、ストック、スター、フォローしてくれると飛び跳ねるくらい嬉しいです。 よろしくお願いします。 すぐはじめたい方へ 完成版 github clone後setup.shを実行すればコンテナが立ち上がります。 構成 バック version Ruby 2.6.2 Rails(API モード) 6.1.4 nginx 1.20.1 MySQL 5.7 フロント version Node.js 12.6.1 React 17.0.2 nginx 1.20.1 投稿時テーブルを上から持ってきたままになっておりバージョンの編集が行われておりませんでしたので編集を行いました。 ディレクトリ . ├── api │   ├── nginx │   │   ├── Dockerfile │   │   └── default.conf │   └── server │   ├── Dockerfile │   ├── Gemfile │   ├── Gemfile.lock │   ├── entrypoint.sh │   └── run.sh ├── frontend │   ├── Dockerfile │ └── run.sh │   ├── front │ │ ├ │   ├── nginx │   │   ├── Dockerfile │   │   └── default.conf ├── mysql │   ├── Dockerfile │   └── my.cnf ├── docker-compose.yml └── setup.sh ポートは以下のようです。 rails 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp api-nginx 0.0.0.0:8080->80/tcp, :::8080->80/tcp react 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp front-nginx 0.0.0.0:80->80/tcp, :::80->80/tcp db 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp ReactとRailsのデフォルトポートが3000でかぶるので、今回はReactのポートを8000にしました。 このポートって慣習とかルールってあるんですかね? qiitaの記事とか見ると色んな設定ですけど、分かる方がいらっしゃればぜひ教えてください?‍♂️ バック まずはバックを構築していきます。はじめにRailsのコンテナ作って、MySQLを作ってからnginx作ります。 Rails Gemfile Gemfile source 'https://rubygems.org' gem 'rails', '~> 6.1.4' Dockerfile Dockerfile FROM ruby:2.6.2 RUN apt-get update -qq && \ apt-get install -y build-essential libpq-dev nodejs vim RUN mkdir /code WORKDIR /code COPY Gemfile /code/Gemfile COPY Gemfile.lock /code/Gemfile.lock RUN bundle install COPY . /code COPY entrypoint.sh /usr/bin/ ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["bundle", "exec", "puma", "-C", "/code/config/puma.rb"] よく見る構成のDockerfileだと思います。 Dockerfileの最後のコマンドは、今回pumaを使用するのでこちらのような形式となっています。 -Cオプションの後ろで実行するファイルを指定します。 今回はルートディレクトリ下の/codeに諸々のアプリケーションファイルを置くので、このような指定としています。 Dockerfile内apt-get installの最後のvimはお好みで入っているだけなのでなくても動きます。 entrypoint.sh entrypoint.sh #!/bin/bash set -e rm -f /code/tmp/pids/server.pid exec "$@" 実行 run.sh #!/bin/bash cd /code rm -f /code/tmp/pids.server.pid bundle exec puma -C /code/config/puma.rb MySQL .env MYSQL_DATABASE=server_dev MYSQL_USER=docker MYSQL_ROOT_PASSWORD=DockerMysql1.0! MYSQL_PASSWORD=DockerMysql1.0! MySQLの環境変数設定です。 今回はユーザー名 docker、パスワード DockerMysql1.0!としてMySQLに接続します。 Dockerfile Dockerfile FROM mysql:5.7 EXPOSE 3306 COPY ./my.cnf /etc/mysql/conf.d/my.cnf CMD ["mysqld"] 特に注意する点はありません。 MySQLイメージを持ってきて、3306ポートを開け、confファイルをコンテナ内にコピーし、mysqldで起動します。 my.cnf [mysqld] explicit_defaults_for_timestamp = 1 character-set-server = utf8mb4 collation-server = utf8mb4_bin [mysql] default-character-set = utf8mb4 [client] default-character-set = utf8mb4 データベースの文字設定を行っています。 nginx(Rails側) Dockerfile Dockerfile FROM nginx:1.20.1 RUN rm /etc/nginx/conf.d/default.conf COPY default.conf /etc/nginx/conf.d CMD ["/usr/sbin/nginx", "-g", "daemon off;", "-c", "/etc/nginx/conf.d/default.conf"] Rails側のnginxコンテナです。 nginxイメージを持ってきて、/etc/nginx/conf.d下にあるデフォルトのconfファイルを削除し、これから作成するconfファイルを置きます。 その後nginxを起動します。 default.cnf default.cnf events { worker_connections 1024; } http { upstream rails { server qiita-rails:3000; } server { listen 80; server_name localhost; root /code/public; location / { try_files $uri @app; } location @app { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://rails; } } } nginxのconfファイルです。 今回はバックエンドでnginxコンテナに来たリクエストをRailsコンテナに流すので、こちらの設定を行います。 upstreem内でrailsコンテナ(これからdocker-compose.yml内で設定するサービス名qiita-railsの3000番ポートサーバー)を設定し、locationのproxy_passで流します。 docker-compose.yml docker-compose.yml version: "3.4" services: qiita-db: build: ./mysql/ container_name: qiita_db environment: MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_PASSWORD: ${MYSQL_PASSWORD} volumes: - db-data:/var/log/mysql healthcheck: test: ["CMD-SHELL", "mysqladmin ping --host=127.0.0.1 --user=${MYSQL_USER} --password=${MYSQL_PASSWORD} --port=3306"] ports: - "3306:3306" qiita-rails: build: ./api/server/ container_name: wt4u_api_rails command: ["./run.sh"] container_name: qiita_rails env_file: - ./api/server/.env volumes: - ./api/server/:/code/ - public:/code/public/ environment: TZ: Asia/Tokyo RAILS_ENV: development depends_on: qiita-db: condition: service_healthy ports: - "3000:3000" healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"] qiita-api-nginx: build: ./api/nginx/ container_name: wt4u_api_nginx volumes: - public:/code/public/ ports: - "8080:80" depends_on: qiita-rails: condition: service_healthy volumes: db-data: public: バック+DBまでのdocker-composeです。 特に変わった点はないですが、注意点としてqiita-db/qiita-rails両サービスにヘルスチェックを設定します。 depends_onで設定できるのはコンテナの起動順のみですので、コンテナ起動後アプリケーションが起動してから新たなコンテナを起動するように設定します。 起動順はdb→rails→nginxです。 DBの方はなくてもいけましたが、Railsのサービスの方はその後のnginxのほうが早く立ち上がり、リクエストを流す先がないエラーとなってしまうことを防ぐために設定しています。 内容としては、DBの方はmysqladminコマンドで接続できるかどうか、Railsの方はcurlコマンドでレスポンスが帰ってくるかどうかです。 詳細はDockerのドキュメントに書かれているのでこちらを参照してください。 コマンドだけでなく試行回数やインターバルなども設定できます。 またDBのデータを永続化するためvolumeを設定します。 そしてローカルの編集をコンテナへ反映させるためapi/serverをマウントします。これでコンテナ内でbundle installしたものなどが永続化できます。 さらにRailsコンテナとnginxコンテナでpublicディレクトリを共有するため同様にvolumeを作成します。 フロント 続いてフロント側のコンテナ達を作成します。 React Dockerfile FROM node:12.16.1 RUN apt-get update && \ apt-get install -y vim COPY run.sh / RUN mkdir /code WORKDIR /code EXPOSE 8000 CMD ["PORT=8000", "yarn", "start"] これもよくあるReactのDockerfileです。 注意点として、今回React側のポートを8000にするので、yarn startコマンドでポートを指定します。 run.sh #!/bin/bash cd /code PORT=8000 yarn start 上と同じくです。 nginx(React側) Dockerfile FROM nginx:1.20.1 RUN rm /etc/nginx/conf.d/default.conf COPY default.conf /etc/nginx/conf.d CMD ["/usr/sbin/nginx", "-g", "daemon off;", "-c", "/etc/nginx/conf.d/default.conf"] React側のnginxのDockerfileはRails側と同じです。 default.cnf events { worker_connections 1024; } http { upstream react { server qiita-react:8000; } server { listen 80; location / { proxy_pass http://react; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect off; } location /sockjs-node { proxy_pass http://react; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } } confもRails側と同じです。今回Reactのコンテナ名をqiita-reactとするのでupstreamで設定します。 docker-compose.yml docker-compose.yml version: "3.4" services: qiita-db: build: ./mysql/ container_name: qiita_db environment: MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_PASSWORD: ${MYSQL_PASSWORD} volumes: - db-data:/var/log/mysql healthcheck: test: ["CMD-SHELL", "mysqladmin ping --host=127.0.0.1 --user=${MYSQL_USER} --password=${MYSQL_PASSWORD} --port=3306"] ports: - "3306:3306" qiita-rails: build: ./api/server/ container_name: wt4u_api_rails command: ["./run.sh"] container_name: qiita_rails env_file: - ./api/server/.env volumes: - ./api/server/:/code/ - public:/code/public/ environment: TZ: Asia/Tokyo RAILS_ENV: development depends_on: qiita-db: condition: service_healthy ports: - "3000:3000" healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"] qiita-api-nginx: build: ./api/nginx/ container_name: wt4u_api_nginx volumes: - public:/code/public/ ports: - "8080:80" depends_on: qiita-rails: condition: service_healthy qiita-react: build: ./frontend/ container_name: qiita_react volumes: - ./frontend/front/:/code/ ports: - "8000:8000" command: ["/run.sh"] healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8000 || exit 1"] qiita-front-nginx: build: ./frontend/nginx/ container_name: qiita_front_nginx ports: - "80:80" depends_on: qiita-react: condition: service_healthy volumes: db-data: public: docker-composeの最終的な構成です。 フロント側もnginxを立てるので同様にヘルスチェックを行い起動を確認後nginxのコンテナを起動します。 RailsもReactもコンテナ内の/codeディレクトリにコードを一式置きます。 volumesの設定でマウントしているので、コンテナ内のbundle(yarn) installやローカルでの編集が同期されます。 立ち上げ Build $ docker-compose build Rails $ docker-compose run qiita-rails rails new . --api -d mysql --force --path vendor/bundle Railsのアプリケーションを新規作成します。 今回はRailsをAPIモードで使用し、データベースにMySQLを使用するのでオプションで指定します。 またbundle installしたgem達を永続化したいので、/code/vendor/bundleにインストールしvolumeの対象となるようにするため--pathオプションで指定します。 Rails DB接続情報 database.yml default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV.fetch("MYSQL_USER") %> password: <%= ENV.fetch("MYSQL_PASSWORD") %> host: qiita-db development: <<: *default database: <%= ENV.fetch("MYSQL_DATABASE") %> RailsのDB接続情報を設定します。 hostにDBのコンテナ名を指定します。 React $ docker-compose run qiita-react npx create-react-app . npx create-react-appでReactアプリケーションを作成します。 起動 $ sudo chmod -R a=rx,u+wx . $ docker-compose up --build dockerがsudo権限で実行されている場合、コンテナ内で作成されvolumeでローカルにマウントされたRails、Reactアプリケーションファイルには実行権限がありません(でした)。 ユーザーに実行権限を設定するため1つ目のコマンドを実行します。 最後にdocker-compose up --buildを実行することですべてのコンテナたちが立ち上がります。 localhostの3000、8080ポートでRailsのトップが、8000、80ポートでReactのトップページが表示されます。 最後に 拙い文章でしたが最後までお読みいただきありがとうございます。 修正/加筆依頼/アドバイスは大歓迎です! qiitaでもissueでもぜひお願いします。 ちょこちょこ記事書きたいと思っているのでぜひよろしくお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerignoreの書き方

.dockerignore に記載のあるファイルはコンテキスト(docker イメージ構築に必要な素材・内容物)から除外されます。 ルール "#"で始まる行はコメントとみなされます。 除外したいファイルやディレクトリの名前を書きます。 # この行はコメント # root以下のあらゆるサブディレクトリ内でtempで始まる名前やファイルの除外 # ex. /somedir/temporary.txt, /somedir/tempを除外 */temp* # rootから2階層以下のサブディレクトリ内で、 temp で始まる名前のファイルやディレクトリを除外 # ex. /somedir/subdir/temporary.txt を除外 */*/temp* # ルートディレクトリ内でtempと1文字が一致する名前のファイルやディレクトリを除外 # ex. /tempa,/tembを除外 temp? 例外 "!"で始まる行は除外対象の例外として指定されます。 ただし、他の行をみて矛盾がある場合には最終行のほうが優先されます。 例 1 !README*.md README-secret.md README-secret.md が!README*.md より後にあるので、README-secret.md は除外対象となり、README で始まる md ファイルは除外対象にはなりません。 例 2 README-secret.md !README*.md !README*.md が README-secret.md より後にあるので、README-secret.md も含む README で始まる md ファイルはすべて除外対象となりません。 .dockerignore ファイルを使うと Dockerfile と.dockerignore ファイルも除外対象にすることができます。除外設定しても、構築処理で必要なためデーモンに送信されますがADD命令とCOPY命令でイメージにコピーされません。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Desktop と Docker CE の差異をメモる

はじめに Windows + WSL環境にてDocker DesktopからDocker CEへ移行した場合、元々WSL内でしか操作利用していなかったとしても幾つかの挙動が存在し、Docker Desktopは便利にできていたと実感することがあるので、できなくなってしまったこととその代替プランをメモとして残します。 loopbackアドレスのマッピング Docker Desktop 127.0.0.1~127.255.255.255までアドレスをマップできる Docker CE 127.0.0.1しかマップできない Docker Destopは127.0.0.0/8が使える。127.1.1.1等もDocker Desktopの中継プロセスがWindowsホストにマッピングしてくれる。 Docker CEも127.1.1.1をマップするとホストにあたるWSLインスタンスにマッピングしてくれる。しかし、WSLからWindowsホストへのマッピングは127.0.0.1のみなのでWindowsホストにはマップされない。 代替プラン 予め決めておいたIPv6 ユニークローカルアドレスをWSLインスタンスに割り振り、そのアドレスに対してマッピングする。 参考) WSL2のUbuntuに固定のIP(IPv6)アドレスを複数つけてみる host.docker.internal Docker Desktop Windowsホスト(デフォルトでは192.168.65.2)を指す。 Docker CE host.docker.internal:host-gateway が代替するが、WSLインスタンスを指す。 Docker Desktopでは、host.docker.internalという特別なドメインでWindowsホストに中継する。しかも中継プロセスがローカルアクセスとして中継するためにファイヤーウォールの影響を受けない。 Docker CEにもでhost.docker.internalの代替として、host-gatewayというキーワードでextra_hostsに登録する手法が提供されたが、ここでいうホストとはWSLを指すのでDocker Desktopの概念とは意味が異なる。 Visual Studio Code PHPDebug拡張を利用するケース Remote WSL を使用してソースツリーにアクセスする場合、vscodeがオープンしたWindowsホストのlocalhot:9003をWSLのlocalhost:9003にポートプロキシを行う。 Windows localhost:9003 WSL localhost:9003 この場合 Docker Desktop host.docker.internal = Windowsの9003へ接続*可能* Docker CE host.docker.internal = WSLの9003へ接続*可能* となりどちらも接続可能となる。 Remote Containerを使う場合でも、WSL経由のリモートコンテナへの接続に関しては双方ともに問題無い。 ただし、vscodeの中継プロセスはコンテナ内で起動するのでWindowsとコンテナ内で9003がlistenされる。 Windows localhost:9003 WSL N/A Container localhost:9003 この場合、Remote WSLとは異なり Docker Desktop host.docker.internal = Windowsの9003へ接続*可能* Docker CE × host.docker.internal = WSLの9003へ接続*不可能* Docker CE 〇 localhost = Containerの9003へ接続*可能* Docker Desktopではどちらも host.docker.internal でよかった設定がDocker CEではどちらのリモート機能を使うかによって設定を切り替える必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python] Docker+Python+Seleniumでスクレイピング

はじめに 前回の記事でDockerコンテナでPython環境を作成しSeleniumを操作してみました。 今回はWebページのフォームで値を入力し、その結果の表示確認をしてみます。 SeleniumでWebページを操作する基本のような部分を備忘録として残すために記載したので見づらいかもしれませんが、そこはご了承ください。 実施すること 今回は下記の項目を実施してみようと思います。 1. Seleniumを使用しChromeを起動する。 2. Webページを表示する。 3. ページ内のボタン要素をクリックし、ページ遷移する。 4. 遷移後のページで必要な要素が表示されるまで待機する。 5. ページのp要素からテキストを取得する。 6. ページ内のボタン要素をクリックし、ダイアログボックスを表示させる。 7. ダイアログボックス内のinput要素に値を入力する。 8. ダイアログボックス内のdropdownメニューから値をセレクトする。 9. ダイアログボックス内のボタン要素をクリックし、データを送信する。 10. 送信完了のアラートが表示されるまで待機する。 11. アラート内のOKボタンをクリックする。 12. ページ内の情報を更新するため、ページのリロードを行う。 13. リロード後のページで必要な要素が表示されるまで待機する。 14. ページのp要素からテキストを取得する。 DockerでPythonのSelenium環境を構築する方法については前回記事([Python] Dockerコンテナでseleniumを使ってみる)を参考にしてみてください。 Seleniumを使ってみる 実装コード 今回実装したコードはこちらになります。 docker_selenium.py import time from selenium import webdriver from selenium.webdriver.common.alert import Alert from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # webのフォームに入力する内容を変数に格納 MILAGE = 38.38 ELEVATION = 218 WEATHER = ['晴れ', '曇り', '雨'] # webdriverのオプションを設定 options = webdriver.ChromeOptions() options.add_argument('--headless') print('Connect remote browser...') driver = webdriver.Remote(command_executor='172.21.0.3:4444/wd/hub', options=options) print('remote browser connected...') try: # ブラウザでWebページを開く driver.get('https://xxxxxxxx.herokuapp.com/') print('current URL: ', driver.current_url) # ボタンをクリックしページ遷移する処理 bicycle_button = driver.find_element(By.XPATH, '//input[@id="button_bicycle"]') bicycle_button.click() print('current URL: ', driver.current_url) # ページの読み込みのための最大待ち時間 print('Max 5 secound wait...') wait = WebDriverWait(driver, 5) wait.until(EC.visibility_of_element_located((By.XPATH, '//p[@id="sum_milage"]'))) # 新規データ入力前の今月の走行距離のテキストを取得する bicycle_page_message_2 = driver.find_element(By.XPATH, '//p[@id="sum_milage"]').get_attribute('textContent') print('bicycle_page_message_2 before: ', bicycle_page_message_2) # データをインプットするためのダイアログを開く処理 input_dialog_button = driver.find_element(By.XPATH, '//*[@id="button_to_input_dialog"]') input_dialog_button.click() print('input dialog open...') # ページの読み込みのための待ち時間 print('3 secound wait...') time.sleep(3) # 走行距離を入力するinputタグ要素を取得 input_milage = driver.find_element(By.XPATH, '//*[@id="input_milage"]') # 入力内容をクリア(何も入力されていないけど念のため) input_milage.clear() # 入力内容をセット input_milage.send_keys(MILAGE) # 獲得標高を入力するinputタグ要素を取得し、値を入力 input_elevation = driver.find_element(By.XPATH, '//*[@id="input_elevation"]') input_elevation.clear() input_elevation.send_keys(ELEVATION) # ドロップダウン要素を取得する dropdown = driver.find_element(By.XPATH, '//*[@id="select_weather"]') # ドロップダウン要素からSelectオブジェクトを作成する select_weather = Select(dropdown) # ドロップダウンの値を表示されているテキストを選択して記入する select_weather.select_by_visible_text(WEATHER[0]) # DBへの入力(Input)ボタンをクリックする処理 input_button = driver.find_element(By.XPATH, '//*[@id="button_input_to_db"]') input_button.click() print('Input button click...') # データが送信されalert表示のための最大待ち時間 print('Max 10 secound wait...') alert_wait = WebDriverWait(driver, 10) alert_wait.until(EC.alert_is_present()) # アラートのokをクリックする処理 Alert(driver).accept() print('alert ok button click...') # ページのリロード driver.refresh() print('reload page...') # ページの読み込みのための最大待ち時間 print('Max 5 secound wait...') wait = WebDriverWait(driver, 5) wait.until(EC.visibility_of_element_located((By.XPATH, '//p[@id="sum_milage"]'))) # 新規データ入力後の今月の走行距離のテキストを取得する bicycle_page_message_2 = driver.find_element(By.XPATH, '//p[@id="sum_milage"]').get_attribute('textContent') print('bicycle_page_message_2 after: ', bicycle_page_message_2) except Exception as e: print('Exception: ', e) finally: # リモートサーバーとの接続を終了する print('Connection stop...') driver.quit() 都合上、WebサイトのURLを一部非表示にしています。 try,except,finally文についてはドライバーを確実に終了させる目的で記載しましたので正確な位置に記述されていないかもしれません。 おかしな点についてはご指摘等いただければありがたいです。 それでは各項目の処理について詳細を見てみます。 XPathについて 各項目の処理を見る前に、Seleniumで要素を指定する際によく使用されるXPathについて解説します。 XPathはXML文章中の要素、属性値などを指定するための言語で、XML文章をツリーとして捉えることで、要素や属性の位置を指定します。 要素の指定はロケーションパスを指定する方式を使用し、属性の指定には@マークを使用します。 また、HTMLもXMLの一種とみなすことができるため、XPathを使ってHTML文章中の要素を指定することができます。 ● 記述方法 対象の要素にid, class等の属性がある場合と無い場合で若干記述の仕方が変わります。 対象の要素にid, class等の属性がある場合 //p[@id="sum_milage"] 対象の要素を指定する形で記述します。 分解して内容を見てみると、 // : ノードpathを記述する。(「//」はルートからのpathの省略形) p : 要素の種類を記述 @id : 「@」で要素の属性を指定する。classを指定する時は@class="" ノードpathはhtmlからのフルパスで記述しても問題ありません。 → 例). html/body/div/p 対象の要素に属性がない場合 //*[@id="top_block"]/p 対象の要素に属性がない場合はその要素の親要素を指定し、その親要素の配下にある〇〇要素という形で記述します。 同じくこの要素を分解すると、 * : 全ての要素を選択 [@id=""]: 対象要素の親要素を指定する /p : 「/」の前で取得した要素の配下の要素を取得している 今回の処理では全ての要素の中からidがtop_blockの要素を取得し、その配下のpタグ要素を取得しています。 同じタグ要素が複数ある時はリスト形式で表現します。 → 例) //*[@id="top_block"]/p[3] ● Chromeの検証ツールからコピーする方法 XPathは下記手順でChromeの検証ツールからコピーしてくることも出来ます。 1. 対象ページの開発者ツールのElementを確認する 2. 必要な要素を選択する 3. 右クリックでコンテキストメニューを開く 4. Copy → Copy XPATHを選択する 5. エディタに貼り付ける 比較的簡単にコピーできるし、ノードpathや属性のスペルミスも無くなるのでこの方法も便利です。 XPathの他のメソッドについてはこちらのサイト(クローラ作成に必須!XPATHの記法まとめ)を参考にしてみてください。 それでは実際の処理を見てみます。 webページを表示する 正確にはブラウザドライバーでリクエストしたwebページのレスポンスを取得する処理です。表現の都合上、表示と記載します。 抜粋 from selenium import webdriver options = webdriver.ChromeOptions() driver = webdriver.Remote(command_executor='172.21.0.3:4444/wd/hub', options=options) # ブラウザでWebページを開く driver.get('https://xxxxxxxx.herokuapp.com/') ライブラリからseleniumのwebdriverをimportします。 オプションでどのブラウザを起動するか指定します。今回はChromeドライバーを設定。 別コンテナに作成しているリモートサーバーのオブジェクトを生成し、get()メソッドにURLを引数として渡してあげればWebページを表示できます。 ボタン要素をクリックする 赤枠内のボタンをクリックする処理です。 抜粋 bicycle_button = driver.find_element(By.XPATH, '//input[@id="button_bicycle"]') bicycle_button.click() ボタン要素をクリックするには、find_element()メソッドで対象要素のオブジェクトを生成し、click()メソッドを呼び出します。 要素からテキストを取得する 赤枠内のp要素から青文字のテキストを取得する処理です。 抜粋 bicycle_page_message_2 = driver.find_element(By.XPATH, '//p[@id="sum_milage"]').get_attribute('textContent') 要素のテキストを取得するにはfind_element()メソッドで対象要素のオブジェクトを生成し、get_attribute()メソッドを呼び出します。 他にも.textでテキストを取得できるようですが、属性で非表示に指定されている場合(display:none 等)は取得できないようなので、属性に関係なくテキストを取得できるget_attribute()メソッドが良いかと思います。 input要素に値を入力する 画像の赤枠内の①と②に値を入力する処理です。 抜粋 # 入力したい値を変数に格納 MILAGE = 38.38 ELEVATION = 218 # ①の処理 # 走行距離を入力するinputタグ要素を取得 input_milage = driver.find_element(By.XPATH, '//*[@id="input_milage"]') # 入力内容をクリア(何も入力されていないけど念のため) input_milage.clear() # 入力内容をセット input_milage.send_keys(MILAGE) # ②の処理 # 獲得標高を入力するinputタグ要素を取得し、値を入力 input_elevation = driver.find_element(By.XPATH, '//*[@id="input_elevation"]') input_elevation.clear() input_elevation.send_keys(ELEVATION) ①と②は同じ処理をしています。 input要素に値を入力するにはfind_element()メソッドで対象要素のオブジェクトを生成し、send_keys()メソッドを呼び出し、引数に入力したい値を渡し実行すれば値の入力が出来ます。 input要素に値がセットされている場合には事前にclear()メソッドで値をクリアしますが、値が入力されていない要素でもバグ防止のため実施しておいた方が無難かと思います。 dropdownメニューで項目を選択する 画像の赤枠内③のdropdownメニューで項目を選択する処理です。 抜粋 # 必要なライブラリのimport from selenium.webdriver.support.select import Select # 入力したい値を変数に格納 WEATHER = ['晴れ', '曇り', '雨'] # ドロップダウン要素を取得する dropdown = driver.find_element(By.XPATH, '//*[@id="select_weather"]') # ドロップダウン要素からSelectオブジェクトを作成する select_weather = Select(dropdown) # ドロップダウンの値を表示されているテキストを選択して記入する select_weather.select_by_visible_text(WEATHER[0]) dropdown要素で値を選択するには、ライブラリからSelectクラスをimportしSelectインスタンスを作成します。 Selectインスタンスで値を選択する方法はいくつかありますが、今回はdropdownメニューの項目に表示されているテキストで指定する方法でコーディングしています。 Selectクラスに用意されているselect_by_visible_text()メソッドに選択したいメニューのテキストを渡してあげればその項目が選択された状態になります。 他の方法についてはこちらのサイト(Selenium API(逆引き)・・・Selenium APIを利用目的から検索できます)を参考にしてみてください。 Alertの「OK」ボタンをクリックする 画面に表示されるAlertのオブジェクトを取得し「OK」ボタンをクリックする処理です。 スクショは取れなかったので画像はありません。 抜粋 from selenium.webdriver.common.alert import Alert # データが送信されalert表示のための最大待ち時間 alert_wait = WebDriverWait(driver, 10) alert_wait.until(EC.alert_is_present()) # アラートのokをクリックする処理 Alert(driver).accept() ライブラリからAlertクラスをimportします。 Alertインスタンスを生成する際にdriverオブジェクトを渡し、accept()メソッドを実行すればOKボタンのクリックが完了します。 またAlertが画面に表示されるのを待つ処理について、Alertの表示確認にはalert_is_present()メソッドを使用します。 最大待ち時間を指定して処理を止める ページ内の要素の読み込みやAlertの表示を待ったりと、ページ内処理を待つ際に使えるメソッド。 抜粋 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 5) wait.until(EC.visibility_of_element_located((By.XPATH, '//p[@id="sum_milage"]'))) まずライブラリからWebDriverWaitとexpected_conditionsをimportします。 expected_conditionsをECとするのはSeleniumのお作法みたいですね。 WebDriverWaitインスタンスを作成し、waitオブジェクトを作成します。その際、第二引数に処理が完了するまでの「最大」待ち時間を指定します。 そしてオブジェクトの持っているuntil()メソッドで望む状態になっているか確認します。 until()メソッドの引数には、expected_conditionsのメソッドでXPathで指定した要素に対する期待動作を記述します。 今回はvisibility_of_element_located()メソッドで、指定要素が表示されているかを確認しています。 XPathで指定した要素が画面に表示されているのが確認できたらuntil()メソッドは正常終了し次の処理に移ります。 もしWebDriverWaitインスタンスを作成する際に指定した最大待ち時間を過ぎても指定要素が画面に表示されなかったらExceptionが返されます。 *標準ライブラリtimeのsleep()との違いは、処理の完了が確認できれば最大時間待つことなく次の処理に移ることです。 ページをリロードする 抜粋 driver.refresh() ページをリロードするには、driverオブジェクトのrefresh()メソッドを使用します。 実行してみる 実際に処理が動作するか、コードを実行して確認してみます。 まずターミナル上で確認してみます。 root@9f672ec558d8:/work# python3 docker_selenium.py Connect remote browser... remote browser connected... current URL: https://xxxxxxxx.herokuapp.com/ current URL: https://xxxxxxxx.herokuapp.com/bicycle_contents Max 5 secound wait... bicycle_page_message_2 before: 今月は 2036.59 km 走っています。 input dialog open... 3 secound wait... Input button click... Max 10 secound wait... alert ok button click... reload page... Max 5 secound wait... bicycle_page_message_2 after: 今月は 2074.97 km 走っています。 Connection stop... bicycle_page_message_2のbefore, afterで走行距離が変化していますね。 その他の処理も問題なく動いているようです。 次にデータベース(MySQL)のテーブル内のデータも実行前後で確認してみます。 実行前 MySQL | 12044 | 2021-10-29 | 22.75 | 318 | 71 | 224 | | 12054 | 2021-10-29 | 22.27 | 173 | 71 | 224 | +-------+---------------------+--------+-----------+------------+-----------+ 1197 rows in set (9.14 sec) mysql> 実行後 MySQL | 12044 | 2021-10-29 | 22.75 | 318 | 71 | 224 | | 12054 | 2021-10-29 | 22.27 | 173 | 71 | 224 | | 12064 | 2021-10-30 | 38.38 | 218 | 71 | 224 | +-------+---------------------+--------+-----------+------------+-----------+ 1198 rows in set (9.16 sec) mysql> rowの数が1つ増え、新しい日付のデータが追加されています。 送信ボタンがタップすることでデータが送信されてテーブルに反映されていることが確認できますね。 最後に画面上でも確認してみます。 赤枠内の青文字のp要素のデータが更新されていることが確認できました。 ページをリロードする処理が正常に動作し、新しいテキストデータを取得できることが確認できました。 おわりに たぶん今回試してみたことが出来ればSeleniumでスクレイピングやテストを行うための基本的な部分はカバーできるかと思います。 あとは記事としてアップするか分かりませんが、マウスオーバーに対する処理やテスト時のUIチェックのための動画撮影等は応用的な感じになるかと思いますが、応用可能だと思います。 ご覧いただきありがとうございました。 参考サイト クローラ作成に必須!XPATHの記法まとめ XPathの記法について詳しく記載されています。 要素の指定の際、かなり参考になりました。ありがとうございます。 Selenium API(逆引き)・・・Selenium APIを利用目的から検索できます seleniumの画面操作について細かく記載されていて、かなり参考になりました。ありがとうございます。 現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル Udemy, Youtube, Twitterで活躍中の酒井潤さんのpython入門。 内容が濃くかつ教え方が丁寧なのですごく分かりやすいです。 pythonについて学びたい方は一度は見た方が良いお勧めの動画です。 pythonの実装で迷った時やコーディングスタイルで迷った時はいつもこの動画で動画で復習しています。ありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1 mac + laradockのapache2とMySQLエラー回避(おまけXdebug3設定)

M1 macでのlaradockの問題点 仕事でlaravelを使用しており、ローカル開発環境としてlaradockをIntel macで使用していましたが、最近M1 macに移行したところ、laradockでいくつか問題点があったので回避策を調べました。 問題1 apache2が起動しない まず、M1ではapache2のコンテナが起動しません。 方法 /apache2/Dockerfileを修正 // 1行目を修正 FROM --platform=linux/x86_64 webdevops/apache:ubuntu-18.04 // 最後の行に下記を追加 RUN wget -O "/usr/local/bin/go-replace" "https://github.com/webdevops/goreplace/releases/download/1.1.2/gr-arm64-linux" \ && chmod +x "/usr/local/bin/go-replace" \ && "/usr/local/bin/go-replace" --version ちなみにnginxはM1でも普通に動きます 問題2 MySQLが起動しない 同様にMySQLも起動しません。 ネットで調べるといろいろ情報がありますが、自分の場合は下記の変更だけで起動しました。 方法 /mysql/Dockerfileを修正 // 2行目を修正 FROM --platform=linux/x86_64 mysql:${MYSQL_VERSION} これで起動しない場合は、.envのDATA_PATH_HOSTで指定しているディレクトリを削除してください。 おまけ Xdebug3の設定 PHPのバージョンによっては(PHP8.0など)Xdebugのバージョン3がインストールされますが、設定のパラメータ名がバージョン2から変わっており、うまくPhpStormでステップ実行ができませんでした。 方法 /php-fpm/xdebug.iniと/workspace/xdebug.iniの最後の行に下記を追加 xdebug.client_host="host.docker.internal" xdebug.discover_client_host=0 xdebug.client_port=9000 xdebug.mode=debug xdebug.start_with_request=yes xdebug.output_dir="~/xdebug/phpstorm/tmp/profiling" .envのWORKSPACE_INSTALL_XDEBUGとPHP_FPM_INSTALL_XDEBUGをtrueに変更してください。 PhpStormでステップ実行すると、ポート周りのワーニングが出ますが、一応ステップ実行はできます。 おまけ2 環境構築のコマンド一覧 コンテナビルド LAMPとredis、phpmyadmin、redis-webuiを構築 docker-compose build --no-cache apache2 php-fpm mysql workspace redis phpmyadmin redis-webui コンテナ起動 docker-compose up -d apache2 mysql workspace redis phpmyadmin redis-webui サーバーの中からlaravelのコマンドやマイグレーションをしたい時 docker-compose exec workspace bash サーバーにsshで入ったような時のような感じで操作できます。 コンテナ停止 docker-compose stop コンテナ削除 docker-compose down phpmyadminへのアクセス(デフォルト設定) localhost:8081 サーバー:mysql ユーザー:root パスワード:root redis-webuiへのアクセス(デフォルト設定) localhost:9987 認証ID:laradock 認証PW:laradock コンテナ再構築 エラーやPHPバージョン変更などでコンテナを再構築するときは、自分は下記のように一旦コンテナを削除してから再構築してます。 もっと簡略化できる場合もあります。 docker-compose down docker-compose build --no-cache apache2 php-fpm mysql workspace redis phpmyadmin redis-webui docker-compose up -d apache2 mysql workspace redis phpmyadmin redis-webui うまくいかない時 上記でも書きましたが、うまくいかない時は.envのDATA_PATH_HOSTで指定しているディレクトリを一旦削除してから、buildすると直る場合があります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む