From 97cab71fe1319ebf65a475ceded6dcace4292aca Mon Sep 17 00:00:00 2001 From: Alexander Kowarik Date: Fri, 17 Apr 2026 10:25:18 +0200 Subject: [PATCH 1/2] SSIG2 presentation Austria --- meetings/SSIG2/2_AT_SSIG2_WEB_FOSS_AT.html | 3372 ++++++++++++++++++++ 1 file changed, 3372 insertions(+) create mode 100644 meetings/SSIG2/2_AT_SSIG2_WEB_FOSS_AT.html diff --git a/meetings/SSIG2/2_AT_SSIG2_WEB_FOSS_AT.html b/meetings/SSIG2/2_AT_SSIG2_WEB_FOSS_AT.html new file mode 100644 index 0000000..20fa203 --- /dev/null +++ b/meetings/SSIG2/2_AT_SSIG2_WEB_FOSS_AT.html @@ -0,0 +1,3372 @@ + + + + + + + + + + + + + + Statistical scraping in Austria: WEB-FOSS-AT + + + + + + + + + + + + + + + +
+
+ + +
+ +
+

Overview

+
    +
  • Progress and current status of webscraping software in R (taRantula)
  • +
+





+
    +
  • Progress and current status of use cases
  • +
+
+
+ +
+

Webscraping Software: R Package taRantula

+
    +
  • Started from:

    +
      +
    • Exisiting bundle of R Scripts
    • +
    • Using Selenium
    • +
    • Highly entangled with STAT infrastructure
    • +
  • +
+
+
    +
  • Current Status: R package taRantula

    +
      +
    • Stand alone R Package available on github: statistikat/taRantula
    • +
    • Using Selenium-Grid or httr::GET() within dockerized environment
    • +
  • +
+
+
+
+

Prerequisites

+
+
+
+
+

Search URLs

+


+
    +
  • Currently only Google Custom Search supported
  • +
  • Google Custom Search API Keys
  • +
+
+
+
+

Scrape URLs

+


+
    +
  • running Selenium Grid using docker
  • +
  • use existing docker image: image
  • +
  • example of docker compose file: here
  • +
+
+
+
+
+

URL searching with R Package taRantula

+
+
# Install from GitHub
+remotes::install_github("statistikat/taRantula")
+library(taRantula)
+
+# set env variables for 
+Sys.setenv(
+  SCRAPING_APIKEY_GOOGLE = "My_ApiKey",
+  SCRAPING_ENGINE_GOOGLE = "My_Engine"
+)
+
+# config object to controll url search
+cfg <- paramsGoogleSearch(
+  path = "~/path/to/my/project",
+  scrape_attributes = c("title", "link", "displayLink", "snippet")
+)
+
+# initialise some dummy data
+dat <- data.table(
+  ID = c(1, 2, 3),
+  EnterpriseName = c("Name1", "Name2", "Name3"),
+  EnterpriseAddress = c("Address1", "Address2", "Address3")
+)
+
+# build queries
+dat[, Query1 := buildQuery(.SD), .SDcols = c(
+  "EnterpriseName",
+  "EnterpriseAddress"
+)]
+dat[, Query2 := buildQuery(.SD), .SDcols = c("EnterpriseAddress")]
+
+# update keys in config
+cfg$set(key = "query_col", c("Query1", "Query2"))
+cfg$set(key = "file", c("File1.csv", "File2.csv")) # <- files to save results in
+
+# run google custom search
+runGoogleSearch(
+  cfg = cfg,
+  data = data
+)
+
+
+
+

URL scraping with R Package taRantula

