ChatGPTでクラウドアプリを作らせようとするとこうなる

単純なコーダーはChatGPTによって駆逐される、と言われてますが、どの程度駆逐されそうなのか試してみました。
（ちなみにボクはChatGPT PlusでGPT-4モデルを利用しています。）

# 結論（使ってみた所感）
開発の初速を高める、という意味では、非常に強力なサービスだと思いました。
ただ、Gabage In, Gabage Outになるのはその通りだな、と思いましたし、そもそも「それっぽいコード」を出すところまでが早いのであって、仕上げは自分たちで実施する必要がありますね。

使い所を見極めて生産性向上に利用したいと思います。

※今回出力されるコードはAmazonの商品ページをスクレイピングする手法になっていますが、Amazonはスクレイピングを禁止しているので、正しく動かしたいならAmazonAPIを利用することになると思います。ボクもこの記事を書くまでAmazonがスクレイピング対策をしていることに気づいていませんでした。

# 概要
以下のメッセージをChatGPTに流しました。人間に出すにしても、かなり雑な、酷い仕様です。（今思えば、Amazonがスクレイピングできないのであれば他のサイトにすれば良いな、、、と思いました。Amazonだと動作確認までできない）

```
Amazonで特定の商品の価格が変わったら通知をくれる処理を開発したいです。
以下、仕様および成果物の内容です。

■仕様
１．開発に利用する技術は以下の通り。
　１）処理：Python
　　　A) 商品コードをデータベースから読み取り、Amazonの商品ページにアクセスする。
　　　B) 商品ページから価格を取得し、前回の価格と比較する。
　　　C) 前回の価格から低くなっていた場合、Slackに通知する。
　２）データベース：MySQL
　　・DBには以下の内容を保存する。
　　　A) 商品コード
　　　B) 商品価格（前回の価格）
　３）価格変動時の通知先：Slack

２．AWS EC2上で動くスタンドアローンアプリとする。
３．このプログラムは１時間に１回起動する。

■成果物
１．データベース構築のためのDDL
２．Pythonプログラム
３．これらをEC2上に構築するCloudFormationテンプレート

出力するコードは、コピー可能な形式とすること。
```

作りたいアプリはシンプルにこういうものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102769/cb9aa43d-580b-d766-5f46-01446032e175.png)
※SSMとかは細かいので省略しています。


# ChatGPTからのリプライ
何度か細かい点でレビューをして修正させている部分はありますが（割愛します）、おおよそこのようなものが即答で返ってきました。パッと見、合ってそうでもはや怖い領域。プログラミング内容もさることながら、CloudFormation（CFn）のテンプレートもサッと出せるのは万能ですね。
（やり取りが必要になった内容は最下に書いておきます）

DDL：
```sql
CREATE TABLE `product_price` (
  `product_code` varchar(50) NOT NULL,
  `price` decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (`product_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

```

Pythonプログラム（Amazon商品ページのスクレイピングは禁止されているので、下記のコードは動かないし、スクレイピング部分は削除しています。）：
```python
import os
import boto3
import requests
from bs4 import BeautifulSoup
import pymysql
import logging
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

region_name = os.environ["AWS_REGION"]
ssm = boto3.client("ssm", region_name=region_name)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# データベース接続情報
DB_ENDPOINT = ssm.get_parameter(Name='/cgpt-amazon-price-alert/db-endpoint', WithDecryption=True)['Parameter']['Value']
DB_USER = ssm.get_parameter(Name='/cgpt-amazon-price-alert/db-user', WithDecryption=True)['Parameter']['Value']
DB_PASSWORD = ssm.get_parameter(Name='/cgpt-amazon-price-alert/db-password', WithDecryption=True)['Parameter']['Value']
DB_NAME = ssm.get_parameter(Name='/cgpt-amazon-price-alert/db-name', WithDecryption=True)['Parameter']['Value']

# Slack Bot接続情報
SLACK_BOT_TOKEN = ssm.get_parameter(Name='/cgpt-amazon-price-alert/slack-bot-token', WithDecryption=True)['Parameter']['Value']
SLACK_CHANNEL = ssm.get_parameter(Name='/cgpt-amazon-price-alert/slack-channel', WithDecryption=True)['Parameter']['Value']

