データサイエンス

日別に分かれた大量のPDFからデータを取得

2023年5月21日

ここでは、PythonのBeautifulSoupとurllibパッケージを使用して大量のPDFをダウンロードする実装について紹介します。
手動で保存するには数が多すぎる場合も、ウェブサイトがきれいに構造化されていれば簡単に自動化できます。

今回取得したデータの可視化・考察まで含めた分析全体については次のページにまとめています。

参考コロナ前後の高速道路の交通量の推移

今回は、高速道路の交通量データの可視化を行い、コロナ禍の外出自粛による交通量激減からの回復状況の区間ごとの違いを見ていきます。 今回使用するデータは、ダウンロードや構造化データに直すまでに一手間かかる ...

続きを見る

今回スクレイピングするデータについて

公的機関などから日々様々なデータが公開されていますが、必ずしも利用しやすい形式とは限りません。
必要なデータが大量のPDFファイルに分かれて保存されている場合は、手動で保存してCSV変換するのは徒労になります。

そこで今回は、国土交通省の下記ページからコロナ前~コロナ後にかけての高速道路の交通量の日次推移のデータを取得します。

全国・主要都市圏における高速道路・主要国道の主な区間の交通量増減(国土交通省)
※このデータを使用した分析全体はこちらを参照

このデータは、全国40区間の高速道路の交通量を日次で記載しているデータです。
リンク先からさらに月別のページに飛び、そこから日ごとに分かれたファイルをダウンロードする形式になっています。

日ごとのPDFファイルは1ページ目に対象区間を示した地図、2ページ目に交通量の表が掲載されています。
交通量の表はエクセルのセル結合を使っているため、テーブルとして読み込もうとすると読み枠が崩れるやっかいなファイルです。

以降では、このPDFファイルをダウンロードしてテーブルを取得し、CSVファイルとして保存する流れをまとめます。

データ取得の流れ

データ取得の流れは以下のようになります。
1.ダウンロード先のURLを取得してPDFをダウンロード(本ページで解説)
2.ダウンロードしたPDFからテーブルデータを抽出してCSV保存(こちらのページで解説)

使用したパッケージは次のとおりです。

import requests
from bs4 import BeautifulSoup
import time
from datetime import datetime
import urllib

1.ダウンロード先URLの取得~ダウンロード

はじめに、ダンロードしたい日ごとのPDFのURLを取得します。

このサイトの構造としては、まず月ごとにページが分かれており、月ごとのページの中に日次のPDFファイルのダウンロードリンクが羅列しています。
この構造からダウンロードを自動化する方法を考えます。

まず月ごとのページについて見てきます。
月ごとのURLを並べてみます。

2023年2月:https://www.mlit.go.jp/road/road_fr4_000147.html
2023年1月:https://www.mlit.go.jp/road/road_fr4_000146.html
2022年12月:https://www.mlit.go.jp/road/road_fr4_000141.html
2022年11月:https://www.mlit.go.jp/road/road_fr4_000140.html
2022年10月:https://www.mlit.go.jp/road/road_fr4_000138.html

一見順番に並んでいるように見えて、所々で数字が飛んでいます。
この数字の飛び方が不規則なので、月ごとのURL取得は自動化を諦めて月ごとに手入力で与えることにしました。

次に、各月のページから日ごとのPDFのダウンロードリンクを取得する方法を考えます。
各月のページは規則的な構造をしているため、ダウンロードの自動化が可能です。

月ごとのページのソースは次のようになっています(一部抜粋、Chromeで右クリックしてページのソースを表示)。
ソース:https://www.mlit.go.jp/road/road_fr4_000149.html

<h2 class="title">令和5年4月の全国・主要都市圏における高速道路の主な区間の交通量増減</h2>
</div>
</div>
<div id="innerL">
<table border="1" cellpadding="1" cellspacing="1" class="undefined" style="width: 100%;">
<tbody>
<tr>
<td><br>
 <1.全国><br>
<br>
[日別資料]<br>
<br>
<a href="/road/content/001609443.pdf">令和5年4月30日(日)</a><br>
<a href="/road/content/001609442.pdf">令和5年4月29日(土)</a><br>
<a href="/road/content/001609441.pdf">令和5年4月28日(金)</a><br>
<a href="/road/content/001609440.pdf">令和5年4月27日(木)</a><br>

このような構造から、aタグのhref属性の値を参照することで、ダウンロードリンクを取得できます。
aタグはダウンロードリンク以外にもありますが、各月のページは必ず6番目のaタグがその月の1日のデータのダウンロードリンクになっています。
この構造を利用して、各月のページの6番目から6+(その月の日数)番目までのダウンロードリンクを取得すれば、全ての日にちのデータを得られます。

以上のような処理はPythonのBeautifulSoupパッケージを使用して実装できます。
以下に実装を示します。

