【Python】新型コロナウイルスの各都道府県の状況がPDFしか公開されていないけどダウンロードはせずにスクレイプしてみた。


はじめに

本記事は、先日デイリートレンドにて見かけた
新型コロナウイルスの各都道府県の状況がPDFしか公開されていないのでAPIを作ってみた[Python] by @tommy19970714
をPDFをダウンロードせずに実現できないかということで、
PDFの内容を取得 → JSON形式に加工
までをPDFのダウンロード無しで書いてみたものです。

PDFをダウンロードせずにどうやって中身の情報を得るか

TikaというPDFなどからテキストを抽出できるライブラリを利用します。
このライブラリはローカルのPDFからのテキスト抽出を紹介されていることが多いですが、実はネット上のPDFに対しても使用することが出来ます。

元記事のコードによりPDFファイルのURLを取得し、

import urllib.request
from bs4 import BeautifulSoup

def extract_page_url(infomation_url):
  req = urllib.request.Request(infomation_url)
  html = urllib.request.urlopen(req)
  soup = BeautifulSoup(html, "html.parser")

  topic = soup.find_all('div', attrs={'class': 'm-grid__col1'})[1]
  article_urls = [tag['href'] for tag in topic.find_all('a', href=True)]
  article_titles = [tag.text for tag in topic.find_all('a', href=True)]
  return article_urls, article_titles

target_url = "https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/0000121431_00086.html"
page_urls, page_titles = extract_page_url(target_url)


def get_pdf_url(page_url):
  req = urllib.request.Request(page_url)
  html = urllib.request.urlopen(req)
  soup = BeautifulSoup(html, "html.parser")
  for atag in soup.find_all('a', href=True):
    if '各都道府県の検査陽性者の状況' in atag.text:
      return atag['href']

pdf_url = "https://www.mhlw.go.jp" + get_pdf_url(page_urls[0])

TikaにPDFのURLを渡すと、

from tika import parser

file_data = parser.from_buffer(requests.get(pdf_url))
text = file_data["content"]

print(text)

出力
out










































2020/8/17 24時時点

うち重症

北 海 道 1,627 36,329 136 3 1,388 103 0

青   森 33 1,731 1 0 31 1 0

岩    手 9 2,218 6 0 3 0 0

宮    城 184 7,146 10 1 173 1 0

秋  田 43 1,554 16 0 26 0 1

山    形 76 3,005 0 0 76 1 1

福    島 106 11,990 16 1 90 0 0

茨    城 456 9,383 83 2 363 10 0

栃    木 277 18,653 43 2 221 1 12

群    馬 307 10,523 101 0 178 19 9

埼    玉 3,256 89,463 549 10 2,625 82 0

       千    葉 ※5 2,495 45,444 506 8 1,931 58 0

    東    京 ※4 17,875 260,990 3,519 27 14,015 341 0

神 奈 川 3,904 91,480 633 21 3,166 105 0

新    潟 130 8,383 8 0 121 0 1

富    山 308 6,694 45 3 242 22 1

石    川 475 4,097 134 2 312 29 0

福    井 155 5,899 6 1 141 8 0

山    梨 140 8,681 22 0 117 1 0

長    野 149 10,909 31 0 119 - 1

岐    阜 516 15,311 69 2 439 8 0

静    岡 418 19,352 76 2 341 1 0

愛    知 3,744 39,879 1,447 13 2,248 44 5

三    重 284 7,642 103 2 180 1 0

滋    賀 346 7,216 106 4 236 4 0

京    都 1,119 24,839 200 3 898 21 0

大    阪 6,916 105,926 1,621 70 5,179 111 5

兵    庫 1,900 36,336 295 14 1,557 48 0

奈    良 403 11,572 102 3 298 3 0

和 歌 山 198 7,395 22 1 170 4 2

鳥    取 21 4,316 11 0 10 0 0

島    根 132 4,211 103 0 29 0 0

岡    山 126 3,836 20 - 90 - 16

    広    島 ※5 437 16,148 72 1 362 3 0

山    口 83 5,050 18 0 65 0 0

徳    島 91 3,936 48 0 38 1 4

