77Lifeworkベータ版

77Lifeworkベータ版

IT関係の話をメインとして、その他私の趣味や雑記用のブログです。ここに書いた内容が少しでも参考になれば嬉しいです。

スプレッドシートでTodoリスト作成(GAS)

はじめに

今回はGoogleスプレッドシートとGAS(Google Apps Script)を使って簡単なTodoリストを作成してみます。
正直、「すぐにTodoリストを使いたい」という目的ならば自作ではなく、世の中に出ているアプリを使うのが手っ取り早いです。
が、今回はGASの使い方を学ぶ意味で自分で作成してみました。

Todoリストの機能

以下のようなイメージのシートを作成します。シート1が未完了のタスク、シート2が完了済みのタスクを記載するシートとなっています。
f:id:J-back:20200903120550p:plain:w600
f:id:J-back:20200903120611p:plain:w600


シート1のタスクの中で、完了したタスクの行のチェックボックスにチェックを入れると、完了したタスクの行がシート1からシート2へ転記されます。
シート2の「完了日」にはチェックを入れた日(つまり、本日日付)が入るようにしています。
詳細な動きについては以下動画をご覧ください。


GASでTodoリスト作成


コード

onEdit(e)を使って、スプレッドシート編集時に処理が走るようにしています。
さらに、編集されたセルが「1列目」かつ「チェックボックスにチェックが入っていない状態からチェックが入っている状態になった」場合のみ転記操作が実行されます。

function onEdit(e) {
  
  if ((e.range.getColumn() == 1) && (e.oldValue == 'false') && (e.value == 'TRUE')) {
    
    // 操作中のシート取得
    var operationSheet = SpreadsheetApp.getActiveSheet();
    var destinationSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート2')
    var sourceRow = e.range.getRow();
    var sourceData = operationSheet.getRange(sourceRow + ':' + sourceRow).getValues();    
    var lastRow = destinationSheet.getLastRow();
    var destinationRow = lastRow + 1;
    
    // 現在日付を書き込み
    sourceData[0][0] = Utilities.formatDate(new Date(), 'JST', 'yyyy/MM/dd');
 
    destinationSheet.getRange(destinationRow + ':' + destinationRow).setValues(sourceData);
    operationSheet.deleteRow(sourceRow);
    
  }
}

おわりに

今回は以上です。GASもいろいろな場面で使えそうですね。

ApacheとTomcatを連携させる方法

はじめに

ここでは、Linuxサーバ(CentOS)上にインストールしたApacheTomcatを連携させ、Apacheで受けた通信をTomcatに転送する方法を書いていきます。

構成情報

CentOS 7.7(1908)
Apache 2.4.43
Tomcat 9.0.34

Apacheの設定

Apache側で転送用のモジュールを有効化します。ここではApacheのインストールディレクトリが「/opt/httpd-2.4.43」である場合について書いていますので、適宜読み替えてください。

まずは「/opt/httpd-2.4.43/conf/httpd.conf」をテキストエディタで開き、以下が記述されている行のコメントアウトをはずして有効化します。

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so

f:id:J-back:20200503204646p:plain:w600

次に、Apacheが受けたリクエストの転送先を設定をするため「/opt/httpd-2.4.43/conf/extra」ディレクトリの「httpd-proxy.conf」を編集します。
「extra」ディレクトリや「httpd-proxy.conf」ファイルがない場合は作成しましょう。
httpd-proxy.conf」をテキストエディタで開き、以下を追記して保存します。
以下のように記載すると、Apacheへの全てのリクエストをajpによって192.168.1.12のポート8009に転送できます。

ProxyPass / ajp://localhost:8009/

このファイルをApacheに読み込ませるために「/opt/httpd-2.4.43/conf/httpd.conf」をテキストエディタで開き、以下を追記します。

Include conf/extra/httpd-proxy.conf

f:id:J-back:20200503213602p:plain:w600

以上でApache側の設定は終わりです。

Tomcatの設定

ここでのTomcatのインストールディレクトリは「/opt/tomcat-9.0.34」として進めます。
「/opt/tomcat-9.0.34/conf/server.xml」をテキストエディタで開き、以下から始まる記述を探します。

<Connector protocol="AJP/1.3"・・・

ここで指定されているポートが「port="8009"」となっていることを確認しましょう。
上記がコメントアウトされている場合、コメントアウトをはずして有効化しておきます。
f:id:J-back:20200503220138p:plain:w600

ここまでできたらTomcatApacheをそれぞれ再起動します。
ブラウザから80番ポートへアクセスするとTomcatへ転送されるようになります。

しかし、この状態でブラウザからアクセスして表示される画面はエラーとなってしまいます。
f:id:J-back:20200503220625p:plain:w600

エラーとなる原因を調べるため、Apacheのログファイルを見てみます。
「/opt/httpd-2.4.43/logs」にある「error_log」を開きましょう。
Connection refusedとなっていて、ajpでうまく転送できていないようです。

AH00957: AJP: attempt to connect to 192.168.1.12:8009 (192.168.1.12) failed
AH00896: failed to make connection to backend: 192.168.1.12

Tomcatの起動ログを確認してみます。「/opt/tomcat-9.0.34/logs/catalina.out」を開くと、以下のようにajpに関するメッセージが出ています。

The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "". This combination is not valid.

secretRequiredをfalseにすることで対応します。
「/opt/tomcat-9.0.34/conf/server.xml」をテキストエディタで開き、ajpのConnectorの部分に「secretRequired="false"」を追記します。
f:id:J-back:20200504005112p:plain:w600

