のちたままブログ

MENU

Python-seleniumでnoteに自動ログイン、記事のタイトル、文章を入力する方法~key_down,key_upで改行の違いも解決~

こんにちは、のちたままです。

今回は、Python-seleniumを使って、noteに自動ログインしてみようと思います。

ついでに内容を書くところまで行ければと思います。

スクリプト全体

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
import time

driver=webdriver.Chrome()
url='https://note.com/login'
driver.get(url)

l_id='自分のID'
l_pw='自分のパスワード'

lid=driver.find_element_by_xpath('/html/body/main/login/div/section/div/div/form/div/div[1]/input')
lid.send_keys(l_id)

lpw=driver.find_element_by_xpath('/html/body/main/login/div/section/div/div/form/div/div[2]/input')
lpw.send_keys(l_pw)

button=driver.find_element_by_xpath('/html/body/main/login/div/section/div/div/form/button')
button.click()

button=WebDriverWait(drivers, 30).until(
    expected_conditions.visibility_of_element_located((By.XPATH, '//*[@id="__layout"]/div/div[2]/div[2]/div[2]/div/div/div[1]/div/div[1]/button'))
)
button.click()

toukoubutton=driver.find_element_by_xpath('//*[@id="__layout"]/div/div[1]/header/div[1]/nav/div[4]/div/div/div/div/div/button')
toukoubutton.click()

text=driver.find_element_by_xpath('//*[@id="__layout"]/div/div[1]/header/div[1]/nav/div[4]/div/div/div/div[2]/div/div[1]/ul/li[1]/a')
text.click()

title=WebDriverWait(drivers, 30).until(
    expected_conditions.visibility_of_element_located((By.XPATH, '//*[@id="note-name"]'))
)
title.send_keys('title タイトル')
inputtext=driver.find_element_by_xpath('//*[@id="note-body"]/p')
inputtext.send_keys('test テスト')

注意すべきこと

このスクリプトに関していくつか注意点があるので説明します。

その①ログイン後のポップアップの要素

seleniumで自動ログイン後、以下の画像の左部分が表示されると思います。

これは×を押せば消えるのでseleniumで操作が可能です。

ただ、要素が取得するときには注意が必要で、画像のようにきちんとdivが表示されているとこで取得しましょう。

間違ってアイコンを取得しようとしてうまくいかない場合があるので気をつけてください。

f:id:nochitamama:20210320121526p:plain

WebDriverWaitについて

これは別記事で説明しているので、そちらを読んでください。

nochitamama.hatenablog.com

time.sleep()を使う方法もありますが、WebDriverWaitを使うほうが結果的に高速になります。

おまけ

noteの仕様として、Enterキーを押したときとShift+Enterの場合で改行の意味が変わります。

Enterキーは段落を変える、Shift+Enterが通常の改行となっています。

seleniumでこれを解決するためにはActionChainsというものを使います。

キーボードを押すときと同じように、Shift+Enterなど複数キーの同時押しが使えるようになります。

それではコードの違いを見ていきます。

まずはEnterだけを押す場合、

element=driver.find_element_by_xpath('<各要素>')
element.send_keys(Keys.ENTER)
from selenium.webdriver.common.action_chains import ActionChains

element=driver.find_elemenet_by_xpath('<各要素>')
action=ActionChains(driver)
action.key_down(Keys.SHIFT)
action.send_keys(Keys.ENTER)
action.key_up(Keys.SHIFT)
action.perform()

少し行数が長くなってしまいますが、これでShift+Enterが使えます。

key_down,key_upは以下の状態を指します。

・key_down...キーを押下している状態
・key_up...キーを押していない状態

これらの状態をperformで実行するということになります。

つまり、「Shiftを押し続ける」→「Enterを押す」→「Shiftを離す」といった動きをしているということです。

最後に

今回はnoteへの自動ログインやタイトルと内容を記述するスクリプトを書いてみました。

前回ははてなブログでやっていたのですが、サイトが変わればスクリプトがガラッと変わりますね。

いろいろなサイトでseleniumを試すとまだ知らなかったseleniumの機能を理解できたりします。

次はまた別のサイトでやってみようと思います。

それでは。

Framework laptopのキーボードについて

こんにちは、のちたままです。

Frameworklaptopの更新情報が出てきたので紹介します。

Framework laptopについてはこちらの記事をご覧ください。

nochitamama.hatenablog.com

キーボードの特徴

Framework laptopはキーストロークが1.5mmに決まったようです。

一般的なノートパソコンのキーボードは薄さを追求するためにキーストロークを短くする必要があります。

そのため、キーストロークが0.8mm~1.2mmになっているようです。

しかし、それではキーボードの品質が犠牲になってしまっているということで、Framework laptopではキーストロークを1.5mmにしたそうです。

キーストロークはキーを押したときに沈み込む深さのことです。

したがって今までのノートパソコンのキーボードより、沈み込むものになるということです。