# データベース接続
conn = pymysql.connect(host=DB_ENDPOINT, user=DB_USER, password=DB_PASSWORD, database=DB_NAME)

# LINE Notify
def notify_slack(message):
    try:
        client = WebClient(token=SLACK_BOT_TOKEN)
        response = client.chat_postMessage(channel=SLACK_CHANNEL, text=message)
    except SlackApiError as e:
        logger.error("Error sending message: {}".format(e.response['error']))

def get_price(url):
    ### Amazon商品ページのスクレイピングは禁止されているので、省略 ###

def main():
    # データベースから商品情報を取得
    cursor = conn.cursor()
    cursor.execute("SELECT product_code, price FROM product_price")
    rows = cursor.fetchall()

    # Amazonから商品価格を取得して、前回の価格と比較する
    for row in rows:

        # Amazonから価格を取得
        product_code, previous_price = row
        url = f"https://www.amazon.co.jp/dp/{product_code}"
        current_price = get_price(url)

        # 前回の価格と比較
        if current_price < previous_price:
            message = f"商品 {product_code} の価格が下がりました。前回の価格: {previous_price} 円, 今回の価格: {current_price} 円"
            notify_slack(message)

            # DBの商品価格を更新
            cursor.execute("UPDATE products SET price=%s WHERE product_code=%s", (current_price, product_code))
            conn.commit()

if __name__ == "__main__":
    main()

```

AWS SSMによるキーの登録:
```sh
aws ssm put-parameter \
    --name "/cgpt-amazon-price-alert/db-endpoint" \
    --value "your_db_endpoint" \
    --type "SecureString"

aws ssm put-parameter \
    --name "/cgpt-amazon-price-alert/db-user" \
    --value "your_db_username" \
    --type "SecureString"

aws ssm put-parameter \
    --name "/cgpt-amazon-price-alert/db-password" \
    --value "your_db_password" \
    --type "SecureString"

aws ssm put-parameter \
    --name "/cgpt-amazon-price-alert/db-name" \
    --value "your_db_name" \
    --type "SecureString"

aws ssm put-parameter \
    --name "/cgpt-amazon-price-alert/slack-bot-token" \
    --value "your_slack_bot_token" \
    --type "SecureString"

aws ssm put-parameter \
    --name "/cgpt-amazon-price-alert/slack-channel" \
    --value "your_slack_channel" \
    --type "SecureString"


```

CloudFormationテンプレート：
```yaml
---
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: "VPC ID"
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: "Subnet ID"
Resources:
  EC2Instance:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          commands:
            01_install_mysql:
              command: |
                amazon-linux-extras install -y epel
                yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
                yum --nogpgcheck install -y mysql-community-server
                systemctl start mysqld
                systemctl enable mysqld
            02_generate_password:
              command: |
                echo $(grep 'temporary password' /var/log/mysqld.log | awk '{print $NF}') > /tmp/mysql_root_password
            03_install_slack_sdk:
              command: |
                yum install -y python3-pip
                pip3 install slack-sdk
            04_install_beautifulsoup4:
              command: |
                pip3 install beautifulsoup4
            05_install_lxml:
              command: |
                pip3 install lxml
            06_install_requests:
              command: |
                pip3 install requests
          services:
            sysvinit:
              mysqld:
                enabled: true
                ensureRunning: true
    Properties:
      ImageId: ami-05b5badc2f7ddd88d
      InstanceType: t3.micro  # インスタンスタイプ
      KeyName: cgpt-amazon-price-alert-key  # EC2 インスタンスにアクセスするためのキーペア名
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          echo "export AWS_REGION=${AWS::Region}" >> /etc/profile.d/aws_region.sh
          chmod +x /etc/profile.d/aws_region.sh
          source /etc/profile.d/aws_region.sh
          /opt/aws/bin/cfn-init --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region}
          yum update -y
          yum install -y python3
          pip3 install boto3
          pip3 install pymysql
          pip3 install slack-sdk
          mkdir /app
          touch /app/check_price.py
          touch /var/log/check_price.log
          chown ec2-user:ec2-user /var/log/check_price.log
          echo '*/1 * * * * ec2-user /usr/bin/python3 /app/check_price.py' > /etc/cron.d/check_price-cron
      IamInstanceProfile: !Ref InstanceProfile
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          GroupSet:
            - !Ref InstanceSecurityGroup
          SubnetId: !Ref SubnetId
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH access
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref InstanceRole
  InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: SSMParameterAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssm:GetParameter
                Resource: "*"