+
+
library(taRantula)
+
+# Initialize scraping parameters
+cfg <- paramsScraper(base_dir = "~/")
+cfg$set("selenium$host", "localhost")
+cfg$set("selenium$workers", 3)
+cfg$set("urls", c("https://www.statistik.at", "https://www.cbs.nl/"))
+
+print(cfg)
+
+
$project
+[1] "my-project"
+
+$base_dir
+[1] "~/"
+
+$urls
+[1] "https://www.statistik.at" "https://www.cbs.nl/"     
+
+$robots
+$robots$check
+[1] TRUE
+
+$robots$snapshot_every
+[1] 10
+
+$robots$workers
+[1] 1
+
+$robots$robots_user_agent
+[1] "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
+
+
+$httr
+$httr$user_agent
+[1] "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
+
+
+$selenium
+$selenium$use_selenium
+[1] TRUE
+
+$selenium$host
+[1] "localhost"
+
+$selenium$port
+[1] 4444
+
+$selenium$verbose
+[1] FALSE
+
+$selenium$browser
+[1] "chrome"
+
+$selenium$user_agent
+[1] "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
+
+$selenium$ecaps
+$selenium$ecaps$args
+ [1] "--headless"                                                                                                                       
+ [2] "--enable-automation"                                                                                                              
+ [3] "--disable-gpu"                                                                                                                    
+ [4] "--no-sandbox"                                                                                                                     
+ [5] "--start-maximized"                                                                                                                
+ [6] "--disable-infobars"                                                                                                               
+ [7] "--disk-cache-size=400000000"                                                                                                      
+ [8] "--disable-browser-side-navigation"                                                                                                
+ [9] "--disable-blink-features"                                                                                                         
+[10] "--window-size=1080,1920"                                                                                                          
+[11] "--disable-popup-blocking"                                                                                                         
+[12] "--disable-dev-shm-usage"                                                                                                          
+[13] "--lang=de"                                                                                                                        
+[14] "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
+
+$selenium$ecaps$prefs
+$selenium$ecaps$prefs$PageLoadStrategy
+[1] "eager"
+
+$selenium$ecaps$prefs$profile.default_content_settings.popups
+[1] 0
+
+
+$selenium$ecaps$excludeSwitches
+[1] "disable-popup-blocking"
+
+
+$selenium$snapshot_every
+[1] 10
+
+$selenium$workers
+[1] 3
+
+
+
+
+

URL scraping with R Package taRantula

+
+
# init scraper and start scraping
+s <- UrlScraper$new(cfg)
+s$scrape()
+
+# Access results as a data.table
+results_dt <- s$results()
+
+# Make a custom query
+s$query("select count(*) as nr_failed from results where status=false")
+
+# Access all discovered links
+links_dt <- s$links()
+
+# Inspect logs for errors or redirects
+logs_dt <- s$logs()
+
+
+
+

URL scraping with R Package taRantula

+
    +
  • Focus-Scrape by updating scraper
  • +
+
+
# Access all discovered links
+links_dt <- s$links()
+s$update_urls(urls = links_dt$href)
+
+# continue scraping
+s$scrape()
+
+# Pattern for Austrian VAT numbers
+uid_pattern <- "ATU[0-9]{8}"
+
+# Extract only from pages where the URL or the link label matches 'imprint'
+vat_results <- s$regex_extract(
+  pattern = uid_pattern,
+  filter_links = "imprint",
+  ignore_cases = TRUE
+)
+
+# terminate and clean up
+s$close()
+
+
+
+

URL scraping with R Package taRantula

+
    +
  • Scraping “method”:

    +
      +
    • Parallelisation using future-framework
    • +
    • Selenium (default)
    • +
    • httr::GET() static sites or APIs where JS rendering is not required
    • +
  • +
  • Results: currently stored in DuckDB

    +
      +
    • Access it via helper methods or raw SQL
    • +
    • Snapshotting Mechanism
    • +
  • +
+
+
+

Development goals

+
    +
  • Improve stability of scraping process (~ Selenium)
  • +
+


+ +


+
    +
  • Extend methods and features
  • +
+


+
    +
  • Release on CRAN

    +
      +
    • Currently on github: https://github.com/statistikat/taRantula/
    • +
  • +
+
+ + +
+ +
+

Scraping wine prices

+
    +
  • Aim: collect wine prices from websites of Austrian winemakers

  • +
  • Why: used as proxy for the agricultural producer price index

  • +
+



+
+
    +
  • Rough outline of actions:

    +
      +
    1. Start with units from Statistical Business Register who run a vineyard
    2. +
    3. Search their URLs and look for Webshop
    4. +
    5. Scrape/Extract price list of wines
    6. +
    7. Estimate average price for differente wines/regions/types
    8. +
  • +
+
+
+
+

Scraping wine prices

+
    +
  • Starting with fields from SBR:

    +
      +
    • Name, Address, Area of vinyard in m^2
    • +
  • +
+
+



+
    +
  • Identified URLs
  • +
+
+
+ + + + + + + + + + + + + + + + + +
# Enterprise# Websites foundShare (%)weighted Share (%)
9586223023.2644.06
+
+
+
+
+
+

