デジタル推進課

KNIME・Excel Macro・Power Automateなど日々の業務で使用できる自動化ツールを中心に書き綴ります

Python - 情報処理試験 サイト内にあるPDFを一括でダウンロードする!! 123ファイルが2分でダウンロードできたよ!

 

 

 

はじめに

こんにちは、まっきーです。

たまにはKNIMEではないTopicも書いてみようと思います。ウェブスクレイピングについての備忘録です。ウェブサイトにいくつもリンクがあり、その中から欲しいPDFだけを探してダウンロードしてみたいと思います。

Pythonについてはまだまだ勉強中なので、かなり不器用な書き方をしていることがあると思います。ぜひアドバイスいただければともいます。

 

実行環境

よく環境が違うことで動かなくなるということが起こるので、実行環境を記載しておこうと思います。

OS:Windows 10

PythonPython 3.7.3

開発環境:Jupyter Notebook

モジュール

beautifulsoup4:4.7.1 

 

やりたいこと

IT系の資格で有名なものに、基本情報技術者試験(FE)、応用情報技術者試験(AP)などがあると思います。私は今度、IT系の資格の一つであるデータベーススペシャリスト試験(DB)に挑戦しようとしています。

情報処理技術者試験を受ける際、いつも過去問題をダウンロードするのですが、1回の試験で問題・回答・講評があり、なおかつ午前と午後で分かれているので何個ダウンロードさせる気だよ!となります。

そんな面倒な作業はPythonに自動でやってもらおうと思います。

 

対象のURL・ファイル

下記のサイトからダウンロードします。

IPA 独立行政法人 情報処理推進機構:問題冊子・配点割合・解答例・採点講評(2018、平成30年)

このサイトにはダウンロードしたいデータベーススペシャリスト試験の過去問題だけでなく、他の試験も同様に含まれています。その中で、データベーススペシャリスト試験(DB)だけを抜き出してダウンロードします。

f:id:makkynm:20200802181542p:plain

やりたいこと - データベーススペシャリストのPDFを一括ダウンロード

 

全体フロー

全体のフローはこんな感じです。

  1. 必要モジュールのImport 
  2. サイトのHTMLを取得・aタグを抜き出す
  3.  aタグからリンクだけを抽出
  4. 「.pdf」で終わるものについてのみ取得
  5.  データベーススペシャリスト試験のPDFのみを取得
  6. 相対URLから絶対URLに変換
  7.  URLからファイル名を取得
  8.  保存先のディレクトリを作成
  9.  ダウンロード実行

Step1 - 必要モジュールのImport

今回使うのは下記のモジュールです。

ウェブスクレイピング用にBeautiful Soup

パスの処理にOS、urljoin

ポーズ用にtime

という形です。

from bs4 import BeautifulSoup
import urllib.request as req
import urllib
import os
import time
from urllib.parse import urljoin

 

Step2 - サイトのHTMLを取得・aタグを抜き出す

コード

まずはサイトのHTMLからaタグを抜き出してきます。urlの欄に今回のターゲットのリンクを貼り付けます。

url = "https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2018h30.html" res = req.urlopen(url) soup = BeautifulSoup(res, "html.parser") result = soup.select("a[href]") print(result)

実行結果

現在までの結果は"result"という変数に格納しています。結果を出力すると、PDF以外にも画像用のリンクなど色々含まれていますね。

[<a href="http://www.ipa.go.jp/index.html"><img alt="IPA 独立行政法人 情報処理推進機構" height="35" src="/common/images/logo.gif" width="273"/></a>, <a href="javascript:void(0);" id="fontM">標準</a>, <a href="javascript:void(0);" id="fontL">拡大</a>, <a href="http://www.ipa.go.jp/about/index.html"><img alt="IPAについて" height="28" src="/common/images/head_link01.gif" width="104"/></a>, .....]

Step3 - aタグからリンクだけを抽出

コード

次に、リンクだけを抜き出します。link_listというリンクに格納していきます。

link_list =[] for link in result: href = link.get("href") link_list.append(href) print(link_list)

実行結果

URLだけ取り除かれましたね。

['http://www.ipa.go.jp/index.html', 'javascript:void(0);', 'javascript:void(0);', 'http://www.ipa.go.jp/about/index.html', 'http://www.ipa.go.jp/about/news/index.html', 'http://www.ipa.go.jp/about/sitemap/index.html', 'http://www.ipa.go.jp/about/inquiry_index_0.html', 'http://www.ipa.go.jp/index-e.html', 'http://www.ipa.go.jp/index.html', '/', '/1_04hanni_sukiru/_index_mondai.html',....]

 