Tomcatを再起動後、ブラウザで80番ポートにアクセスすると、Tomcatの画面が表示されました。
f:id:J-back:20200504005249p:plain:w600

これでApacheからTomcatへの通信転送設定は完了です。

Tomcatを複数起動(複数インスタンスを構成)する

(2020/5/4更新)

はじめに

今回はLinux(CentOS)上にTomcatを複数インストールし、それぞれ独立したインスタンスとして起動する方法を書いていきます。
ここではTomcatを2つ起動し、それに合わせてApacheも2つ起動した上でTomcatへのアクセスはApacheを介するよう設定するので、1つのサーバにApacheおよびTomcatインスタンスが2つずつ起動している状態とします。

アクセスする時のイメージは下図です。
f:id:J-back:20200320175724j:plain:w600

80番ポートでアクセスした場合は1つ目のApacheインスタンスを経由して1つ目のTomcatインスタンスが応答、8080番ポートへアクセスした場合は2つ目のApacheインスタンスを経由して2つ目のTomcatインスタンスが応答、といった感じです。

この記事では最終的にApacheTomcatへの連携をするために、Apacheインスタンスが2つ起動していることを前提としていますが、連携が必要ないならばTomcatのみのインストールでも大丈夫です。

Apacheインスタンスを2つ起動する方法については、以下の記事を参考にしてください。
www.77-lifework.com

構成情報

CentOS 7.7(1908)
Apache 2.4.43
・Open JDK 11.0.7
Tomcat 9.0.34
・ポート番号
 インスタンス1:80
 インスタンス2:8080

Open JDKのインストール

Tomcatを動作させるため、まずはJavaの動作環境としてOpen JDKをインストールしましょう。
以下コマンドを実行します。

yum -y install java-11-openjdk
yum -y install java-11-openjdk-devel

上記を実行後、以下のコマンドでインストールされていることを確認しましょう。

java -version

f:id:J-back:20200503154546p:plain:w600


また、Java環境変数も設定しておきましょう。
「/etc/profile」をテキストエディタで開き、以下を追記します。

export JAVA_HOME=/usr/lib/jvm/java-11-openjdk

追記後、以下を実行して環境変数を反映、確認しておきましょう。

source /etc/profile
echo $JAVA_HOME

Tomcatのインストール

次にTomcatをインストールしていきます。
以下のページを開き、tar.gz形式を選択してダウンロードします。
基本的にその時点での最新バージョンで良いと思います。
以下、「tomcat-x.x.x」はインストールするバージョンに合わせて読み替えてください。
tomcat.apache.org

f:id:J-back:20200503155041p:plain:w600

ダウンロードしたファイルをscp等でLinuxマシン上の「/tmp」に配置します。
(Linuxマシンで直接tomcatのページにアクセスしてダウンロードしても大丈夫です)

以下コマンドで解凍し、「tomcat-9.0.34」というディレクトリ名で「/opt」以下に配置しましょう。

cd /tmp
tar -zxvf apache-tomcat-9.0.34.tar.gz
cp -R apache-tomcat-9.0.34 /opt/tomcat-9.0.34

また、2つ目のインスタンス用として、解凍したディレクトリをもう1つ別名で配置します。

cp -R apache-tomcat-9.0.34 /opt/tomcat-9.0.34-ex

これで「/opt」に2つのTomcatディレクトリが存在する状態となりました。
f:id:J-back:20200503160345p:plain:w600


ここから、Tomcatが使用するポート番号を変更していきます。
まずは1つ目のインスタンスについて、「/opt/tomcat-9.0.34/conf/server.xml」をエディタで開き、以下のように、8005、8080番ポートが記載されている箇所を探します。
f:id:J-back:20200503161507p:plain:w600
f:id:J-back:20200503161613p:plain:w600

これらをそれぞれ18005、8081番ポートへ変更します。
とくに、8080番ポートについては今回構築している環境の2つ目のApacheインスタンスで使用しているため、変更の必要があります。

指定するポート番号(18005、8081)については、他のプロセスと重複していないことをあらかじめ確認しておきましょう。
以下のコマンドで指定したポート番号が他のプロセスで使用されているか確認することができます。

lsof -i:ポート番号

f:id:J-back:20200503161849p:plain:w600


また、TCPUDPで使用されているポートは以下のコマンドでも確認できます。

ss -anut

f:id:J-back:20200503162010p:plain:w600

18005、8081番ポートが他プロセスで使用されていないことを確認できたら、server.xmlを編集します。
8005→18005、8080→8081と変更します。
f:id:J-back:20200503162225p:plain:w600
f:id:J-back:20200503162244p:plain:w600


ここまで設定できたらTomcatの起動設定をしていきます。
まずは以下を実行して、Tomcat用のユーザを作成し、Tomcatインストールディレクトリの権限を変更しておきます。

useradd -s /sbin/nologin tomcat
chown -R tomcat:tomcat /opt/tomcat-9.0.34

次に、Tomcatをサービスとして登録し、systemctlコマンドから起動・停止などといった制御をできるようにします。
「/etc/systemd/system」に「tomcat9.service」というファイルを作成し、中身を以下のように記述しましょう。

[Unit]
Description=Apache Tomcat 9
After=syslog.target network.target

[Service]
User=tomcat
Group=tomcat
Type=oneshot
PIDFile=/opt/tomcat-9.0.34/tomcat9.pid
RemainAfterExit=yes