香    川 65 6,505 8 0 56 1 0

愛    媛 110 3,431 10 0 94 6 0

高    知 103 2,609 19 0 80 3 1

福    岡 3,633 35,054 1,055 21 2,537 41 0

佐    賀 198 3,983 67 0 133 0 2

長    崎 185 10,783 38 - 36 3 108 G48の関数(=O47=★当日データ★!E49)

熊    本 412 9,237 98 5 247 6 61

大    分 113 9,222 32 0 80 1 0

宮    崎 267 6,956 76 0 191 1 1

鹿 児 島 328 13,337 63 2 249 7 9

沖    縄 1,656 18,502 1,123 19 523 14 0

(その他)※3 149 - 0 - 149 - 0

合計 55,958 1,067,156 12,767 243 41,853 1,114 240

※1

※2

※3 その他は、長崎県のクルーズ船における陽性者

※4

※5

PCR検査実施人数は、一部自治体について件数を計上しているため、実際の人数より過大である。また、更新がなかった自治体について

は、前日の数値を記載している。

PCR検査陽性者数から入院治療等を要する者の数、退院又は療養解除となった者の数、死亡者の数を減じて厚労省において作成したもの。

なお、療養解除後に再入院した者を陽性者として改めて計上していない自治体があるため、合計は一致しない。

東京都の数値は次の出典より引用した:https://stopcovid19.metro.tokyo.lg.jp/

空港検疫にて陽性が確認された事例を国内事例としても公表している自治体の当該事例数は含まれていない

各都道府県の検査陽性者の状況(空港検疫、チャーター便案件を除く国内事例)

都道府県名 陽性者数
PCR検査

実施人数※1

入院治療等を

要する者

(人)

退院又は療養解除

となった者の数

(人)

死亡(累積)

(人)

確認中※2

(人)



実に簡単に内容を取得できました。

加工

変数名がめちゃくちゃ適当ですが、

l = {}
for i in re.findall("(?:[一-龥](?:\s+[一-龥]|計){1,2}|(その他))[※\d\s]+?\n", text.translate(str.maketrans({"\u3000":" ",",":"","-":"0"}))):
    a = i.split()
    b = "".join(re.findall("[一-龥その]", i))
    l[b] = {}
    l[b]["陽性者数"] = int(a[-7])
    l[b]["PCR検査実施人数"] = int(a[-6])
    l[b]["入院治療等を要する者"] = {"重症でない":int(a[-5]), "重症":int(a[-4])}
    l[b]["退院又は療養解除となった者の数"] = int(a[-3])
    l[b]["死亡(累積)"] = int(a[-2])
    l[b]["確認中"] = int(a[-1])

print(l["東京"])

出力

out
{'陽性者数': 17875,
 'PCR検査実施人数': 260990,
 '入院治療等を要する者': {'重症でない': 3519, '重症': 27},
 '退院又は療養解除となった者の数': 14015,
 '死亡(累積)': 341,
 '確認中': 0}

きれいになりました。

ちなみに全データを表示すると、

