├── api ├── __init__.py ├── _utils │ ├── __init__.py │ ├── statics │ │ ├── __init__.py │ │ └── inhabitants.py │ ├── scrap_data.py │ └── scrap_data_v2.py ├── test │ ├── __init__.py │ ├── test_statics_inhabitants.py │ ├── test_scrap_data_v2.py │ └── test_scrap_data.py ├── v2.py └── index.py ├── requirements.txt ├── .vercelignore ├── .pylintrc ├── .vuepress └── config.js ├── vercel.json ├── .github └── workflows │ ├── post_deployment.yml │ └── ci.yml ├── package.json ├── LICENSE ├── .gitignore └── README.md /api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/_utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/_utils/statics/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | openpyxl 2 | pytz -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | api/tests -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | ignore=node_modules 3 | indent-string=' ' 4 | [MESSAGES CONTROL] 5 | disable=R0801 -------------------------------------------------------------------------------- /.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Covid-19 vaccination data', 3 | description: 'A API to fetch Covid-19 vaccination data from RKI via JSON', 4 | dest: 'public', 5 | themeConfig: { 6 | search: false, 7 | nav: [{ 8 | text: 'Home', 9 | link: '/' 10 | }, 11 | { 12 | text: 'GitHub', 13 | link: 'https://github.com/ThisIsBenny/rki-vaccination-data' 14 | } 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "functions": { 4 | "api/index.py": { 5 | "includeFiles": "_utils/**" 6 | }, 7 | "api/v2.py": { 8 | "includeFiles": "_utils/**" 9 | } 10 | }, 11 | "headers": [ 12 | { 13 | "source": "/(.*)", 14 | "headers" : [ 15 | { 16 | "key" : "Cache-Control", 17 | "value" : "s-maxage=3600, stale-while-revalidate" 18 | } 19 | ] 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /.github/workflows/post_deployment.yml: -------------------------------------------------------------------------------- 1 | name: Post deployment workflow 2 | 3 | on: [deployment_status] 4 | 5 | jobs: 6 | api-check: 7 | if: github.event.deployment_status.state == 'success' 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check http status code of production site 11 | uses: lakuapik/gh-actions-http-status@v1 12 | with: 13 | sites: '["${{ github.event.deployment_status.target_url }}", "${{ github.event.deployment_status.target_url }}/api", "${{ github.event.deployment_status.target_url }}/api/v2"]' 14 | expected: '[200, 200, 200]' -------------------------------------------------------------------------------- /api/test/test_statics_inhabitants.py: -------------------------------------------------------------------------------- 1 | """ Unittest """ 2 | import unittest 3 | # pylint: disable=import-error 4 | from _utils.statics import inhabitants 5 | 6 | class TestModuleStaticsInhabitants(unittest.TestCase): 7 | """ Test Module for statics.inhabitants """ 8 | def test_total(self): 9 | """ Test for inhabitants.TOTAL """ 10 | self.assertEqual(type(inhabitants.TOTAL).__name__, "int") 11 | 12 | def test_states(self): 13 | """ Test for inhabitants.STATES """ 14 | self.assertEqual(type(inhabitants.STATES).__name__,"dict") 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rki-vaccination-data", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "vuepress dev .", 8 | "build": "vuepress build ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:ThisIsBenny/rki-vaccination-data.git" 13 | }, 14 | "author": "Benny Hierl", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/ThisIsBenny/rki-vaccination-data/issues" 18 | }, 19 | "homepage": "", 20 | "dependencies": {}, 21 | "devDependencies": { 22 | "vuepress": "^1.5.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api/test/test_scrap_data_v2.py: -------------------------------------------------------------------------------- 1 | """ Unittest """ 2 | import unittest 3 | # pylint: disable=import-error 4 | from _utils import scrap_data_v2 5 | 6 | class TestModuleScrapData(unittest.TestCase): 7 | """ Test Module """ 8 | def test_get_file(self): 9 | """ Test for scrap_data_v2.get_file """ 10 | self.assertEqual(type(scrap_data_v2.get_file()).__name__, "bytes") 11 | def test_get_data(self): 12 | """ Test for scrap_data_v2.get_data """ 13 | data = scrap_data_v2.get_data() 14 | self.assertEqual(type(data).__name__, "dict") 15 | 16 | self.assertIn("lastUpdate", data.keys()) 17 | self.assertIn("data", data.keys()) 18 | 19 | self.assertEqual(type(data["lastUpdate"]).__name__, "datetime") 20 | self.assertEqual(type(data["data"]).__name__, "list") 21 | 22 | if __name__ == '__main__': 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Set up Python 3.8 11 | uses: actions/setup-python@v1 12 | with: 13 | python-version: 3.8 14 | - name: Install dependencies 15 | run: | 16 | python -m pip install --upgrade pip 17 | pip install -r requirements.txt 18 | pip install pylint 19 | - name: Analysing the code with pylint 20 | run: | 21 | pylint api/** 22 | test: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Set up Python 3.8 27 | uses: actions/setup-python@v1 28 | with: 29 | python-version: 3.8 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install -r requirements.txt 34 | - name: Unittest 35 | run: | 36 | cd api 37 | python -m unittest 38 | -------------------------------------------------------------------------------- /api/v2.py: -------------------------------------------------------------------------------- 1 | """ API Endpont """ 2 | from http.server import BaseHTTPRequestHandler 3 | import json 4 | from datetime import datetime 5 | import pytz 6 | # pylint: disable=import-error 7 | from api._utils import scrap_data_v2 8 | 9 | try: 10 | data = scrap_data_v2.get_data() 11 | res = { 12 | 'lastUpdate': data['lastUpdate'].isoformat(), 13 | 'data': data['data'] 14 | } 15 | HTTPCODE = 200 16 | except TypeError: 17 | res = { 18 | 'message': 'scrapping data from RKI excel failed' 19 | } 20 | HTTPCODE = 500 21 | 22 | class Handler(BaseHTTPRequestHandler): 23 | """ HTTP Handler """ 24 | # pylint: disable=invalid-name 25 | def do_GET(self): 26 | """ GET Method """ 27 | self.send_response(HTTPCODE) 28 | self.send_header('Content-Type', 'application/json') 29 | self.send_header('X-Cache-Timestamp', 30 | pytz.timezone('Europe/Berlin').localize(datetime.now()).isoformat()) 31 | self.send_header('Access-Control-Allow-Origin', '*') 32 | self.end_headers() 33 | self.wfile.write(json.dumps(res).encode()) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/_utils/statics/inhabitants.py: -------------------------------------------------------------------------------- 1 | """ Static numbers of inhabitants """ 2 | # Source https://www.destatis.de/DE/Themen/Gesellschaft-Umwelt/Bevoelkerung 3 | # /Bevoelkerungsstand/Tabellen/bevoelkerung-nichtdeutsch-laender.html 4 | 5 | TOTAL = 83166711 6 | 7 | STATES = { 8 | 'Baden-Württemberg': { 9 | 'total': 11100394 10 | }, 11 | 'Bayern': { 12 | 'total': 13124737 13 | }, 14 | 'Berlin': { 15 | 'total': 3669491 16 | }, 17 | 'Brandenburg': { 18 | 'total': 2521893 19 | }, 20 | 'Bremen': { 21 | 'total': 681202 22 | }, 23 | 'Hamburg': { 24 | 'total': 1847253 25 | }, 26 | 'Hessen': { 27 | 'total': 6288080 28 | }, 29 | 'Mecklenburg-Vorpommern': { 30 | 'total': 1608138 31 | }, 32 | 'Niedersachsen': { 33 | 'total': 7993608 34 | }, 35 | 'Nordrhein-Westfalen': { 36 | 'total': 17947221 37 | }, 38 | 'Rheinland-Pfalz': { 39 | 'total': 4093903 40 | }, 41 | 'Saarland': { 42 | 'total': 986887 43 | }, 44 | 'Sachsen': { 45 | 'total': 4071971 46 | }, 47 | 'Sachsen-Anhalt': { 48 | 'total': 2194782 49 | }, 50 | 'Schleswig-Holstein': { 51 | 'total': 2903773 52 | }, 53 | 'Thüringen': { 54 | 'total': 2133378 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /api/test/test_scrap_data.py: -------------------------------------------------------------------------------- 1 | """ Unittest """ 2 | import unittest 3 | # pylint: disable=import-error 4 | from _utils import scrap_data 5 | 6 | class TestModuleScrapData(unittest.TestCase): 7 | """ Test Module """ 8 | def test_get_file(self): 9 | """ Test for scrap_data.get_file """ 10 | self.assertEqual(type(scrap_data.get_file()).__name__, "bytes") 11 | def test_get_data(self): 12 | """ Test for scrap_data.get_data """ 13 | data = scrap_data.get_data() 14 | self.assertEqual(type(data).__name__, "dict") 15 | 16 | self.assertIn("lastUpdate", data.keys()) 17 | self.assertIn("states", data.keys()) 18 | self.assertIn("sumStates", data.keys()) 19 | self.assertIn("sumStates2nd", data.keys()) 20 | self.assertIn("sumDiffStates", data.keys()) 21 | self.assertIn("sumDiffStates2nd", data.keys()) 22 | self.assertIn("totalGermany", data.keys()) 23 | 24 | self.assertEqual(type(data["lastUpdate"]).__name__, "datetime") 25 | self.assertEqual(type(data["states"]).__name__, "dict") 26 | self.assertEqual(type(data["sumStates"]).__name__, "int") 27 | self.assertEqual(type(data["sumStates2nd"]).__name__, "int") 28 | self.assertEqual(type(data["sumDiffStates"]).__name__, "int") 29 | self.assertEqual(type(data["sumDiffStates2nd"]).__name__, "int") 30 | self.assertEqual(type(data["totalGermany"]).__name__, "int") 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /api/index.py: -------------------------------------------------------------------------------- 1 | """ API Endpont """ 2 | from http.server import BaseHTTPRequestHandler 3 | import json 4 | from datetime import datetime 5 | import pytz 6 | # pylint: disable=import-error 7 | from api._utils import scrap_data 8 | 9 | try: 10 | data = scrap_data.get_data() 11 | res = { 12 | 'notice': 'endpoint is deprecated, please use /api/v2', 13 | 'lastUpdate': data['lastUpdate'].isoformat(), 14 | 'states': data['states'], 15 | 'vaccinated': data['sumStates'], 16 | '2nd_vaccination': { 17 | 'vaccinated': data['sumStates2nd'], 18 | 'difference_to_the_previous_day': data['sumDiffStates2nd'] 19 | }, 20 | 'sum_vaccine_doses': data['sumStates'] + data['sumStates2nd'], 21 | 'difference_to_the_previous_day': data['sumDiffStates'], 22 | 'vaccinations_per_1000_inhabitants': round(data['sumStates'] / data['totalGermany'] * 1000, 2), 23 | 'total': data['totalGermany'], 24 | 'quote': round(data['sumStates'] / data['totalGermany'] * 100, 2) 25 | } 26 | HTTPCODE = 200 27 | except TypeError: 28 | res = { 29 | 'message': 'scrapping data from RKI excel failed' 30 | } 31 | HTTPCODE = 500 32 | 33 | class Handler(BaseHTTPRequestHandler): 34 | """ HTTP Handler """ 35 | # pylint: disable=invalid-name 36 | def do_GET(self): 37 | """ GET Method """ 38 | self.send_response(HTTPCODE) 39 | self.send_header('Content-Type', 'application/json') 40 | self.send_header('X-Cache-Timestamp', 41 | pytz.timezone('Europe/Berlin').localize(datetime.now()).isoformat()) 42 | self.send_header('Access-Control-Allow-Origin', '*') 43 | self.end_headers() 44 | self.wfile.write(json.dumps(res).encode()) 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | 132 | .DS_Store 133 | # Created by .ignore support plugin (hsz.mobi) 134 | ### Node template 135 | # Logs 136 | /logs 137 | *.log 138 | npm-debug.log* 139 | yarn-debug.log* 140 | yarn-error.log* 141 | 142 | # Runtime data 143 | pids 144 | *.pid 145 | *.seed 146 | *.pid.lock 147 | 148 | # Directory for instrumented libs generated by jscoverage/JSCover 149 | lib-cov 150 | 151 | # Coverage directory used by tools like istanbul 152 | coverage 153 | 154 | # nyc test coverage 155 | .nyc_output 156 | 157 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 158 | .grunt 159 | 160 | # Bower dependency directory (https://bower.io/) 161 | bower_components 162 | 163 | # node-waf configuration 164 | .lock-wscript 165 | 166 | # Compiled binary addons (https://nodejs.org/api/addons.html) 167 | build/Release 168 | 169 | # Dependency directories 170 | node_modules/ 171 | jspm_packages/ 172 | 173 | # TypeScript v1 declaration files 174 | typings/ 175 | 176 | # Optional npm cache directory 177 | .npm 178 | 179 | # Optional eslint cache 180 | .eslintcache 181 | 182 | # Optional REPL history 183 | .node_repl_history 184 | 185 | # Output of 'npm pack' 186 | *.tgz 187 | 188 | # Yarn Integrity file 189 | .yarn-integrity 190 | 191 | # dotenv environment variables file 192 | .env 193 | 194 | # parcel-bundler cache (https://parceljs.org/) 195 | .cache 196 | 197 | # next.js build output 198 | .next 199 | 200 | # nuxt.js build output 201 | .nuxt 202 | 203 | # Nuxt generate 204 | dist 205 | 206 | # vuepress build output 207 | .vuepress/dist 208 | 209 | # Serverless directories 210 | .serverless 211 | 212 | # IDE / Editor 213 | .idea 214 | 215 | # Service worker 216 | sw.* 217 | 218 | # macOS 219 | .DS_Store 220 | 221 | # Vim swap files 222 | *.swp 223 | 224 | .vercel 225 | .now 226 | package-lock.json 227 | public 228 | .vscode -------------------------------------------------------------------------------- /api/_utils/scrap_data.py: -------------------------------------------------------------------------------- 1 | """ Scrap Data from Excel sheet """ 2 | import re 3 | import datetime 4 | from io import BytesIO 5 | import urllib.request 6 | import openpyxl 7 | from .statics import inhabitants 8 | 9 | 10 | def get_file(): 11 | """ Get remote file content """ 12 | # Request to load excel sheet 13 | url = 'https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Daten' \ 14 | '/Impfquotenmonitoring.xlsx?__blob=publicationFile' 15 | hdr = { 16 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11' 17 | ' (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11'} 18 | 19 | req = urllib.request.Request(url, headers=hdr) 20 | with urllib.request.urlopen(req) as response: 21 | return response.read() 22 | 23 | 24 | def get_data(): 25 | """ Get Data for API """ 26 | states = inhabitants.STATES 27 | 28 | file = get_file() 29 | 30 | # Read excel sheet 31 | work_book = openpyxl.load_workbook(filename=BytesIO(file)) 32 | sheet = work_book[work_book.sheetnames[2]] 33 | 34 | # Load update time 35 | first_sheet = work_book[work_book.sheetnames[0]] 36 | relast_update_match = re.search( 37 | r"[\d]{2}\.[\d]{2}\.[\d]{2}", first_sheet['A3'].value) 38 | last_update = datetime.datetime.strptime( 39 | relast_update_match.group(), '%d.%m.%y') 40 | 41 | # Load data from rows 42 | sum_states = 0 43 | sum_states2nd = 0 44 | sum_diff_states = 0 45 | sum_diff_states2nd = 0 46 | for row in sheet.iter_rows(max_row=21): 47 | if row[1].value is None: 48 | continue 49 | state = row[1].value.replace("*", "").strip() 50 | 51 | if state in states: 52 | states[state]['rs'] = str(row[0].value) 53 | 54 | # First vaccination 55 | states[state]['vaccinated'] = row[2].value 56 | states[state]['vaccinated_by_accine'] = {} 57 | states[state]['vaccinated_by_accine']['biontech'] = row[3].value 58 | states[state]['vaccinated_by_accine']['moderna'] = row[4].value 59 | states[state]['vaccinated_by_accine']['astrazeneca'] = row[5].value 60 | # states[state]['vaccinated_by_accine']['janssen'] = row[6].value 61 | states[state]['difference_to_the_previous_day'] = row[8].value 62 | states[state]['vaccinations_per_1000_inhabitants'] = round(states[state]['vaccinated'] 63 | / states[state]['total'] * 1000, 2) 64 | states[state]['quote'] = round( 65 | states[state]['vaccinated'] / states[state]['total'] * 100, 2) 66 | 67 | sum_states += states[state]['vaccinated'] 68 | sum_diff_states += states[state]['difference_to_the_previous_day'] 69 | 70 | # Second vaccination 71 | states[state]['2nd_vaccination'] = {} 72 | states[state]['2nd_vaccination']['vaccinated'] = row[9].value 73 | states[state]['2nd_vaccination']['vaccinated_by_accine'] = {} 74 | states[state]['2nd_vaccination']['vaccinated_by_accine']['biontech'] = row[10].value 75 | states[state]['2nd_vaccination']['vaccinated_by_accine']['moderna'] = row[11].value 76 | states[state]['2nd_vaccination']['vaccinated_by_accine']['astrazeneca'] = row[12].value 77 | states[state]['2nd_vaccination']['vaccinated_by_accine']['janssen'] = row[6].value 78 | states[state]['2nd_vaccination']['difference_to_the_previous_day'] = row[14].value 79 | states[state]['2nd_vaccination']['quote'] = round( 80 | states[state]['2nd_vaccination']['vaccinated'] / states[state]['total'] * 100, 2) 81 | 82 | sum_states2nd += states[state]['2nd_vaccination']['vaccinated'] 83 | 84 | if states[state]['2nd_vaccination']['difference_to_the_previous_day'] is not None: 85 | sum_diff_states2nd += states[state]['2nd_vaccination']['difference_to_the_previous_day'] 86 | 87 | elif state == 'Bundesressorts': 88 | sum_states += row[2].value 89 | sum_diff_states += row[8].value 90 | sum_states2nd += row[9].value 91 | sum_diff_states2nd += row[14].value 92 | 93 | return { 94 | "lastUpdate": last_update, 95 | "states": states, 96 | "sumStates": sum_states, 97 | "sumStates2nd": sum_states2nd, 98 | "sumDiffStates": sum_diff_states, 99 | "sumDiffStates2nd": sum_diff_states2nd, 100 | "totalGermany": inhabitants.TOTAL 101 | } 102 | -------------------------------------------------------------------------------- /api/_utils/scrap_data_v2.py: -------------------------------------------------------------------------------- 1 | """ Scrap Data from Excel sheet """ 2 | import re 3 | import datetime 4 | from io import BytesIO 5 | import urllib.request 6 | import openpyxl 7 | from .statics import inhabitants 8 | 9 | 10 | def get_file(): 11 | """ Get remote file content """ 12 | # Request to load excel sheet 13 | url = 'https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Daten' \ 14 | '/Impfquotenmonitoring.xlsx?__blob=publicationFile' 15 | hdr = { 16 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11' 17 | ' (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11'} 18 | 19 | req = urllib.request.Request(url, headers=hdr) 20 | with urllib.request.urlopen(req) as response: 21 | return response.read() 22 | 23 | 24 | def get_data(): 25 | """ Get Data for API """ 26 | file = get_file() 27 | 28 | # Read excel sheet 29 | work_book = openpyxl.load_workbook(filename=BytesIO(file)) 30 | sheet = work_book[work_book.sheetnames[2]] 31 | 32 | # Load update time 33 | first_sheet = work_book[work_book.sheetnames[0]] 34 | relast_update_match = re.search( 35 | r"[\d]{2}\.[\d]{2}\.[\d]{2}", first_sheet['A3'].value) 36 | last_update = datetime.datetime.strptime( 37 | relast_update_match.group(), '%d.%m.%y') 38 | 39 | # Load data from rows 40 | rows = [] 41 | for row in sheet.iter_rows(max_row=21): 42 | if row[1].value is None: 43 | continue 44 | state = row[1].value.replace("*", "").strip() 45 | if state in inhabitants.STATES or state in ['Bundesressorts', 'Gesamt']: 46 | # Workaround for novavax 47 | row[7].value = row[7].value if row[7].value != "-" else 0 48 | row[13].value = row[13].value if row[13].value != "-" else 0 49 | 50 | if state == 'Bundesressorts': 51 | total = 0 52 | elif state == 'Gesamt': 53 | total = inhabitants.TOTAL 54 | state = "Deutschland" 55 | else: 56 | total = inhabitants.STATES[state]['total'] 57 | data = { 58 | "name": state, 59 | "inhabitants": total, 60 | "isState": state in inhabitants.STATES, 61 | "rs": str(row[0].value) if row[0].value is not None else "", 62 | "vaccinatedAtLeastOnce": { 63 | "doses": row[2].value, 64 | "quote": round(row[2].value / total * 100, 2) if total != 0 else 0, 65 | "differenceToThePreviousDay": row[8].value, 66 | "vaccine": [ 67 | { 68 | "name": "biontech", 69 | "doses": row[3].value 70 | }, 71 | { 72 | "name": "moderna", 73 | "doses": row[4].value 74 | }, 75 | { 76 | "name": "astrazeneca", 77 | "doses": row[5].value 78 | }, 79 | { 80 | "name": "janssen", 81 | "doses": row[6].value 82 | }, 83 | { 84 | "name": "novavax", 85 | "doses": row[7].value 86 | } 87 | ] 88 | }, 89 | "fullyVaccinated": { 90 | "doses": row[9].value + row[6].value, 91 | "quote": round((row[9].value + row[6].value) / total * 100, 2) if total != 0 else 0, 92 | "differenceToThePreviousDay": row[14].value, 93 | "vaccine": [ 94 | { 95 | "name": "biontech", 96 | "firstDoses": row[3].value, 97 | "secondDoses": row[10].value, 98 | "totalDoses": row[3].value + row[10].value 99 | }, 100 | { 101 | "name": "moderna", 102 | "firstDoses": row[4].value, 103 | "secondDoses": row[11].value, 104 | "totalDoses": row[4].value + row[11].value 105 | }, 106 | { 107 | "name": "astrazeneca", 108 | "firstDoses": row[5].value, 109 | "secondDoses": row[12].value, 110 | "totalDoses": row[5].value + row[12].value 111 | }, 112 | { 113 | "name": "janssen", 114 | "firstDoses": row[6].value, 115 | "totalDoses": row[6].value 116 | }, 117 | { 118 | "name": "novavax", 119 | "firstDoses": row[7].value, 120 | "secondDoses": row[13].value, 121 | "totalDoses": row[7].value + row[13].value 122 | } 123 | ] 124 | }, 125 | "boosterVaccinated": { 126 | "doses": row[15].value, 127 | "quote": round(row[15].value / total * 100, 2) if total != 0 else 0, 128 | "differenceToThePreviousDay": row[19].value, 129 | "vaccine": [ 130 | { 131 | "name": "biontech", 132 | "firstDoses": row[3].value, 133 | "secondDoses": row[10].value, 134 | "boosterDoses": row[16].value, 135 | "totalDoses": row[3].value + row[10].value + row[16].value 136 | }, 137 | { 138 | "name": "moderna", 139 | "firstDoses": row[4].value, 140 | "secondDoses": row[11].value, 141 | "boosterDoses": row[17].value, 142 | "totalDoses": row[4].value + row[11].value + row[17].value 143 | }, 144 | { 145 | "name": "janssen", 146 | "firstDoses": row[6].value, 147 | "boosterDoses": row[18].value, 148 | "totalDoses": row[6].value + row[18].value 149 | } 150 | ] 151 | } 152 | } 153 | rows.append(data) 154 | return { 155 | "lastUpdate": last_update, 156 | "data": rows 157 | } 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | lang: en-EN 4 | footer: Made with ❤️ in Düsseldorf 5 | --- 6 | ![CI](https://github.com/ThisIsBenny/rki-vaccination-data/workflows/CI/badge.svg) 7 | 8 | __Update 01.05.2022__: The API has been discontinued! The RKI has constantly introduced breaking changes and the last change no longer provides the data in the usual form. I don’t have the time to keep the api up to date, why i shutdown the API. 9 | 10 | The API provides the current covid-19 vaccination data of the 16 German federal states as JSON. 11 | The data source is an Excel sheet provided by RKI. The data will be updated every working day by the RKI. 12 | 13 | # API 14 | 15 | _Note: v2 of the API was introduced and the documentation was adapted to the new version. v1 of the API is still available._ 16 | 17 | Base-URL: `https://rki-vaccination-data.vercel.app` 18 | 19 | ## Show vaccination data 20 | 21 | Returns json data with the numbers for germany and every state. 22 | 23 | ### Endpoint 24 | 25 | ``` 26 | GET https://rki-vaccination-data.vercel.app/api/v2 27 | ``` 28 | 29 | ### Response 30 | 31 | ```json 32 | { 33 | "lastUpdate": "2021-06-10T00:00:00", 34 | "data": [{ 35 | "name": "Baden-Württemberg", 36 | "inhabitants": 11100394, 37 | "isState": true, 38 | "rs": "08", 39 | "vaccinatedAtLeastOnce": { 40 | "doses": 5162993, 41 | "quote": 46.51, 42 | "differenceToThePreviousDay": 54493, 43 | "vaccine": [{ 44 | "name": "biontech", 45 | "doses": 3458328 46 | }, { 47 | "name": "moderna", 48 | "doses": 443518 49 | }, { 50 | "name": "astrazeneca", 51 | "doses": 1129631 52 | }, { 53 | "name": "janssen", 54 | "doses": 131516 55 | }] 56 | }, 57 | "fullyVaccinated": { 58 | "doses": 2679782, 59 | "quote": 24.14, 60 | "differenceToThePreviousDay": 105744, 61 | "vaccine": [{ 62 | "name": "biontech", 63 | "doses": 2171831 64 | }, { 65 | "name": "moderna", 66 | "doses": 211981 67 | }, { 68 | "name": "astrazeneca", 69 | "doses": 164454 70 | }, { 71 | "name": "janssen", 72 | "doses": 131516 73 | }] 74 | } 75 | }, { 76 | "name": "Bayern", 77 | "inhabitants": 13124737, 78 | "isState": true, 79 | "rs": "09", 80 | "vaccinatedAtLeastOnce": { 81 | "doses": 5997556, 82 | "quote": 45.7, 83 | "differenceToThePreviousDay": 50791, 84 | "vaccine": [{ 85 | "name": "biontech", 86 | "doses": 4160449 87 | }, { 88 | "name": "moderna", 89 | "doses": 406212 90 | }, { 91 | "name": "astrazeneca", 92 | "doses": 1288901 93 | }, { 94 | "name": "janssen", 95 | "doses": 141994 96 | }] 97 | }, 98 | "fullyVaccinated": { 99 | "doses": 3266743, 100 | "quote": 24.89, 101 | "differenceToThePreviousDay": 123492, 102 | "vaccine": [{ 103 | "name": "biontech", 104 | "doses": 2683410 105 | }, { 106 | "name": "moderna", 107 | "doses": 257641 108 | }, { 109 | "name": "astrazeneca", 110 | "doses": 183698 111 | }, { 112 | "name": "janssen", 113 | "doses": 141994 114 | }] 115 | } 116 | }, { 117 | "name": "Berlin", 118 | "inhabitants": 3669491, 119 | "isState": true, 120 | "rs": "11", 121 | "vaccinatedAtLeastOnce": { 122 | "doses": 1748925, 123 | "quote": 47.66, 124 | "differenceToThePreviousDay": 21612, 125 | "vaccine": [{ 126 | "name": "biontech", 127 | "doses": 1173163 128 | }, { 129 | "name": "moderna", 130 | "doses": 184189 131 | }, { 132 | "name": "astrazeneca", 133 | "doses": 348119 134 | }, { 135 | "name": "janssen", 136 | "doses": 43454 137 | }] 138 | }, 139 | "fullyVaccinated": { 140 | "doses": 863354, 141 | "quote": 23.53, 142 | "differenceToThePreviousDay": 29028, 143 | "vaccine": [{ 144 | "name": "biontech", 145 | "doses": 714850 146 | }, { 147 | "name": "moderna", 148 | "doses": 71379 149 | }, { 150 | "name": "astrazeneca", 151 | "doses": 33671 152 | }, { 153 | "name": "janssen", 154 | "doses": 43454 155 | }] 156 | } 157 | }, { 158 | "name": "Brandenburg", 159 | "inhabitants": 2521893, 160 | "isState": true, 161 | "rs": "12", 162 | "vaccinatedAtLeastOnce": { 163 | "doses": 1148445, 164 | "quote": 45.54, 165 | "differenceToThePreviousDay": 23288, 166 | "vaccine": [{ 167 | "name": "biontech", 168 | "doses": 766860 169 | }, { 170 | "name": "moderna", 171 | "doses": 98322 172 | }, { 173 | "name": "astrazeneca", 174 | "doses": 243246 175 | }, { 176 | "name": "janssen", 177 | "doses": 40017 178 | }] 179 | }, 180 | "fullyVaccinated": { 181 | "doses": 605625, 182 | "quote": 24.01, 183 | "differenceToThePreviousDay": 19476, 184 | "vaccine": [{ 185 | "name": "biontech", 186 | "doses": 477478 187 | }, { 188 | "name": "moderna", 189 | "doses": 53359 190 | }, { 191 | "name": "astrazeneca", 192 | "doses": 34771 193 | }, { 194 | "name": "janssen", 195 | "doses": 40017 196 | }] 197 | } 198 | }, { 199 | "name": "Bremen", 200 | "inhabitants": 681202, 201 | "isState": true, 202 | "rs": "04", 203 | "vaccinatedAtLeastOnce": { 204 | "doses": 354840, 205 | "quote": 52.09, 206 | "differenceToThePreviousDay": 5815, 207 | "vaccine": [{ 208 | "name": "biontech", 209 | "doses": 224174 210 | }, { 211 | "name": "moderna", 212 | "doses": 21549 213 | }, { 214 | "name": "astrazeneca", 215 | "doses": 94450 216 | }, { 217 | "name": "janssen", 218 | "doses": 14667 219 | }] 220 | }, 221 | "fullyVaccinated": { 222 | "doses": 187163, 223 | "quote": 27.48, 224 | "differenceToThePreviousDay": 5294, 225 | "vaccine": [{ 226 | "name": "biontech", 227 | "doses": 149527 228 | }, { 229 | "name": "moderna", 230 | "doses": 13728 231 | }, { 232 | "name": "astrazeneca", 233 | "doses": 9241 234 | }, { 235 | "name": "janssen", 236 | "doses": 14667 237 | }] 238 | } 239 | }, { 240 | "name": "Hamburg", 241 | "inhabitants": 1847253, 242 | "isState": true, 243 | "rs": "02", 244 | "vaccinatedAtLeastOnce": { 245 | "doses": 825353, 246 | "quote": 44.68, 247 | "differenceToThePreviousDay": 8959, 248 | "vaccine": [{ 249 | "name": "biontech", 250 | "doses": 542067 251 | }, { 252 | "name": "moderna", 253 | "doses": 80893 254 | }, { 255 | "name": "astrazeneca", 256 | "doses": 176029 257 | }, { 258 | "name": "janssen", 259 | "doses": 26364 260 | }] 261 | }, 262 | "fullyVaccinated": { 263 | "doses": 437952, 264 | "quote": 23.71, 265 | "differenceToThePreviousDay": 17014, 266 | "vaccine": [{ 267 | "name": "biontech", 268 | "doses": 353002 269 | }, { 270 | "name": "moderna", 271 | "doses": 48522 272 | }, { 273 | "name": "astrazeneca", 274 | "doses": 10064 275 | }, { 276 | "name": "janssen", 277 | "doses": 26364 278 | }] 279 | } 280 | }, { 281 | "name": "Hessen", 282 | "inhabitants": 6288080, 283 | "isState": true, 284 | "rs": "06", 285 | "vaccinatedAtLeastOnce": { 286 | "doses": 2967688, 287 | "quote": 47.2, 288 | "differenceToThePreviousDay": 26924, 289 | "vaccine": [{ 290 | "name": "biontech", 291 | "doses": 1982634 292 | }, { 293 | "name": "moderna", 294 | "doses": 231673 295 | }, { 296 | "name": "astrazeneca", 297 | "doses": 688018 298 | }, { 299 | "name": "janssen", 300 | "doses": 65363 301 | }] 302 | }, 303 | "fullyVaccinated": { 304 | "doses": 1395446, 305 | "quote": 22.19, 306 | "differenceToThePreviousDay": 56931, 307 | "vaccine": [{ 308 | "name": "biontech", 309 | "doses": 1132737 310 | }, { 311 | "name": "moderna", 312 | "doses": 91533 313 | }, { 314 | "name": "astrazeneca", 315 | "doses": 105813 316 | }, { 317 | "name": "janssen", 318 | "doses": 65363 319 | }] 320 | } 321 | }, { 322 | "name": "Mecklenburg-Vorpommern", 323 | "inhabitants": 1608138, 324 | "isState": true, 325 | "rs": "13", 326 | "vaccinatedAtLeastOnce": { 327 | "doses": 785489, 328 | "quote": 48.84, 329 | "differenceToThePreviousDay": 8589, 330 | "vaccine": [{ 331 | "name": "biontech", 332 | "doses": 560146 333 | }, { 334 | "name": "moderna", 335 | "doses": 63599 336 | }, { 337 | "name": "astrazeneca", 338 | "doses": 142080 339 | }, { 340 | "name": "janssen", 341 | "doses": 19664 342 | }] 343 | }, 344 | "fullyVaccinated": { 345 | "doses": 429517, 346 | "quote": 26.71, 347 | "differenceToThePreviousDay": 20498, 348 | "vaccine": [{ 349 | "name": "biontech", 350 | "doses": 364389 351 | }, { 352 | "name": "moderna", 353 | "doses": 28337 354 | }, { 355 | "name": "astrazeneca", 356 | "doses": 17127 357 | }, { 358 | "name": "janssen", 359 | "doses": 19664 360 | }] 361 | } 362 | }, { 363 | "name": "Niedersachsen", 364 | "inhabitants": 7993608, 365 | "isState": true, 366 | "rs": "03", 367 | "vaccinatedAtLeastOnce": { 368 | "doses": 3865057, 369 | "quote": 48.35, 370 | "differenceToThePreviousDay": 41865, 371 | "vaccine": [{ 372 | "name": "biontech", 373 | "doses": 2403169 374 | }, { 375 | "name": "moderna", 376 | "doses": 236562 377 | }, { 378 | "name": "astrazeneca", 379 | "doses": 1059786 380 | }, { 381 | "name": "janssen", 382 | "doses": 165540 383 | }] 384 | }, 385 | "fullyVaccinated": { 386 | "doses": 1911217, 387 | "quote": 23.91, 388 | "differenceToThePreviousDay": 83860, 389 | "vaccine": [{ 390 | "name": "biontech", 391 | "doses": 1513838 392 | }, { 393 | "name": "moderna", 394 | "doses": 156927 395 | }, { 396 | "name": "astrazeneca", 397 | "doses": 74912 398 | }, { 399 | "name": "janssen", 400 | "doses": 165540 401 | }] 402 | } 403 | }, { 404 | "name": "Nordrhein-Westfalen", 405 | "inhabitants": 17947221, 406 | "isState": true, 407 | "rs": "05", 408 | "vaccinatedAtLeastOnce": { 409 | "doses": 8981678, 410 | "quote": 50.04, 411 | "differenceToThePreviousDay": 75307, 412 | "vaccine": [{ 413 | "name": "biontech", 414 | "doses": 5900753 415 | }, { 416 | "name": "moderna", 417 | "doses": 649639 418 | }, { 419 | "name": "astrazeneca", 420 | "doses": 2142432 421 | }, { 422 | "name": "janssen", 423 | "doses": 288854 424 | }] 425 | }, 426 | "fullyVaccinated": { 427 | "doses": 4603066, 428 | "quote": 25.65, 429 | "differenceToThePreviousDay": 167665, 430 | "vaccine": [{ 431 | "name": "biontech", 432 | "doses": 3860158 433 | }, { 434 | "name": "moderna", 435 | "doses": 263053 436 | }, { 437 | "name": "astrazeneca", 438 | "doses": 191001 439 | }, { 440 | "name": "janssen", 441 | "doses": 288854 442 | }] 443 | } 444 | }, { 445 | "name": "Rheinland-Pfalz", 446 | "inhabitants": 4093903, 447 | "isState": true, 448 | "rs": "07", 449 | "vaccinatedAtLeastOnce": { 450 | "doses": 1912371, 451 | "quote": 46.71, 452 | "differenceToThePreviousDay": 26711, 453 | "vaccine": [{ 454 | "name": "biontech", 455 | "doses": 1286009 456 | }, { 457 | "name": "moderna", 458 | "doses": 168833 459 | }, { 460 | "name": "astrazeneca", 461 | "doses": 408676 462 | }, { 463 | "name": "janssen", 464 | "doses": 48853 465 | }] 466 | }, 467 | "fullyVaccinated": { 468 | "doses": 1016635, 469 | "quote": 24.83, 470 | "differenceToThePreviousDay": 29445, 471 | "vaccine": [{ 472 | "name": "biontech", 473 | "doses": 858066 474 | }, { 475 | "name": "moderna", 476 | "doses": 55760 477 | }, { 478 | "name": "astrazeneca", 479 | "doses": 53956 480 | }, { 481 | "name": "janssen", 482 | "doses": 48853 483 | }] 484 | } 485 | }, { 486 | "name": "Saarland", 487 | "inhabitants": 986887, 488 | "isState": true, 489 | "rs": "10", 490 | "vaccinatedAtLeastOnce": { 491 | "doses": 502948, 492 | "quote": 50.96, 493 | "differenceToThePreviousDay": 5962, 494 | "vaccine": [{ 495 | "name": "biontech", 496 | "doses": 376290 497 | }, { 498 | "name": "moderna", 499 | "doses": 36157 500 | }, { 501 | "name": "astrazeneca", 502 | "doses": 78632 503 | }, { 504 | "name": "janssen", 505 | "doses": 11869 506 | }] 507 | }, 508 | "fullyVaccinated": { 509 | "doses": 289544, 510 | "quote": 29.34, 511 | "differenceToThePreviousDay": 10794, 512 | "vaccine": [{ 513 | "name": "biontech", 514 | "doses": 250920 515 | }, { 516 | "name": "moderna", 517 | "doses": 16011 518 | }, { 519 | "name": "astrazeneca", 520 | "doses": 10744 521 | }, { 522 | "name": "janssen", 523 | "doses": 11869 524 | }] 525 | } 526 | }, { 527 | "name": "Sachsen", 528 | "inhabitants": 4071971, 529 | "isState": true, 530 | "rs": "14", 531 | "vaccinatedAtLeastOnce": { 532 | "doses": 1736798, 533 | "quote": 42.65, 534 | "differenceToThePreviousDay": 23409, 535 | "vaccine": [{ 536 | "name": "biontech", 537 | "doses": 1194663 538 | }, { 539 | "name": "moderna", 540 | "doses": 161736 541 | }, { 542 | "name": "astrazeneca", 543 | "doses": 336326 544 | }, { 545 | "name": "janssen", 546 | "doses": 44073 547 | }] 548 | }, 549 | "fullyVaccinated": { 550 | "doses": 1067551, 551 | "quote": 26.22, 552 | "differenceToThePreviousDay": 25766, 553 | "vaccine": [{ 554 | "name": "biontech", 555 | "doses": 898957 556 | }, { 557 | "name": "moderna", 558 | "doses": 81087 559 | }, { 560 | "name": "astrazeneca", 561 | "doses": 43434 562 | }, { 563 | "name": "janssen", 564 | "doses": 44073 565 | }] 566 | } 567 | }, { 568 | "name": "Sachsen-Anhalt", 569 | "inhabitants": 2194782, 570 | "isState": true, 571 | "rs": "15", 572 | "vaccinatedAtLeastOnce": { 573 | "doses": 985654, 574 | "quote": 44.91, 575 | "differenceToThePreviousDay": 13588, 576 | "vaccine": [{ 577 | "name": "biontech", 578 | "doses": 677695 579 | }, { 580 | "name": "moderna", 581 | "doses": 85185 582 | }, { 583 | "name": "astrazeneca", 584 | "doses": 184491 585 | }, { 586 | "name": "janssen", 587 | "doses": 38283 588 | }] 589 | }, 590 | "fullyVaccinated": { 591 | "doses": 540620, 592 | "quote": 24.63, 593 | "differenceToThePreviousDay": 19655, 594 | "vaccine": [{ 595 | "name": "biontech", 596 | "doses": 430185 597 | }, { 598 | "name": "moderna", 599 | "doses": 46119 600 | }, { 601 | "name": "astrazeneca", 602 | "doses": 26033 603 | }, { 604 | "name": "janssen", 605 | "doses": 38283 606 | }] 607 | } 608 | }, { 609 | "name": "Schleswig-Holstein", 610 | "inhabitants": 2903773, 611 | "isState": true, 612 | "rs": "01", 613 | "vaccinatedAtLeastOnce": { 614 | "doses": 1453333, 615 | "quote": 50.05, 616 | "differenceToThePreviousDay": 21580, 617 | "vaccine": [{ 618 | "name": "biontech", 619 | "doses": 984112 620 | }, { 621 | "name": "moderna", 622 | "doses": 101284 623 | }, { 624 | "name": "astrazeneca", 625 | "doses": 324245 626 | }, { 627 | "name": "janssen", 628 | "doses": 43692 629 | }] 630 | }, 631 | "fullyVaccinated": { 632 | "doses": 757298, 633 | "quote": 26.08, 634 | "differenceToThePreviousDay": 22415, 635 | "vaccine": [{ 636 | "name": "biontech", 637 | "doses": 612893 638 | }, { 639 | "name": "moderna", 640 | "doses": 57750 641 | }, { 642 | "name": "astrazeneca", 643 | "doses": 42963 644 | }, { 645 | "name": "janssen", 646 | "doses": 43692 647 | }] 648 | } 649 | }, { 650 | "name": "Thüringen", 651 | "inhabitants": 2133378, 652 | "isState": true, 653 | "rs": "16", 654 | "vaccinatedAtLeastOnce": { 655 | "doses": 974457, 656 | "quote": 45.68, 657 | "differenceToThePreviousDay": 10321, 658 | "vaccine": [{ 659 | "name": "biontech", 660 | "doses": 679316 661 | }, { 662 | "name": "moderna", 663 | "doses": 112873 664 | }, { 665 | "name": "astrazeneca", 666 | "doses": 161076 667 | }, { 668 | "name": "janssen", 669 | "doses": 21192 670 | }] 671 | }, 672 | "fullyVaccinated": { 673 | "doses": 534526, 674 | "quote": 25.06, 675 | "differenceToThePreviousDay": 17225, 676 | "vaccine": [{ 677 | "name": "biontech", 678 | "doses": 448856 679 | }, { 680 | "name": "moderna", 681 | "doses": 32616 682 | }, { 683 | "name": "astrazeneca", 684 | "doses": 31862 685 | }, { 686 | "name": "janssen", 687 | "doses": 21192 688 | }] 689 | } 690 | }, { 691 | "name": "Bundesressorts", 692 | "inhabitants": 0, 693 | "isState": false, 694 | "rs": "", 695 | "vaccinatedAtLeastOnce": { 696 | "doses": 135585, 697 | "quote": 0, 698 | "differenceToThePreviousDay": 6622, 699 | "vaccine": [{ 700 | "name": "biontech", 701 | "doses": 39751 702 | }, { 703 | "name": "moderna", 704 | "doses": 73836 705 | }, { 706 | "name": "astrazeneca", 707 | "doses": 18293 708 | }, { 709 | "name": "janssen", 710 | "doses": 3705 711 | }] 712 | }, 713 | "fullyVaccinated": { 714 | "doses": 62422, 715 | "quote": 0, 716 | "differenceToThePreviousDay": 2140, 717 | "vaccine": [{ 718 | "name": "biontech", 719 | "doses": 2038 720 | }, { 721 | "name": "moderna", 722 | "doses": 50093 723 | }, { 724 | "name": "astrazeneca", 725 | "doses": 6586 726 | }, { 727 | "name": "janssen", 728 | "doses": 3705 729 | }] 730 | } 731 | }, { 732 | "name": "Deutschland", 733 | "inhabitants": 83166711, 734 | "isState": false, 735 | "rs": "", 736 | "vaccinatedAtLeastOnce": { 737 | "doses": 39539170, 738 | "quote": 47.54, 739 | "differenceToThePreviousDay": 425836, 740 | "vaccine": [{ 741 | "name": "biontech", 742 | "doses": 26409579 743 | }, { 744 | "name": "moderna", 745 | "doses": 3156060 746 | }, { 747 | "name": "astrazeneca", 748 | "doses": 8824431 749 | }, { 750 | "name": "janssen", 751 | "doses": 1149100 752 | }] 753 | }, 754 | "fullyVaccinated": { 755 | "doses": 20648461, 756 | "quote": 24.83, 757 | "differenceToThePreviousDay": 756442, 758 | "vaccine": [{ 759 | "name": "biontech", 760 | "doses": 16923135 761 | }, { 762 | "name": "moderna", 763 | "doses": 1535896 764 | }, { 765 | "name": "astrazeneca", 766 | "doses": 1040330 767 | }, { 768 | "name": "janssen", 769 | "doses": 1149100 770 | }] 771 | } 772 | }] 773 | } 774 | ``` 775 | 776 | ### Sample Call 777 | 778 | cURL: 779 | 780 | ```sh 781 | curl -X GET 'https://rki-vaccination-data.vercel.app/api/v2' 782 | ``` 783 | 784 | jQuery: 785 | 786 | ```javascript 787 | var settings = { 788 | "url": "https://rki-vaccination-data.vercel.app/api/v2", 789 | "method": "GET" 790 | }; 791 | 792 | $.ajax(settings).done(function (response) { 793 | console.log(response); 794 | }); 795 | ``` 796 | 797 | Scriptable App: 798 | 799 | ```javascript 800 | const req = new Request('https://rki-vaccination-data.vercel.app/api/v2') 801 | const res = await req.loadJSON() 802 | console.log(res) 803 | ``` 804 | 805 | # Code 806 | 807 | The API Code is Open-Source 808 | 809 | ## API Interface 810 | 811 | <<< @/api/v2.py 812 | 813 | ## Scrapping Data 814 | 815 | <<< @/api/_utils/scrap_data_v2.py 816 | 817 | # Data-Sources 818 | 819 | * [https://www.destatis.de/DE/Themen/Gesellschaft-Umwelt/Bevoelkerung/Bevoelkerungsstand/Tabellen/bevoelkerung-nichtdeutsch-laender.html](https://www.destatis.de/DE/Themen/Gesellschaft-Umwelt/Bevoelkerung/Bevoelkerungsstand/Tabellen/bevoelkerung-nichtdeutsch-laender.html) 820 | * [https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Daten/Impfquoten-Tab.html](https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Daten/Impfquoten-Tab.html) 821 | 822 | Licence: Robert Koch-Institut (RKI), dl-de/by-2-0 823 | 824 | # Apps / websites that use the API 825 | 826 | * [Number of Covid 19 vaccinations](https://widget-hub.app/widget/5fec48637b99ef0008e8a27d/number-of-covid-19-vaccinations) (iOS Scriptable Widget) 827 | * [Impf-Status in Deutschland](https://play.google.com/store/apps/details?id=de.dschlapa.vaccination) (Android) 828 | * [Hotspot or not?](https://hotspotornot.de/) 829 | --------------------------------------------------------------------------------