ExecStart=/opt/tomcat-9.0.34/bin/startup.sh
ExecStop=/opt/tomcat-9.0.34/bin/shutdown.sh
ExecReStart=/opt/tomcat-9.0.34/bin/shutdown.sh;/opt/tomcat-9.0.34/bin/startup.sh

[Install]
WantedBy=multi-user.target

上記を保存した後、以下コマンドでTomcatを起動します。

systemctl start tomcat9

起動後、以下コマンドで状態を確認します。

systemctl status tomcat9

f:id:J-back:20200503162730p:plain:w600


ブラウザから8081番ポートにアクセスすると、以下のようにTomcatの初期画面が表示されます。
f:id:J-back:20200503162825p:plain:w600

これで1つ目のTomcatインスタンスのインストールが完了しました。


次に、2つ目のインスタンス設定を実施します。
基本的に流れは同じですが、使用するポート番号を他と重複しないように28005、8082と設定します。
「/opt/tomcat-9.0.34-ex/conf/server.xml」をエディタで開き、8005→28005、8080→8082と変更します。
f:id:J-back:20200503163401p:plain:w600
f:id:J-back:20200503163419p:plain:w600


上記が設定できたら、2つ目のインスタンスについても起動設定をしていきます。
プログラムディレクトリの権限を変更します。

chown -R tomcat:tomcat /opt/tomcat-9.0.34-ex

次に、2つ目のインスタンスをサービスとして登録します。
「/etc/systemd/system」に「tomcat9-ex.service」というファイルを作成し、中身を以下のように記述しましょう。

[Unit]
Description=Apache Tomcat 9 ex
After=syslog.target network.target

[Service]
User=tomcat
Group=tomcat
Type=oneshot
PIDFile=/opt/tomcat-9.0.34-ex/tomcat9-ex.pid
RemainAfterExit=yes

ExecStart=/opt/tomcat-9.0.34-ex/bin/startup.sh
ExecStop=/opt/tomcat-9.0.34-ex/bin/shutdown.sh
ExecReStart=/opt/tomcat-9.0.34-ex/bin/shutdown.sh;/opt/tomcat-9.0.34-ex/bin/startup.sh

[Install]
WantedBy=multi-user.target


上記を保存し、以下コマンドで2つ目のTomcatを起動します。

systemctl start tomcat9-ex

f:id:J-back:20200503163835p:plain:w600


ブラウザから8082番ポートにアクセスすると、2つ目のTomcatインスタンスが応答した画面が表示されます。
f:id:J-back:20200503163932p:plain:w600

ここまでの設定によって、
・80番ポートへアクセス→1つ目のApacheインスタンスが応答
・8080番ポートへアクセス→2つ目のApacheインスタンスが応答
・8081番ポートへアクセス→1つ目のTomcatインスタンスが応答
・8082番ポートへアクセス→2つ目のTomcatインスタンスが応答
という状態になりました。

ApacheからTomcatへの通信転送設定

以下の記事を参考に設定していきます。
www.77-lifework.com

今回はApacheTomcatインスタンスを2つずつ起動しているので、それぞれに対して転送設定をします。

1つ目のインスタンスについて(80番ポートを使用)

Apacheの1つ目のインスタンスのインストールディレクトリを「/opt/httpd-2.4.43」とします。
まずは「/opt/httpd-2.4.43/conf/extra」ディレクトリの「httpd-proxy.conf」ファイルを編集し、Apacheの80番ポートで受けた通信を18009番ポートで転送するように設定します。
(これらのディレクトリやファイルが存在しない場合は新規に作成してください)

ProxyPass / ajp://localhost:18009/

f:id:J-back:20200504153843p:plain:w600

このファイルをApacheに読み込ませるために、「/opt/httpd-2.4.43/conf」ディレクトリの「httpd.conf」を開き、以下を追記します。

Include conf/extra/httpd-proxy.conf

f:id:J-back:20200504154300p:plain:w600


次に、Tomcatの設定をしていきます。
インストールディレクトリを「/opt/tomcat-9.0.34」とした場合に、「/opt/tomcat-9.0.34/conf」ディレクトリの「server.xml」を編集します。
ajpのConnector設定の記載箇所について、8009番ポートを18009番ポートへ変更しましょう。
このとき、「secretRequired="false"」の記載がなければ記載しておきます。
f:id:J-back:20200504155017p:plain:w600

ここまで設定できたら一度ApacheTomcatを再起動しておきましょう。
再起動後、ブラウザから80番ポートにアクセスすると、以下のように通信がApacheから転送されてTomcatの画面が表示されています。
f:id:J-back:20200504155401p:plain:w600

2つ目のインスタンスについて(8080番ポートを使用)

次に、2つ目のインスタンスの設定です。基本的に1つ目のインスタンスと同様ですが、ポート番号が重複しないよう設定します。
Apacheのインストールディレクトリを「/opt/httpd-2.4.43-ex」とした場合、「/opt/httpd-2.4.43-ex/conf/extra」ディレクトリの「httpd-proxy.conf」に以下を追記します。2つ目のインスタンスajpは28009番ポートを使用するよう設定します。

ProxyPass / ajp://localhost:28009/

また、「/opt/httpd-2.4.43-ex/conf」ディレクトリの「httpd.conf」に以下を記載します。

Include conf/extra/httpd-proxy.conf

Tomcat側も設定していきます。
インストールディレクトリを「/opt/tomcat-9.0.34-ex」とした場合に、「/opt/tomcat-9.0.34-ex/conf」ディレクトリの「server.xml」を編集します。
ajpのConnector設定の記載箇所について、8009番ポートを28009番ポートへ変更し、「secretRequired="false"」を記載します。
ApacheTomcatを再起動後、ブラウザで8080番ポートにアクセスすると、以下のようにTomcatの画面が表示されます。
f:id:J-back:20200504160613p:plain:w600

