[PYTHON] Ich habe das Stempeln von Job Can mit Selen automatisiert und in Google Cloud Functions bereitgestellt, aber es war ziemlich schwierig

Einführung

Ich habe Selen verwendet, um das Stempeln von Jobdosen zu automatisieren, und es für Google Cloud-Funktionen bereitgestellt. Ich werde die Punkte beschreiben, von denen ich damals süchtig war.

Einfache Selbsteinführung

Arbeitete bei SIer in Tokio RPA-Ingenieur → Unterstützung für die PoC-Einführung von AI-Lösungen usw.

Ich mache auch Twitter ↓ https://twitter.com/m_schna2

Flussdiagramm

Es hat die folgende Konfiguration. Als ich es tatsächlich ausprobierte, dauerte es eine Weile, bis Google Drive und Cloud-Funktionen verknüpft waren. フロー

Ganzer Code

Die ID pw url ist eingebettet, aber bitte ändern Sie sie entsprechend.

main.py


from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.chrome.options import Options
import jpholiday
import datetime
import requests, json
import os, time
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
import shutil

def punchClockWrapper(request):
  def isBizDay():
    dt_now = datetime.datetime.now()
    dt_now = utc_to_jst(dt_now)
    DATE = dt_now.strftime('%Y%m%d')
    Date = datetime.date(int(DATE[0:4]), int(DATE[4:6]), int(DATE[6:8]))
    if Date.weekday() >= 5 or jpholiday.is_holiday(Date):
      return 0
    else:
      return 1

  def punchClock():
    #Konstante
    url_jobcan = 'https://id.jobcan.jp/users/~~~~~~~~~~'
    id = '~~~~~~~~~~~~~'
    pw = '~~~~~~~~~~~~~'

    #Optionseinstellung
    options = Options()
    options.add_argument('--headless')
    options.add_argument('--disable-gpu')
    options.add_argument('--window-size=1280x1696')
    options.add_argument('--no-sandbox')
    options.add_argument('--hide-scrollbars')
    options.add_argument('--enable-logging')
    options.add_argument('--log-level=0')
    options.add_argument('--v=99')
    options.add_argument('--single-process')
    options.add_argument('--ignore-certificate-errors')

    #Backend-Betrieb der GUI
    options.binary_location = os.getcwd() + "/headless-chromium"    
    driver = webdriver.Chrome(os.getcwd() + "/chromedriver", options=options) #Laden Sie den Treiber, auf dem Chrome ausgeführt wird

    #Öffnen Sie Job Can
    driver.get(url_jobcan)

    #E-Mail-Eingabe
    text = driver.find_element_by_id("user_email")
    text.send_keys(id)

    #Passworteingabe
    text = driver.find_element_by_id("user_password")
    text.send_keys(pw)

    #Login klicken
    btn = driver.find_element_by_name("commit")
    btn.click()

    #Klicken Sie auf die Schaltfläche Stempel
    btn = driver.find_element_by_id("adit-button-push")
    btn.click()

    #Warten Sie 8 Sekunden
    time.sleep(8)

    #schließe das Fenster
    driver.quit()

  def toSlack():
    WEB_HOOK_URL = "https://hooks.slack.com/services/~~~~~~~~~"
    dt_now = datetime.datetime.now()
    dt_now = utc_to_jst(dt_now)
    dt_punch = dt_now.strftime('%Y Jahr%m Monat%d Tag%H:%M:%S')
    requests.post(WEB_HOOK_URL, data = json.dumps({
      'text': str(dt_punch)+u'Stempeln abgeschlossen',  #Benachrichtigungsinhalt
      'username': '~~~~~~',  #Nutzername
      'icon_emoji': u':smile_cat:',  #Symbol
      'link_names': 1,  #Linknamen
    }))

  def utc_to_jst(timestamp_utc):
    timestamp_jst = timestamp_utc.astimezone(datetime.timezone(datetime.timedelta(hours=+9)))
    return timestamp_jst

  def writeLog(message):
    dt_now = datetime.datetime.now()
    dt_now = utc_to_jst(dt_now)
    dt_punch = dt_now.strftime('%Y Jahr%m Monat%d Tag%H:%M:%S')
    with open(log_path, 'a') as f:
      print(str(dt_punch)+u'  '+message, file=f)

  def downloadLog():
    f = drive.CreateFile(drive_param)
    f.GetContentFile(log_path)

  def uploadLog():
    f = drive.CreateFile(drive_param)
    f.SetContentFile(log_path)
    f.Upload()

  #Konstante Einstellung
  log_path = "/tmp/punch_clock.log"
  drive_param = {
    'parents': [{
        'id':'~~~~~~~~~~~'
    }],
    'id': '~~~~~~~~~~~',
    'title': 'punch_clock.log'
  }
  #dir bewegen(settings.Da yaml aus dem Ausführungsverzeichnis gelesen wird, ist der Standard das aktuelle Verzeichnis)
  #Muss in einem Verzeichnis mit Lese- und Schreibberechtigungen gespeichert werden
  os.chdir(os.path.dirname(os.path.abspath(__file__)))
  shutil.copy2("credentials.json","/tmp/credentials.json")
  #Authentifizierung
  gauth = GoogleAuth()
  gauth.CommandLineAuth()
  drive = GoogleDrive(gauth)
  #Unterhalb der Hauptverarbeitung
  downloadLog()#Laden Sie Protokolle von Google Drive herunter
  writeLog('Starten Sie die Verarbeitung') #Protokoll starten
  flg = isBizDay() #Wochentagsurteil(Wochentage: 1, Feiertage: 0)
  if flg == 1 :
    punchClock() #Stempeln
    toSlack() #Slack-Benachrichtigung
    writeLog('Stempeln abgeschlossen') #Protokoll beenden
  else :
    writeLog('Ich habe es nicht abgestempelt, weil es ein Feiertag ist') #Protokoll beenden
  uploadLog() #Laden Sie Protokolle auf Google Drive hoch
  return 'Erledigt'