Step4 - 「.pdf」で終わるものについてのみ取得

コード

続いて、pdfのリンクを抜き出していきます。endswitchという関数を用いてPDFで終わる文字列を抜き出していきます。

pdf_list = [temp for temp in link_list if temp.endswith('pdf')]
print(pdf_list)
実行結果

 先ほどのURLリストから、.pdfを抜き出せています。また、このサイトは相対パスで書いていることが分かるので、絶対パスに後程直さないといけませんね。

['mondai_kaitou_2018h30_2/2018h30a_sg_am_qs.pdf', 'mondai_kaitou_2018h30_2/2018h30a_sg_am_ans.pdf', 'mondai_kaitou_2018h30_2/2018h30a_sg_pm_qs.pdf', 'mondai_kaitou_2018h30_2/2018h30a_sg_pm_ans.pdf', 'mondai_kaitou_2018h30_2/2018h30a_sg_pm_cmnt.pdf', 'mondai_kaitou_2018h30_2/2018h30a_fe_am_qs.pdf', 'mondai_kaitou_2018h30_2/2018h30a_fe_am_ans.pdf', 'mondai_kaitou_2018h30_2/2018h30a_fe_pm_qs.pdf', 'mondai_kaitou_2018h30_2/2018h30a_fe_pm_ans.pdf', 'mondai_kaitou_2018h30_2/2018h30a_fe_pm_cmnt.pdf', 'mondai_kaitou_2018h30_2/2018h30a_ap_am_qs.pdf', ...]


Step5 - データベーススペシャリスト試験のPDFのみを取得

コード

先ほど取得したのはすべてのPDFなので、基本情報処理技術者試験などのPDFも含んでいます。私はデータベーススペシャリストのみで十分なので、絞っていきたいと思います。

どう絞るかは、URLによく注目して規則性を見つけましょう。すると、データベーススペシャリスト試験のものには必ず「"_db_"」という文字が入っていることが分かります。他の試験、例えば基本情報処理技術者試験では、「"_fe_"」という文字が入っていますね。

そこで、「"_db_"」を含む文字列だけを取り出していきます。

dbpdf_list = [temp for temp in pdf_list if '_db_' in temp]
print(dbpdf_list)

 

実行結果

 このように抜き出せました。

['mondai_kaitou_2018h30_1/2018h30h_db_am2_qs.pdf', 'mondai_kaitou_2018h30_1/2018h30h_db_am2_ans.pdf', 'mondai_kaitou_2018h30_1/2018h30h_db_pm1_qs.pdf', 'mondai_kaitou_2018h30_1/2018h30h_db_pm1_ans.pdf',...]

 


Step6 - 相対URLから絶対URLに変換

コード

先ほど指摘した通り、今の段階では相対URLになっているので、絶対URLに書き換えなければいけません。

urljoinという関数を使うと、元URLと相対URLを使って、絶対URLに書き換えてくれます。

abs_dbpdf_list = []
for relative in dbpdf_list:
    temp_url = urljoin(url, relative)
    abs_dbpdf_list.append(temp_url)
print(abs_dbpdf_list)
実行結果

 前に共通してベースURLがくっつきました。

['https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2018h30_1/2018h30h_db_am2_qs.pdf', 'https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2018h30_1/2018h30h_db_am2_ans.pdf', 'https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2018h30_1/2018h30h_db_pm1_qs.pdf', ...]

 


Step7 - URLからファイル名を取得

コード

 続いて保存するためにファイル名をURLから決めていきたいと思います。まず、split関数を使って、仮のリストにURLを階層ごとに分割していきます。ファイル名は一番最後の要素のはずなので、「len()-1」で場所を指定します。

filename_list = []
for target in abs_dbpdf_list:
    temp_list = target.split("/")
    filename_list.append(temp_list[len(temp_list)-1])
print(filename_list)
実行結果

 ファイル名一覧が得られました。

['2018h30h_db_am2_qs.pdf', '2018h30h_db_am2_ans.pdf', '2018h30h_db_pm1_qs.pdf', '2018h30h_db_pm1_ans.pdf', '2018h30h_db_pm1_cmnt.pdf', '2018h30h_db_pm2_qs.pdf', '2018h30h_db_pm2_ans.pdf', '2018h30h_db_pm2_cmnt.pdf']

 


Step8 - 保存先のディレクトリを作成

コード

さて、続いて保存先を決めていきたいと思います。保存先のフォルダと、先ほど取得したファイル名をくっ付けて絶対パスに変換します。絶対パスにするには、「os.path.join」を使用します。