最後に

以上で、Tomcatインスタンスを2つ起動し、それぞれのインスタンスに対してApacheから通信を転送することができました。
最後まで読んでいただき、ありがとうございました。

Pythonによるスクレイピングで炭焼きさわやかに待たずに入れる時間帯を調べる

こんにちは。タイトルが意味不明かもしれませんが、これは以下の記事の続き的な立ち位置です。
www.77-lifework.com

さわやかの待ち時間を調べられるサイトを定期的にスクレイピングし、待ち時間をスプレッドシートに記録した上、どの時間帯が空いているのか調べてみようというテーマです。
動機や背景は前の記事と同じなので、はしょります。
割と真剣にやっちゃいましたので、ぜひご覧ください。

処理の流れと出力結果

まず、使用しているプログラミング言語Pythonで、待ち時間をスクレイピングし、Googleスプレッドシートに結果を書き込むプログラムを作成しました。これをLinuxサーバに配置し、cronで15分ごとに実行しています。
結果は以下のようになります。日付ごとのシートが作成され、各店舗の待ち時間が書き込まれています。
f:id:J-back:20200126001754p:plain:w700

営業時間外の場合は「-1」を書き込むようにしています。データベースで管理するほど大量のデータを扱う気はなかったので、今回は代わりにスプレッドシートを使用しました。

Pythonのコード

さて、今回のPythonコードですが、以下です。

# coding: UTF-8
import os
import locale
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import slackweb
import time
import datetime
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# 定数宣言
SLACKURL_ex = '例外発生時のslack通知用URLを入力'

#jsonファイルを指定
CREDENTIAL_JSON = 'スプレッドシートアクセスのためのjsonファイルのパスを指定'

# main処理
def main():

    scope = ['https://spreadsheets.google.com/feeds',
            'https://www.googleapis.com/auth/drive']

    # 認証
    credentials = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIAL_JSON, scope)
    gc = gspread.authorize(credentials)

    # 対象ブックを指定
    target_book = gc.open('sawayaka_analysis')

    # 現在日付を取得
    now_date = datetime.date.today().strftime('%Y-%m%d')

    # シート名一覧を取得
    worksheet_list = target_book.worksheets()
    sheetname_list = []
    for wsList in worksheet_list:
        sheetname_list.append(wsList.title)

    if not now_date in sheetname_list :
        target_book.add_worksheet(title=now_date, rows=100, cols=26)
    
    # 書き込み対象のシートを指定
    target_sheet = target_book.worksheet(now_date)

    # スクレイピング対象URL
    URL = "https://www.genkotsu-hb.com/airwait.php"

    # スクレイピング実行
    try:

        # 現在時刻取得
        now_time = datetime.datetime.now().strftime('%H:%M')       

        # Chrome設定
        options = Options()
        options.add_argument('--headless')

        # Chromeを起動してurlを開く
        driver = webdriver.Chrome(options=options)
        driver.get(URL)
        time.sleep(20)

        # 文字コードをUTF-8に変換し、html取得
        html = driver.page_source.encode('utf-8')
        soup = BeautifulSoup(html, "html.parser")

        # 対象店名読み込み
        storeTagList = soup.find_all('div', class_='sc-fONwsr bmOhpB')

        # 店名入力対象セルを取得
        storeName_cells = target_sheet.range(1,1,1,len(storeTagList)+1)
        storeName_cells[0].value = '時刻/待ち時間' 

        for i in range(0, len(storeTagList)):
            storeName_cells[i+1].value = str(storeTagList[i]).replace('<div class="sc-fONwsr bmOhpB">', '').replace('</div>', '')

        # スプレッドシートへ店名書き込み
        target_sheet.update_cells(storeName_cells)

        # 待ち時間入力対象列を決定
        colList = target_sheet.col_values(1)
        target_rownum = len(colList) + 1

        # 待ち時間入力対象セルを取得
        waitTime_cells = target_sheet.range(target_rownum,1,target_rownum,len(storeTagList)+1)
        waitTime_cells[0].value = now_time

        for i in range(0, len(storeTagList)):
            # tagとclassを指定して要素を取り出す
            storeElement = soup.find('div', string=storeName_cells[i+1].value)
            waitingTimeObj = storeElement.find_parent('a').find('div',class_='sc-ipXKqB iHAIGD')

            if (waitingTimeObj is not None):
                waitTime_cells[i+1].value = waitingTimeObj.text.replace("約","").replace("分待ち", "")
            else:
                # 受付時間外は-1を代入
                waitTime_cells[i+1].value = -1

        # スプレッドシートへ待ち時間書き込み        
        target_sheet.update_cells(waitTime_cells)
            
    except Exception as e:
        message = "[例外発生]"+ os.path.basename(__file__) +"\n"+"type:{0}".format(type(e))+"\n"+"args:{0}".format(e.args)
        slackweb.Slack(url = SLACKURL_ex).notify(text = message)

    finally:
        # 起動したChromeを閉じる
        driver.close()
        driver.quit()


if __name__ == '__main__':
    main()

スプレッドシートへの書き込み処理については以下の記事を参考にしてください。
www.77-lifework.com


ちなみに、

# 定数宣言
SLACKURL_ex = '例外発生時のslack通知用URLを入力'