Glatte Punkte

selenium Dies ist der Hauptstempelvorgang (Stanzuhr). Im Code mache ich mir Sorgen, dass ich nach dem Stempeln 8 Sekunden warten muss, aber ich habe es nie versäumt, zu stempeln, also ist es wahrscheinlich in Ordnung.

Slack-Benachrichtigung

Benachrichtigen Sie über die Webhook-API. Es gibt Schritte für API-Einstellungen unter der folgenden URL. https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8#incoming-webhook-u12398u35373u23450

Der schwierige Punkt

Bereitstellung in Google Cloud-Funktionen (GCF)

Legen Sie in Bezug auf die Bereitstellungsmethode den gesamten Code und die erforderlichen Bibliotheksmodule in demselben Ordner ab und laden Sie ihn mit dem folgenden Code usw. hoch.

gcloud functions deploy punchClockWrapper --runtime python37 --trigger-http --region asia-northeast1 --memory 512MB

Hier finden Sie die folgende URL. https://blowup-bbs.com/gcp-cloud-functions-python3/

Protokoll zu Google Drive hinzufügen (mit PyDrive)

So fügen Sie Google Drive ein Protokoll hinzu Laden Sie das vorherige Protokoll herunter → Hinzufügen → Hochladen Da Google Drive jedoch nach Datei-ID verwaltet, wird es auch dann, wenn Sie mit demselben Namen hochladen, als separate Datei mit demselben Namen hochgeladen, anstatt standardmäßig zu überschreiben. Daher kann es durch Angabe der ID des übergeordneten Ordners und der ID der Protokolldatei überschrieben werden. Kann die ID der Protokolldatei jedoch nicht durch normalen GUI-Betrieb abgerufen werden? Ich habe den folgenden Code gedreht, um die ID zu überprüfen.

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
import pprint

#Authentifizierung
gauth = GoogleAuth()
gauth.CommandLineAuth()
drive = GoogleDrive(gauth)

#Dateidatenerfassung
file_id = drive.ListFile({'q':'title="punch_clock.log"'}).GetList()[0]['id']
f = drive.CreateFile({'id':file_id})

#Anzeige von Dateimetadaten
f.FetchMetadata()
pprint.pprint(f)

Übrigens können Sie die Ordner-ID im URL-Teil überprüfen, indem Sie sie mit einem Browser öffnen. Sie können die Methode unter dem folgenden Link überprüfen. https://tonari-it.com/gas-google-drive-file-folder-id/

Kombination von PyDrive und GCF

PyDrive ist eine Wrapper-Bibliothek für die Google Drive-API. Zunächst ist eine interaktive Authentifizierung erforderlich, aber json wird nach der erfolgreichen Authentifizierung erstellt, und der json wird ab dem zweiten Mal referenziert, sodass er zur Automatisierung von Google Drive in den Code integriert werden kann. Es gibt nur wenige Codesituationen und es ist sehr ordentlich.

#Authentifizierung
gauth = GoogleAuth()
gauth.CommandLineAuth()
drive = GoogleDrive(gauth)

