Pythonで基本情報技術者試験の過去問PDFを一気にダウンロードする話

# 概要
Python の urllib パッケージを用いて[基本情報技術者試験（FE）](https://www.jitec.ipa.go.jp/1_11seido/fe.html)の過去問PDFをダウンロードする．
# 詳細
基本情報技術者試験の過去問は IPA の公式 Web サイトで[公開されている](https://www.jitec.ipa.go.jp/1_04hanni_sukiru/_index_mondai.html)．しかし，問題や解答は年度毎に掲載されており，いちいち各年度のページに移動してダウンロードしなければならない．この煩わしい手間を省くため，Python の urllib パッケージを用いて問題や解答を一気にダウンロードする．
## URLを調べる
過去問の掲載ページを見てみると，例えば平成27年度春期試験の URL は次の通りである．

- 午前試験
    - 問題: https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2015h27_1/2015h27h_fe_am_qs.pdf
    - 解答: https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2015h27_1/2015h27h_fe_am_ans.pdf
- 午後試験
    - 問題: https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2015h27_1/2015h27h_fe_pm_qs.pdf
    - 解答: https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2015h27_1/2015h27h_fe_pm_ans.pdf
    - 講評: https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2015h27_1/2015h27h_fe_pm_cmnt.pdf

過去問の URL の構造は
`https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_`
に加えて
`[西暦][和暦]_[1 OR 2]/[西暦][和暦][h OR a]_fe_[am OR pm]_[qs OR ans OR cmnt].pdf`
となっていることがわかる．

## 実装
あまり深く考えずに for 文を多用してコードを書いてみた．[^1]

[^1]: このプログラム `kakomon.py` では，元号を扱うために Python パッケージ Japanera を使用している．Japanera は Python の標準ライブラリに含まれていないため，例えば `pip install japanera` を実行するなどして，別途インストールする必要がある．

```py:kakomon.py
from japanera import Japanera
from datetime import date
import urllib.request

def download(startYear, endYear):
    # URLの共通（前半）部分
    urlbase = "https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_"
    # Japanera
    jpnera = Japanera()
    # 春期と秋期
    seasons = {1:"h", 2:"a"}
    months = {1:4, 2:10}
    # 時間帯
    times = ["am", "pm"]
    # 問題・解答・講評
    qacs = ["qs", "ans", "cmnt"]

    print("downloading...")

    # [startYear]年度から[endYear]年度のPDF（問題・解答・講評）をダウンロード
    for year in range(startYear, endYear + 1):
        for season in seasons.items():
            (sKey, sValue) = season
            # 年度の文字列（例: 2009h21）
            nendo1 = str(year) + jpnera.strftime(date(year, 4, 1), "%-a%-o").lower()
            nendo2 = str(year) + jpnera.strftime(date(year, months[sKey], 1), "%-a%-o").lower()

            for time in times:
                for qac in qacs:
                    # 講評は午後のみ
                    if not (time == "am" and qac == "cmnt"):
                        try:
                            url = urlbase + nendo1 + "_" + str(sKey) + "/" + nendo2 + sValue + "_fe_" + time + "_" + qac + ".pdf"
                            filename = nendo2 + sValue + "_fe_" + time + "_" + qac + ".pdf"
                            urllib.request.urlretrieve(url, "{0}".format(filename))
                        except urllib.error.HTTPError:
                            # ダウンロードできなかったファイル名を表示
                            print("Error: " + filename)

    print("finished!")

if __name__ == "__main__":
    # [第1引数]年度から[第2引数]年度のPDF（問題・解答・講評）をダウンロードします
    download(2009, 2019)
```

上記を実行すると，PDF ファイルが得られるとともに次のエラーメッセージが出る（2022年4月29日時点）．

```
Error: 2011h23h_fe_am_qs.pdf
Error: 2011h23h_fe_am_ans.pdf
Error: 2011h23h_fe_pm_cmnt.pdf
Error: 2011h23h_fe_pm_qs.pdf
Error: 2011h23h_fe_pm_ans.pdf
```

これは次の事実による．

- 東日本大震災の影響で平成23年度は春期試験が無く，代わりに**特別試験**が行われた．

エラーの出た，すなわちプログラムの例外に該当する年度・時期の過去問については，手動でダウンロードするか上記プログラムを書き換えて手に入れる必要がある．例えば，筆者は次のようなプログラムを用いた．

```py:kakomon_revised.py
import urllib.request

def download():
    # 2011年度特別試験URLの共通（前半）部分:ファイル名の共通部分
    urlbases = {"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2011h23_1/2011h23tokubetsu_fe_":"2011h23tokubetsu_fe_"}
    # 時間帯
    times = ["am", "pm"]
    # 問題・解答・講評
    qacs = ["qs", "ans", "cmnt"]

    print("downloading...")

    # 2011年度特別試験のPDF（問題・解答・講評）をダウンロード
    for urlbase in urlbases:
        for time in times:
            for qac in qacs:
                # 講評は午後のみ
                if not (time == "am" and qac == "cmnt"):
                    try:
                        url = urlbase + time + "_" + qac + ".pdf"
                        filename = urlbases[urlbase] + time + "_" + qac + ".pdf"
                        urllib.request.urlretrieve(url, "{0}".format(filename))
                    except urllib.error.HTTPError:
                        # ダウンロードできなかったファイル名を表示
                        print("Error: " + filename)

    print("finished!")

if __name__ == "__main__":
    download()
```

# 余談
## 初版投稿時（2019年12月31日）
令和2年度から午後試験に変更があり，プログラム言語は [COBOL が廃止で Python が追加される](https://www.ipa.go.jp/about/press/20190124.html)らしい．出題数，解答数，配点等も変わるとのこと．
## 第2版投稿時（2022年4月29日）
つい先日，[基本情報技術者試験と情報セキュリティマネジメント試験の通年試験化](https://www.jitec.ipa.go.jp/1_00topic/topic_20220425.html)について公表された．変更後の試験は2023年4月から開始される．変更点はいろいろあるが，基本情報技術者試験の午後問題（新試験では「科目B試験」と呼ばれる）では，
>個別プログラム言語（C、Java、Python、アセンブラ言語、表計算ソフト）による出題は、普遍的・本質的なプログラミング的思考力を問う擬似言語による出題に統一します。

とのことだ．ついこの間 Python が追加されたと思ったら，擬似言語に取って代わられてしまうらしい．
# 参考
- [PythonでWeb上のファイルをダウンロードする - Qiita](https://qiita.com/yuukiclass/items/88e9ac6c5a3b5ab56cc4)
- [nagataaaas/Japanera: Easy Tools for Japanese Era System](https://github.com/nagataaaas/Japanera)