ってところと

   except Exception as e:
        message = "[例外発生]"+ os.path.basename(__file__) +"\n"+"type:{0}".format(type(e))+"\n"+"args:{0}".format(e.args)
        slackweb.Slack(url = SLACKURL_ex).notify(text = message)

ってところはなくても大丈夫です。
不測の事態が起きた時に通知されてるといいなー、と思ってslackに通知する処理を入れているだけなので、本プログラムの動作には関係ない部分です。

このコードを毎日15分ごとに実行した結果がさっき貼ったスプレッドシートの画像ですね。とりあえず全店舗について取得していますが、ある程度しぼっても良いかな、とも思います。

データ分析

分析、というほどすごいことはできないのですが(というか、スクレイピングできたところで結構満足してしまっています)、せっかくデータ取得したので、少し中身を見てみましょう。
Pythonコードを実行して作成したスプレッドシートのデータを使って、ある1週間について「御殿場インター店」の空き具合を曜日ごとにまとめたものが以下です。
f:id:J-back:20200126003412p:plain:w700

これをグラフにしてみると、以下のようになります。
平日に比べて土日のほうが全体的な待ち時間が多いのが見て取れますね、これは感覚的にも正しい結果です。
また、日曜日は基本的にどの時間帯も混んでいますが、土曜日だったら16:00〜17:30ぐらいが少し空いていそうな感じですね。
とはいえ、今回はデータが7日間分しかないので、たまたまこの土曜日だけこの傾向となっているのかもしれません。
もっと長い期間で曜日ごとに平均を出したりすれば、より現実に近い傾向がつかめるのではないでしょうか。
f:id:J-back:20200126003954p:plain:w700

最後に

今回はさわやかの待ち時間データを取得して分析するところまでやってみました。今回はさわやかの待ち時間というテーマでやりましたが、こんな感じで様々なデータを集めて分析してみるとおもしろいですね、アイデア次第でいろいろ応用がききそうです。
最後まで読んでくださり、ありがとうございました。

Apacheを複数起動(複数インスタンスを構成)する

はじめに

今回はLinux(CentOS)上にApacheをインストールし、複数の独立したインスタンスとして起動する方法を書いていきます。
ここでは2つのインスタンスを起動しますが、それぞれのインスタンスへのアクセスはポート番号で区別することとします。
(もちろん、IPアドレスでも区別できます)

アクセスする時のイメージは下図のような感じです。
f:id:J-back:20191110232259j:plain:w600

構成情報

CentOS 7.7(1908)
Apache 2.4.41
・ポート番号
 インスタンス1:80
 インスタンス2:8080

Apacheインストール準備

今回はApacheのソースファイルをダウンロードし、コンパイルしてインストールする方法で進めます。
Apacheコンパイルにはいくつかパッケージが必要なので、先にインストールしておきます。

まずは以下のパッケージをyumでインストールします。実行前にはインターネットと接続できること、DNSサーバが設定されていて名前解決ができることを確認しておきましょう。

yum -y install gcc pcre pcre-devel expat-devel

Apacheのソースファイルをコンパイルするためには「apr」と「apr-util」が必要なので、これらのソースファイルを以下のリンク先からダウンロードします。tar.gz形式を選択しましょう。
apr.apache.org


ここでは「apr-1.6.5」と「apr-util-1.6.1」をダウンロードしました。基本的に最新版を使用で問題ないと思います。
以下の「apr-x.x.x」と「apr-util-x.x.x」は適宜ダウンロードしたバージョンに読み替えてください。
ダウンロードしたtarファイルをSCP等でインストール先のサーバに配置します。今回は「/tmp」に置きました。
f:id:J-back:20191112232627p:plain:w600

以下コマンドで解凍します。

cd /tmp
tar -zxvf apr-1.6.5.tar.gz
tar -zxvf apr-util-1.6.1.tar.gz


aprを「/opt/apr-1.6.5」にインストールします。

cd /tmp/apr-1.6.5/
./configure --prefix=/opt/apr-1.6.5
make
make install

下記のエラーが出た場合にはconfigureファイルを編集します。

rm: cannot remove 'libtoolT': No such file or directory

以下のように変更し、保存したら再度上記のコマンドを実行します。

vi /tmp/apr-1.6.5/configure

変更前:
$RM "$cfgfile"
変更後:
$RM -f "$cfgfile"

f:id:J-back:20191112233058p:plain:w600



次にapr-utilを「/opt/apr-util-1.6.1」にインストールします。このとき、先ほどインストールした「apr-x.x.x」を指定します。

cd /tmp/apr-util-1.6.1/
./configure --prefix=/opt/apr-util-1.6.1 --with-apr=/opt/apr-1.6.5
make
make install


また、Apacheの実行用ユーザを作成しておきます。

useradd -s /sbin/nologin apache
groupadd apache
usermod -aG apache apache

Apacheコンパイル

以下のダウンロードページからApacheのソースファイルをダウンロードします。
httpd.apache.org


最新版のtar.gz形式のソースファイルを選択しましょう。
以下、「httpd-x.x.x」はダウンロードしたバージョンに読み替えてください。

f:id:J-back:20191109232457p:plain:w600

ダウンロードしたソースファイルをSCP等でインストール先のサーバに配置します。
ここでは「/tmp」に配置したので、以下コマンドで解凍します。

cd /tmp
tar -zxvf httpd-2.4.41.tar.gz

以下コマンドでコンパイルします。先ほどインストールした「apr-x.x.x」と「apr-util-x.x.x」を指定します。

