きゃっとぐるーぶ

忘れてもいいようにメモを取っても、メモを取ったことを忘れる男の備忘録

Pythonでタイピング練習プログラム

あいかわらずPythonスクレイピングばかりしているわけですが、テーブルタグのスクレイピングが苦手なので、練習がてらに英単語をスクレイプした際に出来たデータをつかい英単語練習用プログラムを作ってみました。単語のスペルをタイプし、その単語の意味も表示される。タイプミスすると色が変わります。

ところで、私はプログラミングでなにがしたいのでしょうか・・・。

f:id:catgroove:20190529215416j:plain:w300

わたし(おかず)のプログラミング学習目的に作られたスクリプトです

import re
import requests
import json
import random
import sys
import os
import string

from time import sleep
from bs4 import BeautifulSoup
from pathlib import Path
from itertools import zip_longest
from collections import defaultdict


SAVE_PATH = Path(os.path.dirname(__file__))
FILE_PATH = SAVE_PATH / 'eigoduke.json'
typo_cnt_dict = defaultdict(int)
typo_string_dict = defaultdict(list)

keys = ['chu1', 'chu2', 'chu3']
level = {'chu1': '中学1年生・英単語',
         'chu2': '中学2年生・英単語',
         'chu3': '中学3年生・英単語',
         'kou1': '高校1年生・英単語',
         'kou2': '高校2年生・英単語',
         'kou3': '高校3年生・英単語'
         }


def pageDownload(url):
    session = requests.session()
    session.headers.update({"User-Agent":'Mozilla/5.0 (X11; Linux x86_64) \
        AppleWebKit/537.36 (KHTML, like Gecko)\
              Chrome/73.0.3683.75 Safari/537.36'})
    r = session.get(url)
    if r.status_code == 200:
        r.encoding = r.apparent_encoding
        soup = BeautifulSoup(r.text, 'lxml')
        return soup
    return False


def eng_word_list(soup):
    return [
        eitango_word.text
        for eitango_word in soup.select('.hpb-cnt-tb-cell3')
        if re.findall(r'[a-zA-Z0-9]+', eitango_word.text)
        ]


def eng_meaning_list(soup):
    return [eng_meaning.text for eng_meaning in soup.select('.hpb-cnt-tb-cell4')]


def save_dict(arg):
    with open(FILE_PATH, 'w') as f:
        json.dump(arg, f, ensure_ascii=False, indent=4)


def create_engdict():
    eng_words = []
    gakunen = [g + str(i) for g in ["chu", "kou"] for i in range(1, 4)]

    for num in gakunen:
        soup = pageDownload(f'http://www.eigo-duke.com/tango/{num}')
        eng_words.append([eng_word_list(soup), eng_meaning_list(soup)])
        sleep(1)

    eng_word_meaning_dict = dict()
    _ = [eng_word_meaning_dict.update({gaku: words}) for gaku in gakunen for words in eng_words]

    save_dict(eng_word_meaning_dict)

    return eng_word_meaning_dict


def load_eng_word_meaning_dict():
    if FILE_PATH.exists():
        with open(FILE_PATH, 'r') as f:
            eng_word_meaning_dict = json.load(f)
        return eng_word_meaning_dict
    sys.stdout.write('英単語リストを取得します。\n')
    sys.stdout.write('取得には数分かかります\n')
    sys.stdout.flush()
    return create_engdict()


def at_random_question(eng_word_meaning_dict, gakunen, num):
    return random.sample(eng_word_meaning_dict[gakunen][0], num).pop()


def lookfor_key_index(arg, eng_word_meaning_dict):
    # 問題をrandomで選ぶため、問題の属するkeyとindexを取得する
    for key in eng_word_meaning_dict.keys():
        for idx, value in enumerate(eng_word_meaning_dict[key][0]):
            if arg in value:
                return key, idx
            continue


def display_question(q, level, key, meaning):
    msg = f'''
    出題:{level[key]}
    意味:{meaning}
    > {q}
    '''
    ans = input(msg)

    return ans