# 関数定義:交通量のテーブルを記載したPDFのURLと日付を取得
def perform_get_pdf(domain, url, day_month):
    #domain = 'https://www.mlit.go.jp' # aタグから取得できないURLのドメイン部分
    #day_month = 31 # 1ヶ月間の月数
    
    time.sleep(30) # 負荷をかけすぎないように間隔を空ける
    
    # データの取得
    r = requests.get(url)
    soup = BeautifulSoup(r.content, 'html.parser')
    
    for link in soup.find_all('a')[6:6+day_month]:
        time.sleep(3) # 負荷をかけすぎないように間隔を空ける

        page = link.get('href') # リンク先のURLを取得
        url = domain + page
        date = link.text  # リンクになっている文字列(日付)を取得
        # 日付取得
        date = convert_date(date=date) # 自作関数

        # PDFに保存
        get_pdf(date=date, url=url) # 自作関数

上記関数は月ごとにPDFファイルをまとめてダウンロードする関数です。
①サイトドメイン、②月ごとのページのURL、③その月の日数の3つを引数にとります。

はじめに、BeautifulSoup関数を使ってHTMLソースコードを取得しています。
soup.find_all関数を使って6番目から6+day_month(その月の日子)番目までのaタグのデータを取得します。
href属性の値から取得したURLはドメインが無いので、ドメイン部分を足し合わせてURLが得られます。

convert_dateは自作関数です。
PDFを保存する際には、ファイル名に西暦の日付を使用したいです。
PDFファイルの日付はリンクの文字列に記載されているのですが、和暦表記なので西暦に直す処理を行っています。

# 関数定義:和暦日付を西暦日付に変換
def convert_date(date):
    x = date
    x = x.replace('令和5', '2023')
    x = x.replace('令和4', '2022')
    x = x.replace('令和3', '2021')
    x = x.replace('令和2', '2020')
    x = x.replace('令和元', '2019')
    x = x.replace('平成31', '2019')
    x = x.replace('平成30', '2018')
    # 曜日部分以外を取得
    x = x.split('(')[0]
    # 型変換
    try:
        # 2021/5/20は誤字のためエラー出る
        x = datetime.strptime(x, '%Y年%m月%d日').date()
    except:
        # 2021/5/20の処理
        x = datetime.strptime(x, '%Y月%m月%d日').date()
    
    return(x)

和暦での年月日は平成/令和の年号の違いなどの表記ゆれがあるため、置換内容はハードコーディングしています。
年月日の表記をdatetime型へ変換する際には、リンクの年月日に一部誤字があるため、try-except関数を使用してエラーを回避しています。

get_pdfも同じく自作関数です。
次のように取得したURLからPDFを保存します。

# 関数定義:PDFをダウンロード・保存
def get_pdf(date, url):
    # save_dir = '../00_data/01_高速道路交通量増減/01_pdf/'
    save_dir = '../02_work/01_高速道路日別交通量_pdf/'
    file_name = '高速道路交通量_全国_日別_' + date.strftime('%Y-%m-%d') + '.pdf'

    # PDFをダウンロード
    # ダウンロード開始時にファイルを作成するurlretrieveではなくダウンロード完了後にファイルを作成するurlopenを使う
    # https://qiita.com/Kanahiro/items/dd585599f36a77540439

    # urllib.request.urlretrieve(url, os.path.join(save_dir, file_name))
    data = urllib.request.urlopen(url).read()

    # ダウンロードしたPDFを保存
    save_path = save_dir + file_name
    with open(save_path, mode="wb") as f:
        f.write(data)
    print(date.strftime('%Y-%m-%d') + ' download and save finished!')

最後にperform_get_pdf関数の引数について説明します。
①サイトドメイン、②月ごとのページのURL、③その月の日数の3つを引数にとります。

# 関数実行・PDFを保存
domain = 'https://www.mlit.go.jp' # aタグから取得できないURLのドメイン部分
# 2023/04
url = 'https://www.mlit.go.jp/road/road_fr4_000149.html'
day_month = 30

perform_get_pdf(domain, url, day_month)

2.PDFからテーブルを取得~CSVに保存

取得したPDFから交通量のテーブルを取得してCSVに保存する工程は別のページにまとめています。

参考PDFからテーブルデータを取得

前回、国土交通省のウェブサイトから高速道路の日次交通量のPDFファイルをダウンロードしました。 今回はダウンロードしたPDFを読み込み、PDFファイルの中にある表(テーブルデータ)を取得してCSVに保 ...

続きを見る

参考文献

全国・主要都市圏における高速道路・主要国道の主な区間の交通量増減 国土交通省 2023/5/18閲覧
Pythonのファイルダウンロードにはurlretrieveではなくurlopenを使うべき理由 Qiita 2023/5/18閲覧

-データサイエンス
-, , ,