cd /tmp/httpd-2.4.41/
./configure --prefix=/opt/httpd-2.4.41 --with-apr=/opt/apr-1.6.5 --with-apr-util=/opt/apr-util-1.6.1
make
make install

ここまでで1つ目のApacheをインストールすることができたので、次は2つ目のApacheをインストールしていきましょう。
「/tmp」に展開した「/tmp/httpd-2.4.41」は一度削除し、再度2つ目のApache用にtarファイルを展開します。

cd /tmp
rm -rf httpd-2.4.41
tar -zxvf httpd-2.4.41.tar.gz

その後、2つ目のApacheディレクトリ名を「httpd-2.4.41-ex」として「/opt/httpd-2.4.41-ex」にインストールします。

cd /tmp/httpd-2.4.41
./configure --prefix=/opt/httpd-2.4.41-ex --with-apr=/opt/apr-1.6.5 --with-apr-util=/opt/apr-util-1.6.1
make
make install

これで独立した2つのApacheをインストールすることができました。
最後にインストールしたディレクトリの所有者・グループをapacheに変更しておきましょう。

chown -R apache:apache /opt/httpd-2.4.41
chown -R apache:apache /opt/httpd-2.4.41-ex

IPアドレス・ポートの設定

ここからは、インストールした2つのApacheが使用するIP・ポートを設定します。
今回は以下のように、同じIPアドレス・別々のポートでアクセスするようにしました。

httpd-2.4.41
 ServerRoot:/opt/httpd-2.4.41
 IPアドレス:192.168.1.12
 ポート:80

httpd-2.4.41-ex
 ServerRoot:/opt/httpd-2.4.41-ex
 IPアドレス:192.168.1.12
 ポート:8080


まずは1つ目のインスタンスhttpd-2.4.41」の設定をするため、「/opt/httpd-2.4.41/conf/httpd.conf」をエディタで開きます。

ServerRootが「/opt/httpd-2.4.41」となっていることを確認。
f:id:J-back:20191123141325p:plain:w600


使用するIPとポートの指定部分で「Listen 192.168.1.12:80」と記述します。
f:id:J-back:20191123141540p:plain:w600


UserとGroupは「apache」を指定しておきましょう。
f:id:J-back:20191123141725p:plain:w600


DocumentRootが「/opt/httpd-2.4.41/htdocs」であることを確認します。
f:id:J-back:20191123141843p:plain:w600


ここまでできたら、「/opt/httpd-2.4.41/conf/httpd.conf」を保存して閉じます。

次に2つ目のインスタンスhttpd-2.4.41-ex」の設定ファイル「/opt/httpd-2.4.41-ex/conf/httpd.conf」を開き、
・ServerRootが「/opt/httpd-2.4.41-ex」となっていることを確認
・使用するIPとポートの指定部分で「Listen 192.168.1.12:8080」と記述
・UserとGroupは「apache」を指定
・DocumentRootが「/opt/httpd-2.4.41-ex/htdocs」であることを確認
を実施します。

Apacheをサービスに登録

インストールした2つのApacheをそれぞれ別のサービスとして登録しましょう。
まずは「/etc/systemd/system」に1つ目のインスタンス用として「httpd24.service」というファイルを作成し、中身を以下のように記述します。

[Unit]
Description=httpd24
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd(8)
Documentation=man:apachectl(8)

[Service]
Type=forking
ExecStart=/opt/httpd-2.4.41/bin/apachectl -f /opt/httpd-2.4.41/conf/httpd.conf -k start
ExecReload=/opt/httpd-2.4.41/bin/apachectl -k graceful
ExecStop=/opt/httpd-2.4.41/bin/apachectl -k stop
PrivateTmp=true

[Install]
WantedBy=multi-user.target


2つ目のインスタンスについても同様に「/etc/systemd/system」に「httpd24-ex.service」というファイルを作成し、中身を以下のように記述します。

[Unit]
Description=httpd24-ex
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd(8)
Documentation=man:apachectl(8)

[Service]
Type=forking
ExecStart=/opt/httpd-2.4.41-ex/bin/apachectl -f /opt/httpd-2.4.41-ex/conf/httpd.conf -k start
ExecReload=/opt/httpd-2.4.41-ex/bin/apachectl -k graceful
ExecStop=/opt/httpd-2.4.41-ex/bin/apachectl -k stop
PrivateTmp=true

[Install]
WantedBy=multi-user.target

2つのインスタンスのファイルを用意できたら、起動してみましょう。

systemctl start httpd24

以下コマンドで起動できているか確認します。

systemctl status httpd24

f:id:J-back:20191123145904p:plain:w600

同様に、2つ目のインスタンスも起動しましょう。

systemctl start httpd24-ex

以下コマンドでプロセスを確認し、それぞれ起動していればOKです。

ps aux | grep httpd

f:id:J-back:20191123152759p:plain:w600

2つのインスタンスの動作確認

ブラウザを起動して「http://192.168.1.12」にアクセスすると、1つ目のインスタンスが応答しています。
f:id:J-back:20200320163541p:plain:w600

また、「http://192.168.1.12:8080」にアクセスすると、2つ目のインスタンスが応答します。
f:id:J-back:20200320163700p:plain:w600


しかし、この状態だと両方とも初期画面なので、それぞれのインスタンスが応答しているのか分かりずらいですね。
そこで、初期画面にインスタンスの番号を表示するように少し書き換えます。
1つ目のインスタンスの初期画面として表示されるのは「/opt/httpd-2.4.41/htdocs/index.html」なので、これを以下のように編集します。