def typo_chenge_fg_color(char):
    # 文字色を赤
    char = f'\033[31m{char}\033[0m'
    return char


def is_first_char_upper_case(arg):
    # 先頭文字が大文字か判定
    if arg[0] in string.ascii_uppercase:
        return True
    return False


def typo_string_cnt(qes, ans):
    qes_counted_num = len(qes)
    ans_counted_num = len(ans)

    qes_lower_case = list(qes.lower())
    ans_lower_case = list(ans.lower())

    if qes_counted_num > ans_counted_num:
        # タイプが足りなければ*で埋める
        for i, word in enumerate(zip_longest(qes_lower_case, ans_lower_case), start=0):
            if word[1] is None:
                ans_lower_case.append('*')
            else:
                continue
        return "".join(ans_lower_case)
    elif qes_counted_num < ans_counted_num:
        # タイプが多すぎれば色を変える
        for i, word in enumerate(zip_longest(qes_lower_case, ans_lower_case), start=0):
            if word[0] is None:
                ans_lower_case[i] = typo_chenge_fg_color(word[1])
                return "".join(ans_lower_case)
            else:
                continue
    return ans


def typo_uppercase(qes, ans):
    if not is_first_char_upper_case(qes):
        # firstなら
        qes = list(qes.lower())
        ans = list(ans.lower())
    else:
        # Firstなら
        qes = list(qes)
        ans = list(ans)

    for i, word in enumerate(zip(qes, ans), start=0):
        if word[0] != word[1]:
            ans[i] = typo_chenge_fg_color(ans[i].upper())
    return "".join(ans)


def is_typo(qes, ans):
    # 正誤判定
    if qes == ans:
        return True
    return False


def typo_counter(key):
    # TYPOをカウント
    typo_cnt_dict[key] += 1


def typo_string_record(key, ans):
    # TYPOの入力文字列を記録
    typo_string_dict[key].append(ans)


def typo_cnt_msg():
    print('')
    print('TYPO回数')
    for item in typo_cnt_dict.items():
        key, value = item
        msg = f'{key}:\t {value}回'
        history = " ".join(typo_string_dict[key])
        print(msg, '\t' , history)
    else:
        print()


def retry_msg(retry):
    if int(retry) > 0:
        sys.stdout.write(f'ReTry: {retry + 1}/3\n')


def question():
    eng_word_meaning_dict = load_eng_word_meaning_dict()

    key = random.choice(keys)
    qes = at_random_question(eng_word_meaning_dict, key, 1)

    result = lookfor_key_index(qes, eng_word_meaning_dict)
    # {'chu1':[['apple','orange'],['りんご', 'オレンジ']], 'chu2': [[],[]]}
    meaning = eng_word_meaning_dict[result[0]][1][result[1]]

    retry = 0
    while retry < 3:
        ans = display_question(qes, level, key, meaning)
        retry_msg(retry)
        if is_typo(qes, ans):
            break
        else:
            ans = typo_string_cnt(qes, ans)
            # typoした回数を数える
            typo_counter(qes)
            # typoした文字を大文字、かつ、色付けをする
            ans = typo_uppercase(qes, ans)
            # typo履歴 最後に表示
            typo_string_record(qes, ans)
            sys.stdout.write("TYPO=> " + ans + '\n')
            retry += 1


def yes_no_select(choice='no'):
    while True:
        msg = 'exit? [Y]es/[N]o (default: no)'
        choice = input(msg).lower()
        if choice in ('y', 'ye', 'yes'):
            return True
        elif choice in ('n', 'no'):
            return False


def main():
    try:
        while True:
            question()
    except KeyboardInterrupt:
        if yes_no_select():
            typo_cnt_msg()
            sys.exit(0)
        else:
            main()

if __name__ == '__main__':
    main()

Pythonスタートブック [増補改訂版]

Pythonスタートブック [増補改訂版]