Es ist nur das Verhalten zum Zeitpunkt der Authentifizierung Siehe settings.yaml im aktuellen Verzeichnis → Beziehen Sie sich auf den in save_credentials_file angegebenen Pfad → Wenn json vorhanden ist, ist die Authentifizierung erfolgreich und überschreibt json. Wenn nicht, wird die Authentifizierung mit Code angefordert. Wenn Code eingegeben wird, wird json unter demselben angegebenen Pfad erstellt. Es ist notwendig, sich dieses Punktes bewusst zu sein.

Natürlich können Sie sich nach der Bereitstellung in GCF nicht interaktiv authentifizieren. Ich authentifiziere mich also vorab lokal, erstelle eine JSON-Datei und speichere sie zusammen in dem Ordner, der bereitgestellt werden soll. In GCF ist das beschreibbare Verzeichnis jedoch auf / tmp beschränkt. Und da json auch bei erfolgreicher Authentifizierung geschrieben (überschrieben) wird, müssen Sie den Pfad /tmp/~~~.json in save_credentials_file angeben. Im Code (unter dem Code) wird credentials.json aus dem Bereitstellungsverzeichnis nach / tmp kopiert. In der zusammen bereitgestellten Datei settings.yaml wird /tmp/credentials.json im Element save_credentials_file angegeben. Übrigens muss sich PyDrive settings.yaml im aktuellen Verzeichnis befinden, daher wurde es in das Dateiausführungsverzeichnis verschoben.

#dir bewegen(settings.Da yaml aus dem Ausführungsverzeichnis gelesen wird, ist der Standard das aktuelle Verzeichnis)
#Muss in einem Verzeichnis mit Lese- und Schreibberechtigungen gespeichert werden
os.chdir(os.path.dirname(os.path.abspath(__file__)))
shutil.copy2("credentials.json","/tmp/credentials.json")

Standardmäßig ist die Zeit ausgeschaltet

Da der von GCF gestartete Standardbereich des Workers us-central1 ist, der 9 Stunden von der japanischen Zeit entfernt ist, wird die in der Protokollausgabe und der Slack-Benachrichtigung beschriebene Zeit durch die folgende Funktion korrigiert. (Ich hätte diesmal mit Asia-North1 stationieren sollen, aber es verschiebt sich immer noch aus irgendeinem Grund ...)

def utc_to_jst(timestamp_utc):
  timestamp_jst = timestamp_utc.astimezone(datetime.timezone(datetime.timedelta(hours=+9)))
  return timestamp_jst

Zukünftige Aufgaben

--embed id, pw, url Es hat eine niedrige Priorität, aber ich möchte es eines Tages verbessern.

Referenz-URL

https://stackoverflow.com/questions/51487885/tmp-file-in-google-cloud-functions-for-python https://blowup-bbs.com/gcp-cloud-functions-python3/ https://dev.classmethod.jp/articles/python-time-string-timezone/ https://qiita.com/wezardnet/items/34b89276cdfc66607727

Recommended Posts

Ich habe das Stempeln von Job Can mit Selen automatisiert und in Google Cloud Functions bereitgestellt, aber es war ziemlich schwierig
Konvertieren Sie eine Tabelle in CSV und laden Sie sie mit Cloud-Funktionen in den Cloud-Speicher hoch
Die mit vim bearbeitete Datei war schreibgeschützt, aber ich möchte sie speichern
Ich war süchtig danach, 2020 mit Selen (+ Python) zu kratzen
[IOS] GIF-Animation mit Pythonista3. Ich war süchtig danach.
PyTorchs Buch war schwer zu verstehen, deshalb habe ich es ergänzt
Laden Sie Dateien mit Django-Speicher in Google Cloud Storages hoch und löschen Sie sie
Ein Memorandum beim automatischen Erwerb mit Selen
Ich möchte eine externe Bibliothek mit IBM Cloud-Funktionen verwenden
Herstellen einer Verbindung zum Cloud Firestore über Google Cloud-Funktionen mit Python-Code
Ich habe versucht, mit Selenium und Python einen regelmäßigen Ausführungsprozess durchzuführen
Ich habe versucht zusammenzufassen, was mit Qiita mit Word Cloud ausgegeben wurde
Ich habe einen Musik-Bot mit discord.py und der Google Drive-API erstellt (getestet mit Docker → bereitgestellt für Heroku).
Ich habe eine App für Ausländer erstellt, die Japan in Hackason besuchen, und einen Preis gewonnen, aber als ich mir das genau überlegte, war es nutzlos.