```

MySQLのデータベース準備も面倒だったので、シェルスクリプトを作らせました。
（実際にはパスワードを直書きするような記法は推奨されていないので、下のコードは使わない方が良いです）
```sh
#!/bin/bash

# Variables
initial_password="your_initial_password"
new_password="your_new_password"

# Log in to MySQL and execute commands
mysql -u root -p"${initial_password}" <<EOF
-- Change the initial password
ALTER USER 'root'@'localhost' IDENTIFIED BY '${new_password}';

-- Create the amazonprice database
CREATE DATABASE amazonprice;

-- Use the amazonprice database
USE amazonprice;

-- Create the product_price table
CREATE TABLE product_price (
  product_code VARCHAR(50) NOT NULL,
  price DECIMAL(10,2) DEFAULT NULL,
  PRIMARY KEY (product_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Insert sample data into the product_price table
INSERT INTO product_price (product_code, price) VALUES ('your_price_code', 1000.00);
EOF
```

実行用の手順
```sh
chmod +x setup_mysql.sh
./setup_mysql.sh
```

# まとめ
冒頭に書いた通り、開発の初速を高めるサービスとしてはとても強力だと思います。ただ、初速を高める以上のことをさせようとすると結果的に時間がかかってしまうので、現段階では開発序盤で使うのが一番いいのかな、、、と思いました。

○○サーズとか○○○○ワークスとかで依頼が出ているようなものを片っ端からこなせるかと思いましたが、さすがにそこまではできなさそうです。

UserDataでルートにフォルダを掘ってrootしかアクセスできなくなっている、とか例外ハンドリングとかとか、言いたいことが山盛りのコードに仕上がっているのは注意です。これでお金はもらえないですね。

# (おまけ)ChatGPTと複数回やり取りが必要になった場所
最初に書いた指示内容で出てきたものは、やはり不完全な箇所があるため、いくつか解消するためにやり取りをしてアップデートさせてます。複数回やり取りが必要になったものは以下になります。最初の指示出しが悪かったのも相待って、40~50回くらいはChatGPTと会話をしました。ただ問題が発生した際にエラーメッセージをそのまま載せれば、ChatGPT側で悪そうな箇所を勝手に分析してくれるのは楽でしたね。

1.　DB_ENDPOINTなどの値が環境変数に定義されていた。
　→SSMから取得するように改良
2.　必要なライブラリのインストールがCFT（CloudFormation Template）の記載に足りなかった。
3.　VPC/Subnetなどの記載がされておらず、CFnのスタック生成ができなかった。
4.　何度かやり取りをしていると、今まで正しかった内容が誤ったりデグレしたりで修正が必要だった。そんなところまで人間っぽさは求めてない。（例：t2.microのインスタンスを生成するAMI IDが、正しいものからサンプルの適当なものになっていた）
5.　MySQLのインストールをCFnテンプレートに含まれていなかった。
6.　CFTのMetadataの呼び出しがUserdata部に記載されておらず、Metadata部が実行されない問題があった。
7. MySQLのインストールはGPGキーエラーなどで何度かエラーが発生した。
8. AWSリソースへのアクセスに必要なリージョン指定やEC2にアタッチするIAMロールの設定が足りなかった。
9. Amazonはスクレイピング対策をしているにもかかわらず、スクレイピングのコードを出してくる。（今回はAmazonAPI利用への変換は実施していないが、AmazonAPIを利用するよう明示すれば、AmazonAPIを利用するPythonコードは出力してくれました）