😪

python スクレイピングとブラウザ操作

2022/08/03に公開1

外部のプラットフォームを経由したお問い合わせに自動返信するRPAを作って欲しいと要望がありました。
RPAと言われましたが、Uipathで夜間ずっとそのサイトを監視するためにライセンスとPCを占領するのもなんかもったいないと思い、Pythonで実装することにしました。
去年の年末くらいに作ったのでほとんど忘れていますが思い出しながらまとめたいと思います。
主にスクレイピングについてですが、GmailAPIを使用しているのでコード内にGmail関係もだいぶ登場します。

やりたいこと

お問い合わせがあると担当者にメールが届くらしいですが、今回は直接会員向けサイトでお問い合わせの有無を確認して、お問い合わせがあったらメールの送信をすることにしました。
処理する時間は営業終了後の18時から翌日の10時までです。

事前準備

Gmail APIを使用するので認証に必要な情報をGCPで設定しておきます。
認証情報はJsonファイルでダウンロードしてからPythonファイルと同一フォルダに保存します。
下記サイトを参考にさせていただきました。

https://qiita.com/muuuuuwa/items/822c6cffedb9b3c27e21
https://valmore.work/automate-gmail-sending/#Google_Cloud_PlatformGmail_API

また、スクレイピングをするため、chromedriverもインストールしておきます。
https://chromedriver.chromium.org/downloads

ちなみにchromeのバージョンがかわると、chromedriverもそれに合わせてバージョンアップしないといけないのでご注意ください。

環境

anaconda3 4.11.0

コード

SendMail.py
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
import pickle
import os.path
import datetime
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import base64
from email.mime.text import MIMEText
from apiclient import errors
import sys

SCOPES = ['https://www.googleapis.com/auth/gmail.send']

# メール本文の作成
def create_message(sender, to, subject, message_text):
    message = MIMEText(message_text)
    message['to'] = to
    message['Bcc'] = "Bccに入れたいメールアドレス"
    message['from'] = sender
    message['subject'] = subject
    encode_message = base64.urlsafe_b64encode(message.as_bytes())
    return {'raw': encode_message.decode()}

# メール送信
def send_message(service, user_id, message):
    try:
        message = (service.users().messages().send(userId=user_id, body=message)
                   .execute())
        return message
    except errors.HttpError as error:
        print(error)

# 認証する関数
def gmail_authorize():
    creds = None
    # 認証済みか否かを確認する(token.pickleファイルの存在の有無)
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'XXXXXXXXXXXXXXXXXXXXXXXXXXX.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)
    return creds

# スクレイピングをしてくる
def collection():
    driverpath = 'chromedriver.exeのパス'
    driver = webdriver.Chrome(executable_path=driverpath)
    driver.get('ログインサイトのURL')
    time.sleep(1)
    idelement = driver.find_element(By.NAME, "idの要素のNAME属性")
    idelement.send_keys("ログインID")
    time.sleep(1)
    passelement = driver.find_element(By.NAME,"パスワードの要素のNAME属性")
    passelement.send_keys("ログインパスワード")
    time.sleep(1)
    loginelement = driver.find_elements(By.CLASS_NAME,"ログインボタンのクラス属性")
    loginelement[0].click()
    # ログイン後お問い合わせ情報閲覧ページへ
    # クリックしにくかったのでページの遷移でページを移動
    driver.get('お問い合わせページのURL')
    time.sleep(1)
    terms = driver.find_element(By.XPATH, 'クリックしたい要素のXpath')
    terms.click()
    time.sleep(1)
    seach = driver.find_element(By.XPATH, 'クリックしたい要素のXpath')
    seach.click()
    time.sleep(1)
    # 未閲覧の有無確認
    val = driver.find_elements(By.CLASS_NAME, '未閲覧のお問い合わせ件数の要素のクラス属性')
    valtext = val[0].text
    # 未閲覧が0件ならFalseを返す
    if valtext == "0件該当しました":
        return False
    link = driver.find_element(By.XPATH, 'お問い合わせの詳細ページのリンクのXpath')
    link.click()
    time.sleep(1)
    name = driver.find_element(By.XPATH, 'お客様氏名のXpath').text
    mailadress = driver.find_element(By.XPATH, 'メールアドレスのXpath').text
    PropertyName = driver.find_element(By.XPATH, 'お問い合わせの商品名の要素のXpath').text
    url = driver.find_element(By.XPATH, '商品の詳細ページのリンクの要素のXpath').text
    # 配列にして返す
    return [name, mailadress,PropertyName,url]