<html>
    <body>
        <h1>It works!<br></h1>
        Instance1
    </body>
</html>

この状態で再び「http://192.168.1.12」にアクセスすると、以下のように「Instance1」と表示されていますね。
f:id:J-back:20200320173021p:plain:w600



また、2つ目のインスタンスの初期画面として表示されるのは「/opt/httpd-2.4.41-ex/htdocs/index.html」なので、これも以下のように編集します。

<html>
    <body>
        <h1>It works!<br></h1>
        Instance2
    </body>
</html>

こちらも編集後、「http://192.168.1.12:8080」にアクセスすると、「Instance2」が表示されています。
f:id:J-back:20200320173138p:plain:w600

Tomcatの複数起動について

Tomcatをインストールして複数起動させる方法についても以下の記事に記載しています。
www.77-lifework.com

最後に

以上で2つのApacheインスタンスをインストールし、起動させることができました。
最後までお読みいただき、ありがとうございました。

Pythonで株価をスクレイピングしてスプレッドシートに書き込む

目的

今回はPythonを使って、スプレッドシートに記載されている銘柄を読み込んで株価を調べ、結果をスプレッドシートに反映する、というプログラムを書いていきます。
ここでは以下のようなスプレッドシートを対象にします。
f:id:J-back:20191012205515p:plain:w600


プログラムの流れは以下のようなイメージです。
f:id:J-back:20191012192348j:plain:w600


なお、スクレイピングを実行するときは、対象のサイトに迷惑をかけないようにアクセス回数や頻度に注意しましょう。
現実的に人間がアクセスするぐらいの感覚で、アクション一つ一つの間に待ち時間を入れるようにするのが良いと思います。

Pythonの動作環境設定

まずはPythonの動作環境を設定しましょう。以下の記事で説明しています。
すでに設定できている方はスキップしてください。
www.77-lifework.com


Pythonスプレッドシートの連携方法

これも少し準備が必要です。以下の記事参照です。
www.77-lifework.com


Pythonのコード

最終的なコードは以下のようになりました。
readSpreadsheet(gc)でスプレッドシートから銘柄コードを読み込んで、スクレイピングを実施。
取得した株価のリスト(price_list)をwriteSpreadsheet(gc, value_list)によってスプレッドシートへ書き込んでいます。

# coding: UTF-8
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from bs4 import BeautifulSoup
import requests
import time
import slackweb
  
# 例外発生通知用のslackのurl
SLACKURL_ex = '各自のslack連携用URLを入れてください'
  
# main処理
def main():
  
    scope = ['https://spreadsheets.google.com/feeds',
            'https://www.googleapis.com/auth/drive']
  
    #jsonファイルを指定
    credentials = ServiceAccountCredentials.from_json_keyfile_name('各自のjsonファイル名を入れてください', scope)
  
    # 認証
    gc = gspread.authorize(credentials)
  
    # 対象銘柄コード読み取り
    code_list = readSpreadsheet(gc)
  
    # 書き込み対象リスト定義
    price_list = []
  
  
    for i in range(1, len(code_list)):
  
        # スクレイピング対象URL
        URL = 'https://kabutan.jp/stock/?code=' + code_list[i]
  
        # スクレイピング実行
        try:
            #対象URLにリクエスト
            req = requests.get(URL)
            time.sleep(3)
  
            # 文字コードをUTF-8に変換し、html取得
            soup = BeautifulSoup(req.text, 'html.parser')
  
            # tagとclassを指定して要素を取り出す
            stock_price = soup.find('div', id='stockinfo_i1').find('span', class_='kabuka').text
            price_list.append(stock_price.replace('円',''))
  
        except Exception as e:
            message = "[例外発生]stock-price\n"+"type:{0}".format(type(e))+"\n"+"args:{0}".format(e.args)
            slackweb.Slack(url = SLACKURL_ex).notify(text = message)
            break
  
    # スプレッドシートへ株価を書き込む
    writeSpreadsheet(gc, price_list)
  
  
def readSpreadsheet(gc):
  
    # 読み込むシートを指定
    target_sheet = gc.open('test_sheet').worksheet('stock')
  
    # 指定した列のセルの値を読み込む
    value_list = target_sheet.col_values(1)
  
    return value_list
  
  
def writeSpreadsheet(gc, value_list):
  
    # 書き込み対象のシートを指定
    target_sheet = gc.open('test_sheet').worksheet('stock')
  
    for i in range(0,len(value_list)):
        #対象のセルに値を書き込む(row, col, value)
        target_sheet.update_cell(i+2, 3, value_list[i])
  
  
if __name__ == '__main__':
    main()


上記コードでは、スクレイピング中に例外が発生した際にはslackに通知するようにしてみました。
Pythonとslackの連携設定については、以下をご覧ください。
www.77-lifework.com


実際にコードを実行してみると・・・

f:id:J-back:20191012211454p:plain:w600

という感じで、株価が記載されています!


ちなみにスクレイピングの対象とするサイトから必要な情報(今回であれば株価)を抽出するときは、ブラウザで対象のページにアクセスし、
ページのソースを確認しましょう。
GoogleChromeであれば、対象のページを開いた状態でキーボードのF12を押すと、ページのソースhtmlを見ることができます。
f:id:J-back:20191012210218p:plain:w600


これを見ると、株価の値を取得するには、「 idが 'stockinfo_i1' のdivタグ」配下にある「classが 'kabuka' のspanタグ」を抽出すれば良いことが分かります。
このように、スクレイピングする際には対象のページを確認した上で、どのように必要な情報を取得するか考えてプログラムを組めばOKです。

