Browse Source

Eerste versie van het Voedingscentrum script

Stan Jansen 4 years ago
commit
465579d7f0
4 changed files with 247 additions and 0 deletions
  1. 16 0
      README.md
  2. 102 0
      migrations.pgsql
  3. 3 0
      requirements.txt
  4. 126 0
      voedingscentrum.py

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+# Voedingscentrum Data Importer
+
+Dit script logt in op je account bij Voedingscentrum.nl en haalt daar je ingevulde consumptiegegevens op, en importeert deze in een PostgreSQL database.
+
+## Installatie
+
+   pip3 install --user -r requirements.txt
+   psql -e CREATE DATABASE voeding
+   cat migrations.pgsql | sed -r "s/USERNAME/`whoami`/g" | psql -d voeding
+
+## Gebruik
+
+Bij het opstarten van het script dien je je gegevens als command-line argumenten op te geven. Voor informatie over de argumenten voer je het volgende commando uit:
+
+   python3 voedingscentrum.py -h
+

+ 102 - 0
migrations.pgsql

@@ -0,0 +1,102 @@
+CREATE TABLE producten (guid TEXT, naam TEXT, energie_kcal NUMERIC,
+                        vet_g NUMERIC,
+                        verzadigd_vet_g NUMERIC,
+                        koolhydraten_g NUMERIC,
+                        eiwit_g NUMERIC,
+                        vezels_g NUMERIC,
+                        zout_g NUMERIC,
+                        alcohol_g NUMERIC,
+                        water_g NUMERIC,
+                        natrium_mg NUMERIC,
+                        kalium_mg NUMERIC,
+                        calcium_mg NUMERIC,
+                        magnesium_mg NUMERIC,
+                        ijzer_mg NUMERIC,
+                        selenium_µg NUMERIC,
+                        zink_mg NUMERIC,
+                        vitamine_a_µg NUMERIC,
+                        vitamine_d_µg NUMERIC,
+                        vitamine_e_mg NUMERIC,
+                        vitamine_b1_g NUMERIC,
+                        vitamine_b2_mg NUMERIC,
+                        vitamine_b6_mg NUMERIC,
+                        foliumzuur_µg NUMERIC,
+                        vitamine_b12_µg NUMERIC,
+                        nicotinezuur_mg NUMERIC,
+                        vitamine_c_mg NUMERIC,
+                        jodium_µg NUMERIC,
+                        fosfor_mg NUMERIC,
+                        suikers_g NUMERIC,
+                        eiwit_plantaardig_g NUMERIC);
+
+CREATE TABLE consumpties_USERNAME (id SERIAL, datum DATE, product_guid TEXT, periode TEXT, hoeveelheid_g NUMERIC);
+
+CREATE VIEW voedingswaarden_per_dag_USERNAME AS
+(WITH sub AS (SELECT datum,
+                     hoeveelheid_g / 100 * energie_kcal AS energie_kcal,
+                     hoeveelheid_g / 100 * vet_g AS vet_g,
+                     hoeveelheid_g / 100 * verzadigd_vet_g AS verzadigd_vet_g,
+                     hoeveelheid_g / 100 * koolhydraten_g AS koolhydraten_g,
+                     hoeveelheid_g / 100 * eiwit_g AS eiwit_g,
+                     hoeveelheid_g / 100 * vezels_g AS vezels_g,
+                     hoeveelheid_g / 100 * zout_g AS zout_g,
+                     hoeveelheid_g / 100 * alcohol_g AS alcohol_g,
+                     hoeveelheid_g / 100 * water_g AS water_g,
+                     hoeveelheid_g / 100 * natrium_mg AS natrium_mg,
+                     hoeveelheid_g / 100 * kalium_mg AS kalium_mg,
+                     hoeveelheid_g / 100 * calcium_mg AS calcium_mg,
+                     hoeveelheid_g / 100 * magnesium_mg AS magnesium_mg,
+                     hoeveelheid_g / 100 * ijzer_mg AS ijzer_mg,
+                     hoeveelheid_g / 100 * selenium_µg AS selenium_µg,
+                     hoeveelheid_g / 100 * zink_mg AS zink_mg,
+                     hoeveelheid_g / 100 * vitamine_a_µg AS vitamine_a_µg,
+                     hoeveelheid_g / 100 * vitamine_d_µg AS vitamine_d_µg,
+                     hoeveelheid_g / 100 * vitamine_e_mg AS vitamine_e_mg,
+                     hoeveelheid_g / 100 * vitamine_b1_g AS vitamine_b1_g,
+                     hoeveelheid_g / 100 * vitamine_b2_mg AS vitamine_b2_mg,
+                     hoeveelheid_g / 100 * vitamine_b6_mg AS vitamine_b6_mg,
+                     hoeveelheid_g / 100 * foliumzuur_µg AS foliumzuur_µg,
+                     hoeveelheid_g / 100 * vitamine_b12_µg AS vitamine_b12_µg,
+                     hoeveelheid_g / 100 * nicotinezuur_mg AS nicotinezuur_mg,
+                     hoeveelheid_g / 100 * vitamine_c_mg AS vitamine_c_mg,
+                     hoeveelheid_g / 100 * jodium_µg AS jodium_µg,
+                     hoeveelheid_g / 100 * fosfor_mg AS fosfor_mg,
+                     hoeveelheid_g / 100 * suikers_g AS suikers_g,
+                     hoeveelheid_g / 100 * eiwit_plantaardig_g AS eiwit_plantaardig_g
+                FROM consumpties_USERNAME
+                JOIN producten
+                  ON consumpties_USERNAME.product_guid = producten.guid)
+  SELECT datum,
+         SUM(energie_kcal) AS energie_kcal,
+         SUM(vet_g) AS vet_g,
+         SUM(verzadigd_vet_g) AS verzadigd_vet_g,
+         SUM(koolhydraten_g) AS koolhydraten_g,
+         SUM(eiwit_g) AS eiwit_g,
+         SUM(vezels_g) AS vezels_g,
+         SUM(zout_g) AS zout_g,
+         SUM(alcohol_g) AS alcohol_g,
+         SUM(water_g) AS water_g,
+         SUM(natrium_mg) AS natrium_mg,
+         SUM(kalium_mg) AS kalium_mg,
+         SUM(calcium_mg) AS calcium_mg,
+         SUM(magnesium_mg) AS magnesium_mg,
+         SUM(ijzer_mg) AS ijzer_mg,
+         SUM(selenium_µg) AS selenium_µg,
+         SUM(zink_mg) AS zink_mg,
+         SUM(vitamine_a_µg) AS vitamine_a_µg,
+         SUM(vitamine_d_µg) AS vitamine_d_µg,
+         SUM(vitamine_e_mg) AS vitamine_e_mg,
+         SUM(vitamine_b1_g) AS vitamine_b1_g,
+         SUM(vitamine_b2_mg) AS vitamine_b2_mg,
+         SUM(vitamine_b6_mg) AS vitamine_b6_mg,
+         SUM(foliumzuur_µg) AS foliumzuur_µg,
+         SUM(vitamine_b12_µg) AS vitamine_b12_µg,
+         SUM(nicotinezuur_mg) AS nicotinezuur_mg,
+         SUM(vitamine_c_mg) AS vitamine_c_mg,
+         SUM(jodium_µg) AS jodium_µg,
+         SUM(fosfor_mg) AS fosfor_mg,
+         SUM(suikers_g) AS suikers_g,
+         SUM(eiwit_plantaardig_g) AS eiwit_plantaardig_g
+    FROM sub
+GROUP BY datum
+ORDER BY datum ASC);