全データ
out
{'北海道': {'陽性者数': 1627,
  'PCR検査実施人数': 36329,
  '入院治療等を要する者': {'重症でない': 136, '重症': 3},
  '退院又は療養解除となった者の数': 1388,
  '死亡(累積)': 103,
  '確認中': 0},
 '青森': {'陽性者数': 33,
  'PCR検査実施人数': 1731,
  '入院治療等を要する者': {'重症でない': 1, '重症': 0},
  '退院又は療養解除となった者の数': 31,
  '死亡(累積)': 1,
  '確認中': 0},
 '岩手': {'陽性者数': 9,
  'PCR検査実施人数': 2218,
  '入院治療等を要する者': {'重症でない': 6, '重症': 0},
  '退院又は療養解除となった者の数': 3,
  '死亡(累積)': 0,
  '確認中': 0},
 '宮城': {'陽性者数': 184,
  'PCR検査実施人数': 7146,
  '入院治療等を要する者': {'重症でない': 10, '重症': 1},
  '退院又は療養解除となった者の数': 173,
  '死亡(累積)': 1,
  '確認中': 0},
 '秋田': {'陽性者数': 43,
  'PCR検査実施人数': 1554,
  '入院治療等を要する者': {'重症でない': 16, '重症': 0},
  '退院又は療養解除となった者の数': 26,
  '死亡(累積)': 0,
  '確認中': 1},
 '山形': {'陽性者数': 76,
  'PCR検査実施人数': 3005,
  '入院治療等を要する者': {'重症でない': 0, '重症': 0},
  '退院又は療養解除となった者の数': 76,
  '死亡(累積)': 1,
  '確認中': 1},
 '福島': {'陽性者数': 106,
  'PCR検査実施人数': 11990,
  '入院治療等を要する者': {'重症でない': 16, '重症': 1},
  '退院又は療養解除となった者の数': 90,
  '死亡(累積)': 0,
  '確認中': 0},
 '茨城': {'陽性者数': 456,
  'PCR検査実施人数': 9383,
  '入院治療等を要する者': {'重症でない': 83, '重症': 2},
  '退院又は療養解除となった者の数': 363,
  '死亡(累積)': 10,
  '確認中': 0},
 '栃木': {'陽性者数': 277,
  'PCR検査実施人数': 18653,
  '入院治療等を要する者': {'重症でない': 43, '重症': 2},
  '退院又は療養解除となった者の数': 221,
  '死亡(累積)': 1,
  '確認中': 12},
 '群馬': {'陽性者数': 307,
  'PCR検査実施人数': 10523,
  '入院治療等を要する者': {'重症でない': 101, '重症': 0},
  '退院又は療養解除となった者の数': 178,
  '死亡(累積)': 19,
  '確認中': 9},
 '埼玉': {'陽性者数': 3256,
  'PCR検査実施人数': 89463,
  '入院治療等を要する者': {'重症でない': 549, '重症': 10},
  '退院又は療養解除となった者の数': 2625,
  '死亡(累積)': 82,
  '確認中': 0},
 '千葉': {'陽性者数': 2495,
  'PCR検査実施人数': 45444,
  '入院治療等を要する者': {'重症でない': 506, '重症': 8},
  '退院又は療養解除となった者の数': 1931,
  '死亡(累積)': 58,
  '確認中': 0},
 '東京': {'陽性者数': 17875,
  'PCR検査実施人数': 260990,
  '入院治療等を要する者': {'重症でない': 3519, '重症': 27},
  '退院又は療養解除となった者の数': 14015,
  '死亡(累積)': 341,
  '確認中': 0},
 '神奈川': {'陽性者数': 3904,
  'PCR検査実施人数': 91480,
  '入院治療等を要する者': {'重症でない': 633, '重症': 21},
  '退院又は療養解除となった者の数': 3166,
  '死亡(累積)': 105,
  '確認中': 0},
 '新潟': {'陽性者数': 130,
  'PCR検査実施人数': 8383,
  '入院治療等を要する者': {'重症でない': 8, '重症': 0},
  '退院又は療養解除となった者の数': 121,
  '死亡(累積)': 0,
  '確認中': 1},
 '富山': {'陽性者数': 308,
  'PCR検査実施人数': 6694,
  '入院治療等を要する者': {'重症でない': 45, '重症': 3},
  '退院又は療養解除となった者の数': 242,
  '死亡(累積)': 22,
  '確認中': 1},
 '石川': {'陽性者数': 475,
  'PCR検査実施人数': 4097,
  '入院治療等を要する者': {'重症でない': 134, '重症': 2},
  '退院又は療養解除となった者の数': 312,
  '死亡(累積)': 29,
  '確認中': 0},
 '福井': {'陽性者数': 155,
  'PCR検査実施人数': 5899,
  '入院治療等を要する者': {'重症でない': 6, '重症': 1},
  '退院又は療養解除となった者の数': 141,
  '死亡(累積)': 8,
  '確認中': 0},
 '山梨': {'陽性者数': 140,
  'PCR検査実施人数': 8681,
  '入院治療等を要する者': {'重症でない': 22, '重症': 0},
  '退院又は療養解除となった者の数': 117,
  '死亡(累積)': 1,
  '確認中': 0},
 '長野': {'陽性者数': 149,
  'PCR検査実施人数': 10909,
  '入院治療等を要する者': {'重症でない': 31, '重症': 0},
  '退院又は療養解除となった者の数': 119,
  '死亡(累積)': 0,
  '確認中': 1},
 '岐阜': {'陽性者数': 516,
  'PCR検査実施人数': 15311,
  '入院治療等を要する者': {'重症でない': 69, '重症': 2},
  '退院又は療養解除となった者の数': 439,
  '死亡(累積)': 8,
  '確認中': 0},
 '静岡': {'陽性者数': 418,
  'PCR検査実施人数': 19352,
  '入院治療等を要する者': {'重症でない': 76, '重症': 2},
  '退院又は療養解除となった者の数': 341,
  '死亡(累積)': 1,
  '確認中': 0},
 '愛知': {'陽性者数': 3744,
  'PCR検査実施人数': 39879,
  '入院治療等を要する者': {'重症でない': 1447, '重症': 13},
  '退院又は療養解除となった者の数': 2248,
  '死亡(累積)': 44,
  '確認中': 5},
 '三重': {'陽性者数': 284,
  'PCR検査実施人数': 7642,
  '入院治療等を要する者': {'重症でない': 103, '重症': 2},
  '退院又は療養解除となった者の数': 180,
  '死亡(累積)': 1,
  '確認中': 0},
 '滋賀': {'陽性者数': 346,
  'PCR検査実施人数': 7216,
  '入院治療等を要する者': {'重症でない': 106, '重症': 4},
  '退院又は療養解除となった者の数': 236,
  '死亡(累積)': 4,
  '確認中': 0},
 '京都': {'陽性者数': 1119,
  'PCR検査実施人数': 24839,
  '入院治療等を要する者': {'重症でない': 200, '重症': 3},
  '退院又は療養解除となった者の数': 898,
  '死亡(累積)': 21,
  '確認中': 0},
 '大阪': {'陽性者数': 6916,
  'PCR検査実施人数': 105926,
  '入院治療等を要する者': {'重症でない': 1621, '重症': 70},
  '退院又は療養解除となった者の数': 5179,
  '死亡(累積)': 111,
  '確認中': 5},
 '兵庫': {'陽性者数': 1900,
  'PCR検査実施人数': 36336,
  '入院治療等を要する者': {'重症でない': 295, '重症': 14},
  '退院又は療養解除となった者の数': 1557,
  '死亡(累積)': 48,
  '確認中': 0},
 '奈良': {'陽性者数': 403,
  'PCR検査実施人数': 11572,
  '入院治療等を要する者': {'重症でない': 102, '重症': 3},
  '退院又は療養解除となった者の数': 298,
  '死亡(累積)': 3,
  '確認中': 0},
 '和歌山': {'陽性者数': 198,
  'PCR検査実施人数': 7395,
  '入院治療等を要する者': {'重症でない': 22, '重症': 1},
  '退院又は療養解除となった者の数': 170,
  '死亡(累積)': 4,
  '確認中': 2},
 '鳥取': {'陽性者数': 21,
  'PCR検査実施人数': 4316,
  '入院治療等を要する者': {'重症でない': 11, '重症': 0},
  '退院又は療養解除となった者の数': 10,
  '死亡(累積)': 0,
  '確認中': 0},
 '島根': {'陽性者数': 132,
  'PCR検査実施人数': 4211,
  '入院治療等を要する者': {'重症でない': 103, '重症': 0},
  '退院又は療養解除となった者の数': 29,
  '死亡(累積)': 0,
  '確認中': 0},
 '岡山': {'陽性者数': 126,
  'PCR検査実施人数': 3836,
  '入院治療等を要する者': {'重症でない': 20, '重症': 0},
  '退院又は療養解除となった者の数': 90,
  '死亡(累積)': 0,
  '確認中': 16},
 '広島': {'陽性者数': 437,
  'PCR検査実施人数': 16148,
  '入院治療等を要する者': {'重症でない': 72, '重症': 1},
  '退院又は療養解除となった者の数': 362,
  '死亡(累積)': 3,
  '確認中': 0},
 '山口': {'陽性者数': 83,
  'PCR検査実施人数': 5050,
  '入院治療等を要する者': {'重症でない': 18, '重症': 0},
  '退院又は療養解除となった者の数': 65,
  '死亡(累積)': 0,
  '確認中': 0},
 '徳島': {'陽性者数': 91,
  'PCR検査実施人数': 3936,
  '入院治療等を要する者': {'重症でない': 48, '重症': 0},
  '退院又は療養解除となった者の数': 38,
  '死亡(累積)': 1,
  '確認中': 4},
 '香川': {'陽性者数': 65,
  'PCR検査実施人数': 6505,
  '入院治療等を要する者': {'重症でない': 8, '重症': 0},
  '退院又は療養解除となった者の数': 56,
  '死亡(累積)': 1,
  '確認中': 0},
 '愛媛': {'陽性者数': 110,
  'PCR検査実施人数': 3431,
  '入院治療等を要する者': {'重症でない': 10, '重症': 0},
  '退院又は療養解除となった者の数': 94,
  '死亡(累積)': 6,
  '確認中': 0},
 '高知': {'陽性者数': 103,
  'PCR検査実施人数': 2609,
  '入院治療等を要する者': {'重症でない': 19, '重症': 0},
  '退院又は療養解除となった者の数': 80,
  '死亡(累積)': 3,
  '確認中': 1},
 '福岡': {'陽性者数': 3633,
  'PCR検査実施人数': 35054,
  '入院治療等を要する者': {'重症でない': 1055, '重症': 21},
  '退院又は療養解除となった者の数': 2537,
  '死亡(累積)': 41,
  '確認中': 0},
 '佐賀': {'陽性者数': 198,
  'PCR検査実施人数': 3983,
  '入院治療等を要する者': {'重症でない': 67, '重症': 0},
  '退院又は療養解除となった者の数': 133,
  '死亡(累積)': 0,
  '確認中': 2},
 '熊本': {'陽性者数': 412,
  'PCR検査実施人数': 9237,
  '入院治療等を要する者': {'重症でない': 98, '重症': 5},
  '退院又は療養解除となった者の数': 247,
  '死亡(累積)': 6,
  '確認中': 61},
 '大分': {'陽性者数': 113,
  'PCR検査実施人数': 9222,
  '入院治療等を要する者': {'重症でない': 32, '重症': 0},
  '退院又は療養解除となった者の数': 80,
  '死亡(累積)': 1,
  '確認中': 0},
 '宮崎': {'陽性者数': 267,
  'PCR検査実施人数': 6956,
  '入院治療等を要する者': {'重症でない': 76, '重症': 0},
  '退院又は療養解除となった者の数': 191,
  '死亡(累積)': 1,
  '確認中': 1},
 '鹿児島': {'陽性者数': 328,
  'PCR検査実施人数': 13337,
  '入院治療等を要する者': {'重症でない': 63, '重症': 2},
  '退院又は療養解除となった者の数': 249,
  '死亡(累積)': 7,
  '確認中': 9},
 '沖縄': {'陽性者数': 1656,
  'PCR検査実施人数': 18502,
  '入院治療等を要する者': {'重症でない': 1123, '重症': 19},
  '退院又は療養解除となった者の数': 523,
  '死亡(累積)': 14,
  '確認中': 0},
 'その他': {'陽性者数': 149,
  'PCR検査実施人数': 0,
  '入院治療等を要する者': {'重症でない': 0, '重症': 0},
  '退院又は療養解除となった者の数': 149,
  '死亡(累積)': 0,
  '確認中': 0},
 '合計': {'陽性者数': 55958,
  'PCR検査実施人数': 1067156,
  '入院治療等を要する者': {'重症でない': 12767, '重症': 243},
  '退院又は療養解除となった者の数': 41853,
  '死亡(累積)': 1114,
  '確認中': 240}}

まとめ

後はjson.dumps(l)などとエンコードして元記事に合流です。
ダウンロードせずにPDFを扱う方法は以前から探していたので良い機会となりました。