f:id:nochitamama:20210320180317j:plain [画像引用元:https://frame.work/blog/the-keyboard]

対応言語について

f:id:nochitamama:20210320180414j:plain [画像引用元:https://frame.work/blog/the-keyboard]

キーボードのは以下の言語に対応するそうです。

・英字(アメリカ)
・英字(イギリス)
・国際英語
・フランス語
・French canadian
・韓国語
・拼音中国語
・伝統的な中国語
・日本語
・ドイツ語
・イタリア語
・スペイン語
・ラテンアメリカ語
・ベルギーで使用されているフランス語

また、無刻印タイプのキーボードも2種類準備しているそうです。

1つは黒の無刻印タイプですが、もう一つはクリアタイプ、つまり中が透けて見えるタイプのキーボードを準備しているようです。 f:id:nochitamama:20210320180453j:plain [画像引用元:https://frame.work/blog/the-keyboard]

最後に

続々と更新情報が出てきますね。

出荷は夏ということですが、予約はいつになるのか(春頃だとは思いますが)楽しみです。

それではまた更新があったら紹介したいと思います。

それでは。

参考資料

frame.work


※この記事の内容は執筆時点での内容です。
最新情報はFramework社のページをご覧ください。
https://frame.work/
https://frame.work/blog/the-keyboard


Python-seleniumではてなブログ自動投稿プログラムを作ってみる⑥~結合編その3~

こんにちは、のちたままです。

長らく続いたはてなブログ自動投稿プログラム製作ですが、今回で主な説明は終了です。

長々とお疲れ様でした。

それでは最後も張り切っていきましょう。

コード全体

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
import os
import sys


xpath=[
    '//*[@id="login-name"]',
    '//*[@id="container"]/div[1]/form[1]/div/div[2]/div/input',
    '//*[@id="option"]/input[2]',
    '//*[@id="admin-menu"]/ul/li[3]/ul[1]/li[2]/a',
    '//*[@id="title"]',
    '//*[@id="body"]'
]

xpath_app=[#公開
    '//*[@id="submit-button"]', 
]
xpath_save=[#下書き保存
    '//*[@id="editor-main"]/div/div[3]/div[2]/div[2]/span[1]/div/a', 
    '//*[@id="editor-main"]/div/div[3]/div[2]/div[2]/div/ul/li[2]/a',
    '//*[@id="editor-main"]/div/div[3]/div[2]/div[2]/span[1]/button'
]
xpath_reserve=[#予約投稿
    '//*[@id="editor-support-container"]/div/div[1]/div[2]/div[3]',
    '//*[@id="datetime"]/div[1]/input',
    '//*[@id="datetime"]/div[1]/div/span/input',
    '//*[@id="datetime"]/div[2]/p[2]/label/input',
    '//*[@id="editor-main"]/div/div[3]/div[2]/div[2]/span[2]/input'
]
def read_file(filename):
    with open(filename) as f:
        s=f.readline()
        d=f.readline()
        d=f.read()
    return s,d

def SEND_KEYS(drivers, xpath, inputtext):
    text=WebDriverWait(drivers, 30).until(
        expected_conditions.visibility_of_element_located((By.XPATH, xpath))
    )
    text.send_keys(inputtext)
    
def CLICK(drivers, xpath):
    button=WebDriverWait(drivers, 30).until(
        expected_conditions.visibility_of_element_located((By.XPATH, xpath))
    )
    button.click()
    
def resource_path(relative_path):
    try:
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.dirname(__file__)
    return os.path.join(base_path, relative_path)


def main(userid,userpw,day,time,option,filename):
    driver = webdriver.Chrome(resource_path('./driver/chromedriver.exe'))
    weburl='https://blog.hatena.ne.jp/'
    driver.get(weburl)
    
    SEND_KEYS(driver, xpath[0], userid)
    SEND_KEYS(driver, xpath[1], userpw)
    CLICK(driver, xpath[2])
    
    CLICK(driver, xpath[3])
    
    title,arch=read_file(filename)
    SEND_KEYS(driver, xpath[4], title)
    SEND_KEYS(driver, xpath[5], arch)
    
    if option==1:#公開
        CLICK(driver, xpath_app)
    elif option==2:#予約投稿
        CLICK(driver, xpath_reserve[0])
        SEND_KEYS(driver, xpath_reserve[1], day)
        CLICK(driver, xpath_reserve[2])
        for i in range(5):
            SEND_KEYS(driver, xpath_reserve[2], Keys.BACK_SPACE)
        SEND_KEYS(driver, xpath_reserve[2],times)
        CLICK(driver, xpath_reserve[3])
        CLICK(driver, xpath_reserve[4])
    else:#下書き保存
        for i in range(3):
            CLICK(driver, xpath_save[i])

メインとなるプログラムです。

今まではスクリプトと読んでいましたが、もう関数化してしまったのであまりスクリプトに見えないですね。

seleniumスクリプトって複雑なことをしているようで実は以下の3つで実現できます。

・要素の取得
・文字列を入力
・ボタンをクリック

なので「文字列の入力」と「ボタンをクリック」を関数化し、それぞれの関数に「要素を取得」処理を入れれば見やすくなるのです。

これが理解できればhatenaprogram.pyはほぼ100%理解できます。

それでは細かい説明に移ります。

細かい説明

xpathのリストについて

コード上部にxpathのリストが書いてあります。

seleniumの自動処理はもともとスクリプトなので、要素を取得する順番も決まっています。

ですので、xpathをリスト化して上から順番に使用すればスクリプトと同じ用に動かすことができます。

要素の取得についてはxpath以外、例えばidやclass_nameでも可能ですが、関数化するにあたってはすべてxpathで取得するようにしたほうがコードが見やすくなります。

実行時間に関しては考えていません。

実際に使ってみるとわかりますが、人間が体感できないような差になると思うので、ここでは気にしないことにします。

read_file関数について

read_file関数は指定されたファイルを読み取り、タイトルと中身を返す関数です。

def read_file(filename):
    with open(filename) as f:
        s=f.readline()
        d=f.readline()
        d=f.read()
    return s,d
引数:ファイル名
返り値:タイトル(文字列)、中身(文字列)

readline()は一行ずつ読み取る処理、read()は全体を読み取る処理です。

ですので、ここで想定されているファイルは、

・1行目にタイトル
・3行目以降に内容

という形式になっています。

ここの処理部を変えることでどんな内容のファイルに対応するか変えることができます。

自分の好み似合わせて作り変えてください。

SEND_KEYS関数について

def SEND_KEYS(drivers, xpath, inputtext):
    text=WebDriverWait(drivers, 30).until(
        expected_conditions.visibility_of_element_located((By.XPATH, xpath))
    )
    text.send_keys(inputtext)
引数:ドライバ、xpath、入力する文字列
返り値:なし

この関数はxpathで指定した空欄に文字列を入力する関数です。

text=から始まる3行は要素が取得できるまで待つ処理になります。

この処理がない場合、画面表示が間に合わず途中でエラーになるときがあります。

なるべく入れるようにしましょう。

今回は最大30秒待つようになっています。

CLICK関数について

def CLICK(drivers, xpath):
    button=WebDriverWait(drivers, 30).until(
        expected_conditions.visibility_of_element_located((By.XPATH, xpath))
    )
    button.click()
引数:ドライバ、xpath
返り値:なし

こちらは取得した要素のボタンをクリックする関数です。

先程のSEND_KEYS関数と主な処理は同じです。

処理としては最後の1行が変わっているだけです。

resource_path関数について

def resource_path(relative_path):
    try:
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.dirname(__file__)
    return os.path.join(base_path, relative_path)

こちらはwebdriverを読み取るときに使う処理です。

この処理はこのプログラムでつかうというよりは、Pyinstallerを使って.exeなどの実行ファイルを作るときに必要な処理になります。

詳しくは以下の記事を御覧ください。

www.zacoding.com

main関数について

def main(userid,userpw,day,time,option,filename):
    driver = webdriver.Chrome(resource_path('./driver/chromedriver.exe'))
    weburl='https://blog.hatena.ne.jp/'
    driver.get(weburl)
    
    SEND_KEYS(driver, xpath[0], userid)
    SEND_KEYS(driver, xpath[1], userpw)
    CLICK(driver, xpath[2])
    
    CLICK(driver, xpath[3])
    
    title,arch=read_file(filename)
    SEND_KEYS(driver, xpath[4], title)
    SEND_KEYS(driver, xpath[5], arch)
    
    if option==1:#公開
        CLICK(driver, xpath_app)
    elif option==2:#予約投稿
        CLICK(driver, xpath_reserve[0])
        SEND_KEYS(driver, xpath_reserve[1], day)
        CLICK(driver, xpath_reserve[2])
        for i in range(5):
            SEND_KEYS(driver, xpath_reserve[2], Keys.BACK_SPACE)
        SEND_KEYS(driver, xpath_reserve[2],times)
        CLICK(driver, xpath_reserve[3])
        CLICK(driver, xpath_reserve[4])
    else:#下書き保存
        for i in range(3):
            CLICK(driver, xpath_save[i])
引数:ユーザID、ユーザパスワード、年月日、時刻、公開設定、ファイル名
返り値:なし

main関数にはGUIから入力された値を引数として処理が行われています。

公開設定をif文で分けている以外はスクリプトと同じ流れになっているので、理解はしやすいと思います。

最後に

これでjsonファイル、GUIプログラム、メインプログラムが完成しました。

はてなブログ自動投稿プログラムは完成です。

お疲れさまでした。

何かあればコメントをお願いします。

それでは。

Python-seleniumではてなブログ自動投稿プログラムを作ってみる⑤~結合編その2~

こんにちは、のちたままです。

はてなブログ自動投稿プログラム製作の第5回です。

今回はGUIを完成させましょう。

コード全体

import PySimpleGUI as sg
import calendar
import hatenaprogram
import json

sg.theme('Dark Blue 3')

def mod_day(year, month, day):
    if year or month or day:
        if int(day) > int(calendar.monthrange(int(year),int(month))[1]):
            print('日付が正しくありません')
            exit()
    month='0'+month
    day='0'+day
    s=year[-4:]+'-'+month[-2:]+'-'+day[-2:]
    return s

def mod_time(time, minutes):
    if time or minutes:
        if int(time)>24 or int(time)<0:
            print('時刻が正しくありません')
            exit()
    time='0'+time
    minutes='0'+minutes
    return time[-2:]+':'+minutes[-2:]

def userjson_save(user,userid,year,month,day,time,minutes,checkid,checkday,checktime):
    if checkid:
        user['Id']=userid
    if checkday:
        user['Daytime']['Year']=year
        user['Daytime']['Month']=month
        user['Daytime']['Day']=day
    if checktime:
        user['Daytime']['Time']=time
        user['Daytime']['Minutes']=minutes
    user['Check']['Id']=checkid
    user['Check']['Day']=checkday
    user['Check']['Time']=checktime
    f=open('user.json','w')
    json.dump(user,f)
    f.close()

def main():
    dict1={1:'公開',2:'予約投稿',3:'下書き保存'}
    f=open('user.json','r')
    user=json.load(f)
    f.close()
    layout=[
        [sg.Text('ID',size=(15,1)),sg.InputText(default_text=user['Id'], key='ID'),sg.Checkbox('保存', key='ID_CHECK',default=user['Check']['Id'])],
        [sg.Text('Password',size=(15,1)),sg.InputText(key='PW')],
        [sg.Text('ファイル',size=(15,1)),sg.InputText(key='FILE'),sg.FileBrowse()],
        [sg.Text('公開設定',size=(15,1)),sg.Radio(dict1[1],'RADIO1',default=True,key='1'),sg.Radio(dict1[2],'RADIO1',key='2'),sg.Radio(dict1[3],'RADIO1',key='3')],
        [sg.Text('日付',size=(15,1)),sg.InputText(size=(5,1),default_text=user['Daytime']['Year'],key='YEAR'),sg.Text('年'),sg.InputText(size=(5,1),default_text=user['Daytime']['Month'],key='MONTH'),sg.Text('月'),sg.InputText(size=(5,1),default_text=user['Daytime']['Day'],key='DAY'),sg.Text('日'),sg.Checkbox('日付を保存する',default=user['Check']['Day'],key='DAY_CHECK')],
        [sg.Text('時刻',size=(15,1)),sg.InputText(size=(5,1),default_text=user['Daytime']['Time'],key='TIME'),sg.Text('時'),sg.InputText(size=(5,1),default_text=user['Daytime']['Minutes'],key='MINUTES'),sg.Text('分'),sg.Text(size=(8,1)),sg.Checkbox('時刻を保存する',default=user['Check']['Time'],key='TIME_CHECK')],
        [sg.Submit(button_text='実行する')]
    ]

    window=sg.Window('hatena',layout)

    while True:
        event, value=window.read()
        if event=='実行する':
            option=1 if value['1'] else 2 if value['2'] else 3
            if not value['FILE']:
                show_message='ファイルが選択されていません'
                sg.popup(show_message)
                exit()
            show_message=value['FILE']
            show_message+='を'+dict1[option]+'します。\n'
            show_message+='OKを押してください。\n'
            day=mod_day(value['YEAR'],value['MONTH'],value['DAY'])
            time=mod_time(value['TIME'],value['MINUTES'])
            userjson_save(user,value['ID'], value['YEAR'],value['MONTH'],value['DAY'], value['TIME'], value['MINUTES'], value['ID_CHECK'], value['DAY_CHECK'], value['TIME_CHECK'])
            sg.popup(show_message)
            hatenaprogram.main(value['ID'], value['PW'], day, time, option, value['FILE'])
        
        if event is None:
            print('exit')
            break
    window.close()

if __name__ == '__main__':
    main()

細かい説明

各関数での処理を説明していきます。

mod_day関数について

mod_dayは入力された年月日の成形をしています。

def mod_day(year, month, day):
    if year or month or day:
        if int(day) > int(calendar.monthrange(int(year),int(month))[1]):
            print('日付が正しくありません')
            exit()
    month='0'+month
    day='0'+day
    s=year[-4:]+'-'+month[-2:]+'-'+day[-2:]
    return s
引数:year(年),month(月),day(日)
返り値:成形された年月日(文字列)

calendar.monthrange()は引数に年と月を入れることで、その月の初めの日(1日)の曜日とその月が何日あるかを返します。

ここで欲しい返り値はその月が何日あるかなので末尾に[1]をつけています。

厳密にやるなら、年月日の欄に何も入力されていない場合、入力された年月日のいづれかがおかしな値をとっている場合の処理も必要ですが、自分で使う分には特に問題ありません。

返り値としてはてなブログ用に成形された年月日の文字列を返し、これをmainの中でスクリプト側に渡します。

mod_time関数について

目的はmod_day()と同じです。

こちらでは時刻の成形を行っています。

def mod_time(time, minutes):
    if time or minutes:
        if int(time)>24 or int(time)<0:
            print('時刻が正しくありません')
            exit()
        if int(minutes)>59 or int(minutes)<0:
            print('時刻が正しくありません')
            exit()
    time='0'+time
    minutes='0'+minutes
    return time[-2:]+':'+minutes[-2:]
引数:time(時)、minutes(分)
返り値:整形された時刻(文字列)

これもmain関数内で返り値をスクリプトに渡します。

userjson_save関数について

こちらについては前回紹介したので割愛します。

以下の記事を御覧ください。

[https://nochitamama.hatenablog.com/entry/Python/autohatena4:embed:cite]

main関数と残りの部分について

def main():
    dict1={1:'公開',2:'予約投稿',3:'下書き保存'}
    f=open('user.json','r')
    user=json.load(f)
    f.close()
    layout=[
        [sg.Text('ID',size=(15,1)),sg.InputText(default_text=user['Id'], key='ID'),sg.Checkbox('保存', key='ID_CHECK',default=user['Check']['Id'])],
        [sg.Text('Password',size=(15,1)),sg.InputText(key='PW')],
        [sg.Text('ファイル',size=(15,1)),sg.InputText(key='FILE'),sg.FileBrowse()],
        [sg.Text('公開設定',size=(15,1)),sg.Radio(dict1[1],'RADIO1',default=True,key='1'),sg.Radio(dict1[2],'RADIO1',key='2'),sg.Radio(dict1[3],'RADIO1',key='3')],
        [sg.Text('日付',size=(15,1)),sg.InputText(size=(5,1),default_text=user['Daytime']['Year'],key='YEAR'),sg.Text('年'),sg.InputText(size=(5,1),default_text=user['Daytime']['Month'],key='MONTH'),sg.Text('月'),sg.InputText(size=(5,1),default_text=user['Daytime']['Day'],key='DAY'),sg.Text('日'),sg.Checkbox('日付を保存する',default=user['Check']['Day'],key='DAY_CHECK')],
        [sg.Text('時刻',size=(15,1)),sg.InputText(size=(5,1),default_text=user['Daytime']['Time'],key='TIME'),sg.Text('時'),sg.InputText(size=(5,1),default_text=user['Daytime']['Minutes'],key='MINUTES'),sg.Text('分'),sg.Text(size=(8,1)),sg.Checkbox('時刻を保存する',default=user['Check']['Time'],key='TIME_CHECK')],
        [sg.Submit(button_text='実行する')]
    ]

    window=sg.Window('hatena',layout)

    while True:
        event, value=window.read()
        if event=='実行する':
            option=1 if value['1'] else 2 if value['2'] else 3
            if not value['FILE']:
                show_message='ファイルが選択されていません'
                sg.popup(show_message)
                exit()
            show_message=value['FILE']
            show_message+='を'+dict1[option]+'します。\n'
            show_message+='OKを押してください。\n'
            day=mod_day(value['YEAR'],value['MONTH'],value['DAY'])
            time=mod_time(value['TIME'],value['MINUTES'])
            userjson_save(user,value['ID'], value['YEAR'],value['MONTH'],value['DAY'], value['TIME'], value['MINUTES'], value['ID_CHECK'], value['DAY_CHECK'], value['TIME_CHECK'])
            sg.popup(show_message)
            hatenaprogram.main(value['ID'], value['PW'], day, time, option, value['FILE'])
        
        if event is None:
            print('exit')
            break
    window.close()

if __name__ == '__main__':
    main()

main()内の上半分は前回説明したので割愛します。

今回はwhile文の中身を説明します。

if event=='実行する':というのは「実行すると書かれたボタンが押されたら」という意味になります。

ボタンが押されるとoption=1 if value['1'] else 2 if value['2'] else 3で公開設定を読み取ります。

この書き方は三項演算子という書き方になります。

これは以下のものと同じ意味です。

if value['1']:
   option=1
elif value['2']:
   option=2
else:
   option=3

ifが2つ以上あると三項演算子は可読性が下がるのであまりおすすめはしません。

続いて、show_messageがいくつも連なっている部分ですが、ファイルが指定されていない場合にプログラムを終了するif文と、ファイルを指定した際にポップアップを表示させるコードです。

特に詳しく説明するところでもないです。

あとは先程説明したmod_day,mod_timeに値を渡して返り値をday,timeに入れています。

これをスクリプトの関数、今回はスクリプトをhatenaprogram.pyという名前にしたので、そのなかのmain関数(hatenaprogram.main)に各値を渡しています。

最後のif __name__=='__main__'については以下の記事が参考になると思います。

note.nkmk.me

最後に

長々と読んでいただきありがとうございます。

これでGUI側は完成です。

色々と荒い部分はありますが、趣味で作る程度ならこれくらいでも良いと自分では思います。

60点でもいいからまずは完成させる方が大切です。

あとはスクリプト(hatenaprogram.py)の修正をすれば完成になります。

それではまた次回をお待ちください。

Python-seleniumではてなブログ自動投稿プログラムを作ってみる④~ユーザ情報の保存~

こんにちは、のちたままです。

はてなブログ自動投稿プログラム作成第4回です。

前回を見ていない方は先にご覧ください。

nochitamama.hatenablog.com

今回は少しずれてユーザ情報についての話です。

IDや日付、時刻など何度も同じものを入力するのは非常に手間なので、前回入力した情報を保存しておいて起動時に同じ値が使えるようにしておくと便利です。

今回はそんな話です。

(GUIを完成させるにはこの話をしておかなければいけないのを忘れていました。)

user.jsonについて

前回、プログラムの構成について話をしたと思います。

その時に、GUIからuser.jsonというところに矢印が相互についていたと思います。

f:id:nochitamama:20210317172648p:plain

あのuser.jsonのお話です。

user.jsonというのは、json形式のファイルです。

自分はよくユーザ情報の保存に使っています。

本来は.iniとか使った方がよいと思いますが、使い勝手が良いのでこちらを使っています。

というか趣味で作っているものなので別に形式は問わないといったスタンスです。

json形式はpythonの辞書型のような形をしています。

細かく言うと異なるのですが、keyと値を使うのは共通しています。

実際に作成したjsonファイルを以下に載せておきます。

{
    "Id": "",
    "Check": {
        "Id": true,
        "Day": true,
        "Time": false},
    "Daytime": {
        "Year": "",
        "Month": "",
        "Day": "",
        "Time": "",
        "Minutes": ""
    }
}

このようにKeyと値で構成されるのでpythonを触っている人は扱いやすいと思います。

pythonjsonを使う方法

ここでは簡単にpythonjson形式のファイルを扱う方法を紹介します。

必要がなければGUIの修正の方まで飛ばしてください。

jsonの読み込み

pythonではありがたいことにjsonのライブラリが標準で存在します。

以下のプログラムで指定したファイルを読み込むことができます。

import json
f=open('user.json','r')
j=json.load(f)
print(j)
f.close()
{'Id': '', 'Check': {'Id': True, 'Day': True, 'Time': False}, 'Daytime': {'Year': '', 'Month': '', 'Day': '', 'Time': '', 'Minutes': ''}}
{'Check': {'Day': True, 'Id': True, 'Time': False},
 'Daytime': {'Day': '',  
'Minutes': '', 'Month': '', 'Time': '', 'Year': ''}, 'Id': ''}
辞書型と同じで順番に出てくれるわけではないみたいですね。

今回はf=open()という書き方をしましたが、文字コードの指定など細かい設定が必要ならwith open()を使う方がよいと思います。

使い方はここでは割愛します。

jsonへの書き込み

書き込みも読み込みとあまり差はありません。

唯一の違いはjson形式のファイルで出力するにはjson.dumpが必要ということでしょうか。

import json
f2=open('user.json','w')
j["Id"]='nochitamama'
json.dump(j,f2)
f2.close()
{'Check': {'Day': True, 'Id': True, 'Time': False},
 'Daytime': {'Day': '', 'Minutes': '', 'Month': '', 'Time': '', 'Year': ''},
 'Id': ''}
{'Check': {'Day': True, 'Id': True, 'Time': False},
 'Daytime': {'Day': '', 'Minutes': '', 'Month': '', 'Time': '', 'Year': ''},
 'Id': 'nochitamama'}

この読み込み、書き込みのやり方さえ覚えれば今回の内容は理解できますのでご安心ください。

それではGUIのコードに組み込んでいきましょう。

GUIのコードに組み込む

それではまずGUI側のコードにjsonファイルを読み込み処理を加えます。

def main():
    dict1={1:'公開',2:'予約投稿',3:'下書き保存'}
    f=open('user.json','r')
    user=json.load(f)
    f.close()
    layout=[
        [sg.Text('ID',size=(15,1)),sg.InputText(default_text=user['Id'], key='ID'),sg.Checkbox('保存', key='ID_CHECK',default=user['Check']['Id'])],
        [sg.Text('Password',size=(15,1)),sg.InputText(key='PW')],
        [sg.Text('ファイル',size=(15,1)),sg.InputText(key='FILE'),sg.FileBrowse()],
        [sg.Text('公開設定',size=(15,1)),sg.Radio(dict1[1],'RADIO1',default=True,key='1'),sg.Radio(dict1[2],'RADIO1',key='2'),sg.Radio(dict1[3],'RADIO1',key='3')],
        [sg.Text('日付',size=(15,1)),sg.InputText(size=(5,1),default_text=user['Daytime']['Year'],key='YEAR'),sg.Text('年'),sg.InputText(size=(5,1),default_text=user['Daytime']['Month'],key='MONTH'),sg.Text('月'),sg.InputText(size=(5,1),default_text=user['Daytime']['Day'],key='DAY'),sg.Text('日'),sg.Checkbox('日付を保存する',default=user['Check']['Day'],key='DAY_CHECK')],
        [sg.Text('時刻',size=(15,1)),sg.InputText(size=(5,1),default_text=user['Daytime']['Time'],key='TIME'),sg.Text('時'),sg.InputText(size=(5,1),default_text=user['Daytime']['Minutes'],key='MINUTES'),sg.Text('分'),sg.Text(size=(8,1)),sg.Checkbox('時刻を保存する',default=user['Check']['Time'],key='TIME_CHECK')],
        [sg.Submit(button_text='実行する')]
    ]
上のコードはGUIのコードの一部です。

3-5行目あたりにjsonファイルの読み込みを入れています。

また、layoutの中にdefault_textとかdefaultという文字がいくつか見えると思います。

default_textGUIを開いたときにデフォルト表示させる文字列です。

Checkboxはdefaultで設定します。

このように書くことで、user.jsonから読み取った情報を各デフォルト値として設定することができます。

では続いて書き込み側です。

def userjson_save(user,userid,year,month,day,time,minutes,checkid,checkday,checktime):
    if checkid:
        user['Id']=userid
    if checkday:
        user['Daytime']['Year']=year
        user['Daytime']['Month']=month
        user['Daytime']['Day']=day
    if checktime:
        user['Daytime']['Time']=time
        user['Daytime']['Minutes']=minutes
    user['Check']['Id']=checkid
    user['Check']['Day']=checkday
    user['Check']['Time']=checktime
    f=open('user.json','w')
    json.dump(user,f)
    f.close()

userの各値に対して引数を割り当てています。

if文がいくつか入っているのは、チェックボックスの状態で上書きするかどうかを変えているからです。

また、最後の3行でuser.jsonに上書きをしています。

これにより、自分が入力した情報がuser.jsonに上書きされ、再度プログラムを起動したときに前回入力した情報がデフォルトで表示されるようになります。

あとはuserjson_saveをwhileの中に組み込めば完成です。

最後に

ユーザ情報の読み込み、書き込み(保存)の組み込みが終わりました。

次回はついにGUIを完成させましょう。

それが終わったらスクリプトを書き直して終わりです。

それでは。


nochitamama.hatenablog.com


Python-seleniumではてなブログ自動投稿プログラムを作ってみる③~結合編その1~

こんにちは、のちたままです。

今回は自動投稿プログラム作成の第3回です。

第1回、第2回でスクリプトGUIを作成したので、今回はそれらを結合していきます。

ただ、結合編は長くなりそうなので複数回に分けます。

気長にお待ちください。

第1回、第2回をまだご覧になっていない方は先にそちらをご覧ください。

nochitamama.hatenablog.com

nochitamama.hatenablog.com

今回の内容

今回はGUI側の修正になります。

GUIで入力した値をどうやってスクリプト側に渡すの?ということを説明していきます。

とはいってもPySimpleGUIの使い方になってしまいそうです。

GUIのコードを完成させるわけではないのでご注意ください。

プログラムの構成について

今回作成しているプログラムは以下のような構成になっています。

今更な感じはありますが、紹介しておこうと思います。

GUI,main,user.jsonは別のファイルです。(gui.py,main.pyみたいなものです。)

f:id:nochitamama:20210317172648p:plain

まず初めに、ユーザがソフトを起動するとGUIがuser.jsonから情報を読み取った状態で現れます。

ユーザが必要な情報を入力し実行ボタンを押すと、mainが動きます。

それに合わせて、user.jsonの上書き保存が実行されます。

第2回で保存と書かれたチェックボックスがあったかと思いますが、あれがuser.jsonに保存される内容になります。

user.jsonの読み取り、保存に関しては別記事で紹介します。

ということでGUIで入力された値をmainに渡すには、PySimpleGUIでどうやって書けばよいのかという内容を説明します。

GUIで入力した値を渡す方法

話を戻します。

GUIから値を渡す方法を知るためには、入力された値がどのように扱われているかを知る必要があります。

前回のプログラムの中に、valueという変数があったのを覚えていますか?

while文の中に入っているやつです。
nochitamama.hatenablog.com まずはそのvalueをprintで出力してみましょう。

すると以下のような出力が得られました。

{0: '', 1: True, 2: '', 3: '', 'Browse': '', 4: True, 5: False, 6: False, 7: '', 8: '', 9: '', 10: True, 11: '', 12: '', 13: True}

これが何を意味するかというと、valueは辞書型であるということです。

4: True, 5: False, 6: Falseの部分に注目してほしいのですが、これは公開設定の情報であることがわかります。

ラジオボタンで選択するやつです。

ほかにもTrueと入っている箇所はチェックボックスかなということが予想できます。

さて、valueについてわかったところで、どうやって値を渡していくかという話です。

察しの良い方は気づいてしまうと思いますが、結論から言うとkeyを渡せばよいのです。

valueは辞書型なのでkeyと値が入っています。

今はkeyが0~13の数字になっていますが、このkeyを自分がわかりやすいものに書き換えてしまえばよいのです。

例えば、以下のような感じです。

{'ID': '', 'ID_CHECK': True, 'PW': '', 'FILE': '', 'Browse': '', '1': True, '2': False, '3': False, 'YEAR': '', 'MONTH': '', 'DAY': '', 'DAY_CHECK': True, 'TIME': '', 'MINUTES': '', 'TIME_CHECK': True}

keyをこのように修正できればあとはvalue[各key]で渡すだけです。

それではGUIのコードを修正していきましょう。

GUIを修正する

valueのことを理解できれば修正に関しては簡単です。

入力されるアイテムのところにkeyを設定するだけです。

以下に変更前、変更後のコードを載せておきます。

違いを見ながらkeyの設定方法を見てください。

・変更前

layout=[
    [sg.Text('ID',size=(15,1)),sg.InputText(''),sg.Checkbox('保存',default=True)],
    [sg.Text('Password',size=(15,1)),sg.InputText('')],
    [sg.Text('ファイル',size=(15,1)),sg.InputText(''),sg.FileBrowse()],
    [sg.Text('公開設定',size=(15,1)),sg.Radio('公開','RADIO1',default=True),sg.Radio('予約投稿','RADIO1'),sg.Radio('下書き保存','RADIO1')],
    [sg.Text('日付',size=(15,1)),sg.InputText('',size=(5,1)),sg.Text('年'),sg.InputText('',size=(5,1)),sg.Text('月'),sg.InputText('',size=(5,1)),sg.Text('日'),sg.Checkbox('日付を保存する',default=True)],
    [sg.Text('時刻',size=(15,1)),sg.InputText('',size=(5,1)),sg.Text('時'),sg.InputText('',size=(5,1)),sg.Text('分'),sg.Text('',size=(8,1)),sg.Checkbox('時刻を保存する',default=True)],
    [sg.Submit(button_text='実行する')]
]

・変更後

layout=[
    [sg.Text('ID',size=(15,1)),sg.InputText(key='ID'),sg.Checkbox('保存',default=True,key='ID_CHECK')],
    [sg.Text('Password',size=(15,1)),sg.InputText(key='PW')],
    [sg.Text('ファイル',size=(15,1)),sg.InputText(key='FILE'),sg.FileBrowse()],
    [sg.Text('公開設定',size=(15,1)),sg.Radio(dict1[1],'RADIO1',default=True,key='1'),sg.Radio(dict1[2],'RADIO1',key='2'),sg.Radio(dict1[3],'RADIO1',key='3')],
    [sg.Text('日付',size=(15,1)),sg.InputText(size=(5,1),key='YEAR'),sg.Text('年'),sg.InputText(size=(5,1),key='MONTH'),sg.Text('月'),sg.InputText(size=(5,1),key='DAY'),sg.Text('日'),sg.Checkbox('日付を保存する',default=True,key='DAY_CHECK')],
    [sg.Text('時刻',size=(15,1)),sg.InputText(size=(5,1),key='TIME'),sg.Text('時'),sg.InputText(size=(5,1),key='MINUTES'),sg.Text('分'),sg.Text(size=(8,1)),sg.Checkbox('時刻を保存する',default=True,key='TIME_CHECK')],
    [sg.Submit(button_text='実行する')]
]

このように書けばvalue['ID']でIDを取り出すことができるようになります。

これらをスクリプト側に渡せば解決です。

スクリプト側も関数化して、引数にvalue[各key]を渡せばmain側で使えるようになります。

例えば、main側でID,PWの情報が必要なら

main(value['ID'],value['PW'])

みたいにすればmain側に渡すことができます。

最後に

完成に少しずつ近づいてきています。

残りは、GUIの修正、スクリプトの関数化、user.jsonの作成ですね。

それでは。

Python-seleniumではてなブログ自動投稿プログラムを作ってみる②~GUI編~

こんにちは、のちたままです。

今回は、以前記事にしたはてなブログ自動投稿プログラムの続きになります。

まだ前回の記事を読んでいない場合は、先にそちらをお読みください。

nochitamama.hatenablog.com

※今回は画面だけの作成なので、実行ボタンを押しても実際に動作するわけではありません。 スクリプトGUIの接続はまた別記事で書く予定です。

今回の目次はこちら

GUIの外観

今回作成したGUIの外観はこのようになっています。

f:id:nochitamama:20210316130104p:plain

今後作るうちに変わっていくかもしれませんが、その場合は更新していこうと思います。

また、今回はPySimpleGUIを使用して作成しています。

複雑な動作をするGUIでなければtkinterよりもコードが書きやすいのでこちらを使用しました。

PySimpleGUIの基本とインストール方法については以下の2つの記事をご覧ください。

qiita.com

nochitamama.hatenablog.com

GUIに必要な機能

GUIには以下の5つの機能をつけることにしました。

・ID,Passwordの入力
・ファイルの指定
・公開設定(公開、予約投稿、下書き保存)
・公開、予約投稿時の日時指定
・実行ボタン

コードについて

以下がGUI全体のコードになります。

最初に話したようにこれだけではポップアップが出るだけで何も動作しないのでご注意ください。

import PySimpleGUI as sg

sg.theme('Dark Blue 3')

layout=[
    [sg.Text('ID',size=(15,1)),sg.InputText(''),sg.Checkbox('保存',default=True)],
    [sg.Text('Password',size=(15,1)),sg.InputText('')],
    [sg.Text('ファイル',size=(15,1)),sg.InputText(''),sg.FileBrowse()],
    [sg.Text('公開設定',size=(15,1)),sg.Radio('公開','RADIO1',default=True),sg.Radio('予約投稿','RADIO1'),sg.Radio('下書き保存','RADIO1')],
    [sg.Text('日付',size=(15,1)),sg.InputText('',size=(5,1)),sg.Text('年'),sg.InputText('',size=(5,1)),sg.Text('月'),sg.InputText('',size=(5,1)),sg.Text('日'),sg.Checkbox('日付を保存する',default=True)],
    [sg.Text('時刻',size=(15,1)),sg.InputText('',size=(5,1)),sg.Text('時'),sg.InputText('',size=(5,1)),sg.Text('分'),sg.Text('',size=(8,1)),sg.Checkbox('時刻を保存する',default=True)],
    [sg.Submit(button_text='実行する')]
]

window=sg.Window('hatena',layout)

while True:
    event, value=window.read()
    if event=='実行する':
        show_message='しばらくお待ちください。\n'
        sg.popup(show_message)
        
    if event is None:
        print('exit')
        break

window.close()

PySimpleGUIについて少し補足をしておきます。

PySimpleGUIではlayoutの中にボタンやTextなどを置いていきます。

layout=[
    [sg.Text('ID',size=(15,1)),sg.InputText(''),sg.Checkbox('保存',default=True)],
    [sg.Text('Password',size=(15,1)),sg.InputText('')],
    [sg.Text('ファイル',size=(15,1)),sg.InputText(''),sg.FileBrowse()],
    [sg.Text('公開設定',size=(15,1)),sg.Radio('公開','RADIO1',default=True),sg.Radio('予約投稿','RADIO1'),sg.Radio('下書き保存','RADIO1')],
    [sg.Text('日付',size=(15,1)),sg.InputText('',size=(5,1)),sg.Text('年'),sg.InputText('',size=(5,1)),sg.Text('月'),sg.InputText('',size=(5,1)),sg.Text('日'),sg.Checkbox('日付を保存する',default=True)],
    [sg.Text('時刻',size=(15,1)),sg.InputText('',size=(5,1)),sg.Text('時'),sg.InputText('',size=(5,1)),sg.Text('分'),sg.Text('',size=(8,1)),sg.Checkbox('時刻を保存する',default=True)],
    [sg.Submit(button_text='実行する')]
]

内側の鍵カッコ[]で囲われた部分は同じ行に表示するアイテムです。

例えば一つ目の[sg.Text('ID',size=(15,1)),sg.InputText(''),sg.Checkbox('保存',default=True)]は、以下のアイテムを同じ行に設置します。

・「ID」という文字(宇余白を含め15文字分を1行生成)
・入力テキスト(InputTextのところ)
・「保存」と文字が横に書かれたチェックボックス

ちょうど図の赤枠で囲んだ部分になります。

f:id:nochitamama:20210316134248p:plain

このように行単位で生成していくので、簡単なGUIであればコードを見やすく書くことができます。

最後に

今回はプログラムを使う際のGUIについて作ってみました。

基本的なもので作れるもんだなあという感想です。

これからどんどん使いやすいように進化させていくので、この記事で書いたものと最終的なものは大きく変わってしまうかもしれません。

それではまた次回。


PySimpleGUIの公式ドキュメントは以下のURLからご覧ください。

pysimplegui.readthedocs.io