Scraping wine prices

+
    +
  • Attributes to collect: Price, volume, Red/White, Quality: example

  • +
  • Focus-Scrape on subpages with potential online shop/ wine lists / pdfs of wines

  • +
+
+
    +
  • Collecting wine prices with LLMs

    +
      +
    • feed raw HTML
    • +
    • prompt contains large list of rules
    • +
    • currently SSPCloud for testing (gpt-oss:120b)
    • +
  • +
+
+
+expand for prompt +
"Du extrahierst strukturierte Daten aus Weinlisten (HTML oder PDF).
+
+--------------------------------------------------
+GLOBAL RULES (STRICT)
+--------------------------------------------------
+- Antworte ausschließlich mit gültigem JSON.
+- Keine Erklärungen.
+- Kein reasoning.
+- Kein zusätzlicher Text.
+- Keine Kommentare
+- Keine Duplikate
+- Keine erfundenen Werte.
+
+--------------------------------------------------
+CHUNK HANDLING
+--------------------------------------------------
+- Wenn der Text zu lang ist, bearbeite ihn in mehreren Teilen.
+- Jedes Teil muss vollständig analysiert werden.
+- Ein Produkt muss vollständig sein. Wenn ein Produkt am Ende abgeschnitten wird, überspringe ihn in diesem Teil.
+- Füge die Ergebnisse aller Teile korrekt als ein JSON-Array zusammen.
+
+--------------------------------------------------
+STEP 1 – SHOP DETECTION
+--------------------------------------------------
+- Prüfe den Text für HTML/Webshop-Inhalte:
+  Wenn keine klassischen Weinprodukte verkauft werden, antworte exakt mit:
+  {\"weinshop\": false}
+- Prüfe den Text für PDF-Inhalte:
+  Wenn keine klassischen Weinprodukte verkauft werden, antworte exakt mit:
+  {\"weinshop_pdf\": false}
+
+--------------------------------------------------
+STEP 2 – PRODUKTDEFINITION
+--------------------------------------------------
+Ein Produkt ist NUR gültig wenn:
+
+- genau EIN Wein beschrieben wird
+- kein Set / Bundle / Paket
+
+Definition Wein:
+Still, vergoren, ohne Kohlensäure
+
+ERLAUBT:
+- Weißwein
+- Rotwein
+- Roséwein
+- Süßwein
+- Cuvée
+
+VERBOTEN:
+- Sekt / Frizzante / Champagner
+- Spirituosen
+- Bier
+- Lebensmittel
+- Zubehör
+- Events
+
+--------------------------------------------------
+OUTPUT STRUCTURE
+--------------------------------------------------
+Ausgabeformat:
+[
+  {
+    \"weinshop\": true | false,
+    \"weinshop_pdf\": true | false,
+    \"Weinbezeichnung\": string | null,
+    \"Rebsorte\": string | null,
+    \"Farbe\": \"Weiß\" | \"Rot\" | \"Rosé\" | null,
+    \"Jahrgang\": number | null,
+    \"Qualitaet_raw\": string | null,
+    \"Qualitaet_gruppe\": \"Qualitaetswein u Praedikatswein\" | \"Landwein\" | \"Anderer Wein\" | \"Unknown\",
+    \"Preis\": number | null,
+    \"Volumen\": number | null
+  }
+]
+
+--------------------------------------------------
+CORE EXTRACTION RULES
+--------------------------------------------------
+- Keine erfundenen Werte.
+- Fehlende Werte = null.
+- Keine Duplikate.
+- Keine zusätzlichen Felder.
+
+--------------------------------------------------
+FARBE (KONTEXTLOGIK)
+--------------------------------------------------
+Abschnittstitel setzen Farbe für alle folgenden Produkte:
+- Weißweine → Weiß
+- Rotweine → Rot
+- Rosé → Rosé
+
+--------------------------------------------------
+QUALITÄTSLOGIK (KRITISCH – STRICT HIERARCHY)
+--------------------------------------------------
+WICHTIG:
+- Prüfung erfolgt von oben nach unten
+- Sobald eine Regel zutrifft → STOP
+- Niemals mehrere Kategorien prüfen
+
+-----
+1) QUALITAETSWEIN u PRAEDIKATSWEIN (höchste Stufe)
+-----
+Trigger (stark):
+- DAC
+- Districtus Austriae Controllatus
+- Qualitätswein
+- Prädikatswein
+- Kabinett
+- Spätlese
+- Auslese
+- Beerenauslese
+- Trockenbeerenauslese
+- Eiswein
+- Strohwein
+- Schilfwein
+- Ausbruch
+- Ruster Ausbruch
+- Erste Lage
+- Große Lage
+ODER (nur wenn eindeutig produktbezogen):
+- Ried (nur als Trigger wenn unmittelbar im Produktnamen vorkommt.)
+- Wachau, Kamptal, Kremstal, Traisental, Wagram,
+  Carnuntum, Thermenregion, Weinviertel,
+  Südsteiermark, Weststeiermark, Vulkanland Steiermark
+→ OUTPUT:
+Qualitaet_gruppe = \"Qualitaetswein u Praedikatswein\"
+Qualitaet_raw = exakt gefundener Begriff
+→ STOP
+
+-----
+2) LANDWEIN
+-----
+Nur wenn oben NICHT erfüllt:
+Trigger:
+- Landwein
+- Weinland
+- Bergland
+- Steirerland
+→ OUTPUT:
+Qualitaet_gruppe = \"Landwein\"
+Qualitaet_raw = gefundener Begriff
+→ STOP
+
+-----
+3) ANDERER WEIN
+-----
+Nur wenn oben NICHT erfüllt:
+Definition:
+Ein Weinprodukt ist erkannt,
+aber KEINE Qualitätsstufe wurde gefunden
+Typische Fälle:
+- generische Bezeichnung \"Wein\"
+- keine DAC / keine Landwein-Angabe
+→ OUTPUT:
+Qualitaet_gruppe = \"Anderer Wein\"
+Qualitaet_raw:
+- \"Wein\" wenn explizit vorhanden
+- sonst null
+→ STOP
+
+-----
+4) UNKNOWN
+-----
+Nur wenn:
+- KEIN Weinprodukt eindeutig erkannt werden kann
+ODER
+- Informationen unzureichend sind
+→ OUTPUT:
+Qualitaet_gruppe = \"Unknown\"
+Qualitaet_raw = null
+
+--------------------------------------------------
+HARTE VERBOTE (FEHLERQUELLEN)
+--------------------------------------------------
+- \"Wein aus Österreich\" = KEIN Qualitätsindikator
+- Bundesländer = KEIN Qualitätsindikator
+- Bio / Reserve / Selection = KEINE Qualitätsstufe
+- Jahrgang = KEINE Qualitätsstufe
+- Cuvée = KEINE Qualitätsstufe
+
+--------------------------------------------------
+KRITISCHE VALIDIERUNG
+--------------------------------------------------
+- Wenn Qualitaet_gruppe ≠ \"Unknown\" → Qualitaet_raw darf NICHT null sein
+  (Ausnahme: ANDERER WEIN ohne explizites Label)
+- Genau EINE Kategorie pro Produkt
+- Keine widersprüchlichen Zuordnungen
+
+--------------------------------------------------
+WEITERE REGELN
+--------------------------------------------------
+- Bestimme die Qualitaetskategorie ausschließlich anhand expliziter Textindikatoren. Gib exakt eine der folgenden Kategorien zurück: Qualitaetswein u Praedikatswein, Landwein, anderer Wein, Unknown.
+- Prüfe Qualitaetsindikatoren (z.B. DAC, Spätlese, Ried, Landwein, ...) **innerhalb des Textes, der zu diesem Produkt gehört**.
+- Ignoriere Vorkommen außerhalb des Produkts (z.B. andere Weinbeschreibungen).
+- Wenn eine Abschnittsüberschrift wie „Weißweine“, „Rotweine“, „Roseweine“, etc. vorkommt, gilt diese Farbe für alle folgenden Produkte, bis eine neue Abschnittsüberschrift erscheint. Diese Kontext-Farbe muss auch dann gesetzt werden, wenn die Farbe nicht explizit im einzelnen Produkttext steht.
+- Wenn ein Begriff zur Bestimmung der Qualitaetskategorie verwendet wird (z.B. DAC, Qualitätswein, Spätlese, Ried, Kremstal, Landwein, etc.), MUSS genau dieser im Text vorkommende Begriff unverändert in Qualitaet_raw übernommen werden.
+- Qualitaet_raw darf NICHT null sein, wenn eine Qualitaetskategorie (außer Unknown) vergeben wird.
+- Bio, Natural Wine, Reserve, Selection, Tradition sind KEINE Qualitätsangaben.
+- Jahrgang ist KEINE Qualitätsangabe.
+- Cuvée ist KEINE Qualitätsangabe.
+- Extrahiere das Volumen nur, wenn im Text ausdrücklich eine Flaschengröße steht, z.B. 0,75 L, 1 L. Ignoriere Preis-pro-Liter-Angaben. Wenn kein Volumen explizit angegeben ist, setze Volumen = null. Interpretiere keine Preisangaben pro Liter oder andere implizite Hinweise als Volumen. Erfinde niemals Volumina.
+
+--------------------------------------------------
+ONE SHOT
+--------------------------------------------------
+**Beispieltext auf Webseite:** 
+Blaufränkisch Classic
+€ 10,49
+Enthält 13% Mehrwertsteuer
+(€ 13,99 / 1 L)
+
+**Erwartetes JSON:**  
+[
+  {
+    \"weinshop\": true,
+    \"weinshop_pdf\": false,
+    \"Weinbezeichnung\": \"Blaufränkisch Classic\",
+    \"Rebsorte\": \"Blaufränkisch\",
+    \"Farbe\": \"Rot\",
+    \"Jahrgang\": 2023,
+    \"Qualitaet_raw\": \"Qualitätswein\",
+    \"Qualitaet_gruppe\": \"Qualitaetswein u Praedikatswein\",
+    \"Preis\": 10.49,
+    \"Volumen\": null
+  }
+]
+
+
+
+
+
+
+"
+
+
+
+
+
    +
  • Using LLMs to collect and process webcontent?
  • +