+ 3 - 0
requirements.txt

@@ -0,0 +1,3 @@
+bs4
+requests
+psycopg2

+ 126 - 0
voedingscentrum.py

@@ -0,0 +1,126 @@
+from argparse import ArgumentParser
+from datetime import date
+from getpass import getuser
+import re
+from xml.etree import ElementTree
+
+from bs4 import BeautifulSoup
+import requests
+import psycopg2
+
+parser = ArgumentParser(description="Tool om data uit voedingscentrum.nl te importeren in PostgreSQL.")
+parser.add_argument('--email', type=str, required=True, help="Je emailadres voor voedingscentrum.nl")
+parser.add_argument('--password', type=str, required=True, help="Je wachtwoord voor voedingscentrum.nl")
+
+parser.add_argument('--dbhost', type=str, default='localhost', help="Je PostgreSQL hostname (standaard localhost)")
+parser.add_argument('--dbuser', type=str, default=getuser(), help=f"Je PostgreSQL username (standaard {getuser()})")
+parser.add_argument('--dbpassword', type=str, default=None, help="Je PostgreSQL password")
+parser.add_argument('--dbport', type=int, default=5432, help="Je PostgreSQL poort (standaard 5432)")
+parser.add_argument('--dbname', type=str, default='voeding', help="De naam van je PostgreSQL database (standaard voeding)")
+
+parser.add_argument('--from-date', type=str, help="Datum vanaf wanneer je gegevens wilt ophalen (standaard vandaag)")
+parser.add_argument('--to-date', type=str, help="Datum tot wanneer je gegevens wilt ophalen (standaard vandaag)")
+args = parser.parse_args()
+
+def parse_date(date_string):
+    matches = re.match(r'(\d{4})-(\d{2})-(\d{2})', date_string)
+    if matches:
+        year, month, day = map(int, matches.groups())
+        return date(year, month, day)
+
+    matches = re.match(r'(\d{1,2})-(\d{1,2})-(\d{4})', date_string)
+    if matches:
+        day, month, year = map(int, matches.groups())
+        return date(year, month, day)
+
+def snake_case(s):
+    s = re.sub(r'([a-z])([A-Z])', r'\1_\2', s)
+    return s.lower()
+
+def download_data():
+    # Laad de startpagina en haal de verborgen form-data op
+    s = requests.Session()
+    r = s.get("https://mijn.voedingscentrum.nl/nl/login/")
+    soup = BeautifulSoup(r.text, 'html.parser')
+    data = {element['name']: element.get('value', None) for element in soup.find_all('input')}
+
+    # Voeg persoonlijke gegevens toe om in te loggen
+    data['ctl00$ctl18$txtUser'] = args.email
+    data['ctl00$ctl18$txtPassword'] = args.password
+    data['__EVENTTARGET'] = 'ctl00$ctl18$lbLogin'
+    data.pop('ctl00$ctl18$cbxRemember')
+
+    # Log in
+    s.post("https://mijn.voedingscentrum.nl/nl/login/", data=data)
+
+    # Laad de overichtpagina en haal de verborgen form-data op
+    r = s.get("https://mijn.voedingscentrum.nl/nl/dashboard/eetmeter/overzicht/")
+    soup = BeautifulSoup(r.text, 'html.parser')
+    data = {element['name']: element.get('value', None) for element in soup.find_all('input')}
+    data['__EVENTTARGET'] = "ctl00$ctl20$lbXML"
+    data['ctl00$ctl20$txtDateStart'] = DATE_FROM.strftime("%d-%m-%Y")
+    data['ctl00$ctl20$txtDateEnd'] = DATE_TO.strftime("%d-%m-%Y")
+
+    # Download de XML
+    r = s.post("https://mijn.voedingscentrum.nl/nl/dashboard/eetmeter/overzicht/", data=data)
+    r.encoding = 'utf-8'
+    return ElementTree.fromstring(r.text)
+
+def extract_consumpties(consumpties):
+    for consumptie in consumpties:
+        product = consumptie.find('Product')
+        product_guid = product.find('Guid').text
+        product_naam = product.find('Naam').text
+
+        nutrienten = consumptie.find('Nutrienten')
+        product_nutrients = {snake_case(n.tag) + "_" + n.attrib['Eenheid']: float(n.attrib.get('WaardePer100Gram', 0))
+                             for n in nutrienten}
+        product_dict= {'guid': product_guid,
+                       'naam': product_naam}
+        product_dict.update(product_nutrients)
+
+        consumptie_eenheden = float(product.find('Eenheid').find('Aantal').text)
+        consumptie_grampereenheid = float(product.find('Eenheid').attrib['GramPerEenheid'])
+        consumptie_hoeveelheid = consumptie_eenheden * consumptie_grampereenheid
+        consumptie_datum = date(*(int(consumptie.find('Datum').find(part).text) for part in ['Jaar', 'Maand', 'Dag']))
+        consumptie_periode = consumptie.attrib['Periode']
+
+        consumptie_dict = {'datum': consumptie_datum,
+                           'product_guid': product_guid,
+                           'periode': consumptie_periode,
+                           'hoeveelheid': consumptie_hoeveelheid}
+
+        yield product_dict, consumptie_dict
+
+def insert_product(cursor, guid, naam, **nutrients):
+    cursor.execute("SELECT * FROM producten WHERE guid = %s", (guid,))
+    if cursor.rowcount > 0:
+        return
+    nutrients_cols = ", ".join(nutrients.keys())
+    placeholders = ", ".join(["%s"] * (len(nutrients.keys()) + 2))
+    cursor.execute(f"INSERT INTO producten (guid, naam, {nutrients_cols}) VALUES ({placeholders})",
+                   (guid, naam, *nutrients.values()))
+
+def insert_consumptie(cursor, datum, product_guid, periode, hoeveelheid):
+    cursor.execute(f"INSERT INTO consumpties_{DATABASE['user']} (datum, product_guid, periode, hoeveelheid_g) VALUES (%s, %s, %s, %s)",
+                   (datum, product_guid, periode, hoeveelheid))
+
+DATABASE = {'host': args.dbhost,
+            'user': args.dbuser,
+            'password': args.dbpassword,
+            'dbname': args.dbname,
+            'port': args.dbport}
+DATE_FROM = parse_date(args.from_date) if args.from_date else date.today()
+DATE_TO = parse_date(args.to_date) if args.to_date else date.today()
+
+conn = psycopg2.connect(**DATABASE)
+cursor = conn.cursor()
+cursor.execute(f"DELETE FROM consumpties_{args.dbuser} WHERE datum BETWEEN %s AND %s", (DATE_FROM, DATE_TO))
+
+data = download_data()
+for product, consumptie in extract_consumpties(data):
+    insert_product(cursor, **product)
+    insert_consumptie(cursor, **consumptie)
+
+conn.commit()
+conn.close()