最後に

これで指定した銘柄の株価をスプレッドシートに反映することができるようになりました。
このプログラムを1日1回自動実行する、など、cronやタスクスケジューラで設定しておけば、自分で調べる手間が省けますね。
最後まで読んでいただき、ありがとうございました。

PythonでGoogleスプレッドシートを操作する

はじめに

今回はPythonのプログラムからGoogleスプレッドシートの内容を読み込んだり、スプレッドシートに対して値を書き込んだり、をはじめとする、Pythonとスプレッドを連携させるための設定方法を書いていきます。

これができると、Pythonスクレイピングした情報をスプレッドシートで管理、共有することができるようになります。
スプレッドシートはクライアントPCの環境に依存せずに使用できるので、何かと便利ですよね。会社で使っている方も多いのではないでしょうか。

Google Cloud PlatformのAPI設定

まずはGoogleアカウントにログインした状態で、以下のリンクを開きましょう。
Googleのアカウントを持っていない場合は、新規登録しておきましょう。
console.cloud.google.com



リンク先を開くと以下の画面が表示されるので、「プロジェクトの選択」をクリック。
f:id:J-back:20191006234515p:plain:w600



「新しいプロジェクト」を選択。
f:id:J-back:20191006234556p:plain:w600



プロジェクト名を入力し、「作成」を選択。
f:id:J-back:20191006234633p:plain:w600



APIとサービス」の「ライブラリ」を選択。
f:id:J-back:20191006234723p:plain:w600



「プロジェクトの選択」をクリック。
f:id:J-back:20191006234821p:plain:w600



先ほど作成したプロジェクトを選択。
f:id:J-back:20191006235000p:plain:w600



以下の画面が表示されます。
f:id:J-back:20191006235108p:plain:w600



Google Drive」と入力して検索し、「Google Drive API」を選択します。
f:id:J-back:20191007000658p:plain:w600




「有効にする」を選択。
f:id:J-back:20191007000822p:plain:w600



以下の画面が表示されます。
f:id:J-back:20191007000901p:plain:w600



今度は「Google Sheets」と入力して検索し、「Google Sheets API」を選択します。
f:id:J-back:20191007000934p:plain:w600



「有効にする」を選択。
f:id:J-back:20191007001039p:plain:w600



以下の画面が表示されます。
f:id:J-back:20191007001112p:plain:w600

認証情報の設定

次に、メニューバーから、「認証情報」を選択します。
f:id:J-back:20191007003204p:plain:w600



「認証情報を作成」をクリックし、「サービス アカウント キー」を選択します。
f:id:J-back:20191007003252p:plain:w600



サービスアカウント名に任意の値を入力します。
f:id:J-back:20191007003457p:plain:w600



役割は「Project」の「編集者」を選択し、「作成」をクリックします。
f:id:J-back:20191007003540p:plain:w600



json形式のファイルがPCにダウンロードされます。
f:id:J-back:20191007003709p:plain:w600



サービス アカウント キーにIDが作成されていればOKです。
f:id:J-back:20191007003833p:plain:w600



ここで、ダウンロードしたjsonファイルを開くと

"client_email": "XXXXXXXX@YYYYYYYY"

と記載されている箇所があるので、「XXXXXXXX@YYYYYYYY」をコピーしておきましょう。


スプレッドシート側の設定

Pythonから操作したいスプレッドシートを開き、右上の「共有」をクリックします。
f:id:J-back:20191008000812p:plain:w600



「ユーザー」の部分に、先ほどコピーした「"client_email": "XXXXXXXX@YYYYYYYY",」の「XXXXXXXX@YYYYYYYY」を貼り付けます。
f:id:J-back:20191008001300p:plain:w600

これでスプレッドシートの設定は完了です。

必要なパッケージをインストール

以下2つのパッケージが必要となります。
・gspread
・oauth2client


以下コマンドを実行してインストールします。

pip install gspread
pip install oauth2client


または、環境によっては以下のコマンドとなります。ご自身の環境にあったものを実行してください。

pip3 install gspread
pip3 install oauth2client

これを実行すると、以下のような表示となります。
f:id:J-back:20191008002820p:plain:w600


f:id:J-back:20191008002839p:plain:w600


これでパッケージインストールはOKです。

Pythonプログラムを編集

以下のコードを作成し、実行しましょう。


# coding: UTF-8
import gspread
from oauth2client.service_account import ServiceAccountCredentials
  
scope = ['https://spreadsheets.google.com/feeds',
         'https://www.googleapis.com/auth/drive']
  
#jsonファイルを指定
credentials = ServiceAccountCredentials.from_json_keyfile_name('各自のjsonファイル名を入れてください', scope)
  
# 認証
gc = gspread.authorize(credentials)
  
# 読み込むスプレッドシートをファイル名で指定
target_book = gc.open('test_sheet')
  
# 読み込むシートをシート名で指定
target_sheet = target_book.worksheet('シート1')
  
#対象のセルに文字列を書き込み
target_sheet.update_acell('A1', 'test')


これを実行すると、以下のようにスプレッドシートのA1セルに「test」と書き込まれました!
f:id:J-back:20191008004650p:plain:w600


なお、上記プログラムを実行する際は、Pythonのファイルと同じ場所にjsonファイルも配置しておいてくださいね。

最後に

PythonのプログラムからGoogleスプレッドシートを操作する設定について書きました。これを応用すればPythonで集めたデータや解析結果などをスプレッドシートで管理することもできますね。

最後まで読んでいただき、ありがとうございました。