+
+
+
+

Scraping wine prices - Preliminary Results

+
+
+ + + + + + + + + + + + + +
# Enterprise# Websites
568556
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VolumeNARedWhite
NA18.4112.5712.90
0.75L82.1917.9215.43
1L4.755.736.12
Other167.2147.5052.60
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Qualitaet_gruppeNARedWhite
Anderer Wein10.808.9911.03
Landwein4.343.994.36
Qualitaetswein u Praedikatswein18.2217.6720.08
Unknown49.0815.9213.22
+
+
+
+
+
+
+ +
+

NACE Classification

+
    +
  • NACE classification using text from enterprise website was already thoroughly investigated during WIN (WP3/UC5)
  • +
+
+


+
    +
  • Massive work load on re-classification with the NACE revision

  • +
  • Re-classification of specific NACE-Group

  • +
+


+
+
+
+
+

NACE2008

+


G 47 ~ retail trade and specifically online shops
overall contains 40 000 - 50 000 enterprises

+
+

NACE2025
G 47.11 to G 47.83
Retail Sale for food, textiles, IT equipment , …
About 37 5-digit codes

+
+
+
+
+

NACE Classification

+



+
    +
  • Rough outline of actions:

    +
      +
    1. Start with units from Statistical Business Register
    2. +
    3. Search their URLs and look for Webshop
    4. +
    5. Scrape products on webshops
    6. +
    7. Classify products on webshops - possibly with the help of COICOP?
    8. +
    9. Derive a rule or model to classify new NACE 2025
    10. +
  • +
+
+
+

NACE Classification

+
    +
  • Started with set of 3442 enterprises

  • +
  • After applying URL search identified websites for (only) 1217 enterprises

  • +
+
+

Next Steps:

+
    +
  • improve URL finding (~)

  • +
  • Focusscrape found websites and feed it into LLM for classification

  • +
+
+
+
+

+





+

Thanks for listening!

Questions?

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 91a249eeab943c20a028c243b9c5200f3ebabc53 Mon Sep 17 00:00:00 2001 From: Alexander Kowarik Date: Fri, 17 Apr 2026 10:31:39 +0200 Subject: [PATCH 2/2] save-the-date for SSIG3, Porto --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9f99047..3955716 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ Logo of SSIG +***Save-the-date: SSIG3 Meeting 16-17 September 2026, Porto, The Social Hub*** + ***SSIG2 Meeting 15-16 April 2026, The Hague, Statistics Netherlands*** [Full Agenda](meetings/SSIG2/0_SSIG2_agenda.md)