target_dir = "C:\\Users\\makky\\Downloads\\DB"
savepath_list = []
for filename in filename_list:
    savepath_list.append(os.path.join(target_dir, filename))
print(savepath_list)

 

実行結果

 保存先を絶対パスで作ることができました。 

['C:\\Users\\makky\\Downloads\\DB\\2018h30h_db_am2_qs.pdf', 'C:\\Users\\makky\\Downloads\\DB\\2018h30h_db_am2_ans.pdf', 'C:\\Users\\makky\\Downloads\\DB\\2018h30h_db_pm1_qs.pdf', 


Step9 - ダウンロード実行

コード

さて最後にダウンロードです。ダウンロードする絶対URLと、保存先の絶対パスを指定して、「urllib.request.urlretrieve」で保存を実行します。

ウェブスクレイピングでダウンロード先のサイトに負荷をかけすぎてしまうと違法になる可能性があるので、1個ダウンロードを実行するごとに2秒間の間隔をあけます。

for (pdflink, savepath) in zip(abs_dbpdf_list, savepath_list):
    urllib.request.urlretrieve(pdflink, savepath)
    time.sleep(2)
実行結果

はい、このように一括でダウンロードできていますね!

f:id:makkynm:20200802190110p:plain

一括ダウンロード結果

ちょっと一言

複数年度の同時ダウンロード

さて、せっかく平成31年分を一括でダウンロードできたわけですから、どうせなら他の年度のものもダウンロードしたいですよね。

最初にリストを作ってしまって、一気にfor文で実行すればできるでしょう!

やりたいこと

わー、いっぱいあるー。これマニュアルダウンロードはやりたくないです。。。

平成16年から平成31年まで、1年に1回がおよそ8ファイル。合計およそ120ファイル分です。

f:id:makkynm:20200802190829p:plain

ダウンロードしたい年度



コード

先ほど貼ったので、今回は一括でコード貼ります。エクセルで元URLのリストを作成してPythonにコピペしていきましょう。

f:id:makkynm:20200802192031p:plain

Excelでリストの作成

実行経過が分かるように、最後のSave部分でPrintを入れています。

#step1
from bs4 import BeautifulSoup
import urllib.request as req
import urllib
import os
import time
from urllib.parse import urljoin

baseurl_list = ["https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2019h31.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2018h30.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2017h29.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2016h28.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2015h27.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2014h26.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2013h25.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2012h24.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2011h23.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2010h22.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2009h21.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2008h20.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2007h19.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2006h18.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2005h17.html",
"https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2004h16.html",
]
for url in baseurl_list:
    #step2
    res = req.urlopen(url)
    soup = BeautifulSoup(res, "html.parser")
    result = soup.select("a[href]")
    #step3
    link_list =[]
    for link in result:
        href = link.get("href")
        link_list.append(href)
    #step4
    pdf_list = [temp for temp in link_list if temp.endswith('pdf')]
    #step5
    dbpdf_list = [temp for temp in pdf_list if '_db_' in temp]
    #step6
    abs_dbpdf_list = []
    for relative in dbpdf_list:
        temp_url = urljoin(url, relative)
        abs_dbpdf_list.append(temp_url)
    #step7
    filename_list = []
    for target in abs_dbpdf_list:
        temp_list = target.split("/")
        filename_list.append(temp_list[len(temp_list)-1])
    #step8
    target_dir = "C:\\Users\\makky\\Downloads\\DB"
    savepath_list = []
    for filename in filename_list:
        savepath_list.append(os.path.join(target_dir, filename))
    #step9
    for (pdflink, savepath) in zip(abs_dbpdf_list, savepath_list):
        print(pdflink)
        urllib.request.urlretrieve(pdflink, savepath)
        time.sleep(2)

 

実行結果

はい、合計123ファイルが2分ほどでダウンロードされました!

f:id:makkynm:20200802193054p:plain

実行結果 - 全過去問ダウンロード

おわりに

あるあるだと思う?のですが、データベーススペシャリスト試験の勉強を始めようとして、Pythonのウェブスクレイピングを書き始めてしまいました。

素直にダウンロード:20分

Pythonのウェブスクレイピングを学ぶ+コード&備忘録書く:4時間

何やってんだって感じですね笑

なお、Pythonデータベーススペシャリスト試験には出ません。一向に進まない試験勉強ですが応用情報処理試験受かってからの午前Ⅰ試験免除が残っている間に受かりたいところです。

今後もこんなTopicも織り交ぜながら書いていきたいと思いますー

 

 

参考リンク