# メインの処理
def main():
    dt_now = datetime.datetime.now()
    try:
        result = collection()
    except:
        print(dt_now.strftime('%Y-%m-%d %H:%M:%S') + " : スクレイピング中にエラー発生処理終了")
        sys.exit()
    # 戻り値がfalseだったらログを追記して処理終了
    if not result:
        dt_now = datetime.datetime.now()
        print(dt_now.strftime('%Y-%m-%d %H:%M:%S') + " : 問い合わせなし実行終了")
        sys.exit()
    # 未確認のお問い合わせがあった場合は下記処理を行う
    creds = gmail_authorize()
    ervice = build('gmail', 'v1', credentials=creds)

    # メールに必要な内容を定義
    to = result[1]
    subject = "メールの件名"
    message_text = "メール本文!ここでスクレイピングした内容を使う"
    sender = "送信者のメールアドレス"

    message = create_message(sender, to, subject, message_text)
    dt_now = datetime.datetime.now()
    print(dt_now.strftime('%Y-%m-%d %H:%M:%S') + " : スクレイピング内容\n"+str(result))

    # 送信結果を出力
    TransmissionResult = send_message(service, "me" , message)
    dt_now = datetime.datetime.now()
    print(dt_now.strftime('%Y-%m-%d %H:%M:%S') + " : 送信結果\n"+str(TransmissionResult))
    print(dt_now.strftime('%Y-%m-%d %H:%M:%S') + " : 実行終了")

# 実行
if __name__ == '__main__':
    main()

解説

Gmailまわりの解説は事前準備のところに貼ったリンクに詳しく書いてあるので、スクレイピング(ほぼselenium)についてだけ解説します。

chromedriverの使い方

driverpath = 'chromedriver.exeのパス'
driver = webdriver.Chrome(executable_path=driverpath)
driver.get('ログインサイトのURL')

これでcrhomedriverを使用してサイトを開きます。
seleniumを使用してブラウザの情報取得を行いました。

https://www.selenium.dev/ja/documentation/webdriver/elements/information/

サイトによって取得するための属性はことなりますが、今回の使ったところをまとめます。

要素の取得

CLASS属性で取得

driver.find_elements(By.CLASS_NAME,"要素のCLASS属性")

NAME属性で取得

driver.find_element(By.NAME,"要素のNAME属性")

(取れそうな属性がない場合は)XPATHで取得

driver.find_element(By.XPATH, '要素のXpath')

今回は使っていませんが、ID属性で取得

driver.find_element(By.ID,"要素のID属性")

CLASS属性は「find_elements」で、取得できる値は配列になりますので注意が必要です。

要素の操作

テキストを取得

要素.text

クリック

要素.click()

主に今回使用したメソッドをまとめていますが、一覧になっている最強のサイトをみつけました。
https://www.seleniumqref.com/index.html

公式ドキュメントを見るのがいいと思いますが、逆引きできるの大変便利です!

所感

Uipathでスクレイピングした方が簡単ですが、ちょっと操作してちょっと情報が欲しいだけならpythonでやるのも悪くないと思います。
外部のシステムから直接自動返信ができるのがベストですが、出来ないそうなので仕方ないです。
このプログラムをタスクスケジューラで営業時間終了後から翌営業開始時刻まで10分おきに動かしています。
欲しい要素や操作したい要素の属性だけ確認したら簡単にスクレイピングできるのでselenium便利だなぁと思いました。

Discussion