---
name: eu-weekly-oil-bulletin-ingestion
description: "How to get EU Weekly Oil Bulletin data into a pandas DataFrame and Parquet: access, the request, the format, limits, quirks and terms."
---

# Load EU weekly oil prices into pandas and Parquet

Source entry: https://signal-lab-jet.vercel.app/datasets/eu-weekly-oil-bulletin
Provider: https://energy.ec.europa.eu/data-and-analysis/weekly-oil-bulletin_en
Checked against the provider's documentation: 2026-11-01

## Access
- No account or key; the historical XLSX workbook is a public download.
- The example parses the first worksheet with Python's standard XLSX ZIP/XML support, so it does not require an Excel engine.

## Request
```bash
curl -O 'https://energy.ec.europa.eu/document/download/906e60ca-8b6a-44e7-8589-652854d2fd3f_en?filename=Weekly_Oil_Bulletin_Prices_History_maticni_4web.xlsx'
```

## Format
XLSX workbook with weekly taxed and untaxed petroleum-price sheets plus consumption, VAT, excise-duty and other-tax sheets; the price sheets use Excel date serials and product-specific units.

## Load into pandas and Parquet
Needs Python 3 with requests, pandas and pyarrow.
```python
"""EU aggregate weekly taxed petroleum prices -> pandas -> Parquet."""
import io
import zipfile
import xml.etree.ElementTree as ET
import requests
import pandas as pd

URL = "https://energy.ec.europa.eu/document/download/906e60ca-8b6a-44e7-8589-652854d2fd3f_en?filename=Weekly_Oil_Bulletin_Prices_History_maticni_4web.xlsx"
response = requests.get(URL, timeout=120)
response.raise_for_status()
ns = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}"
with zipfile.ZipFile(io.BytesIO(response.content)) as book:
    strings_root = ET.fromstring(book.read("xl/sharedStrings.xml"))
    strings = ["".join(t.text or "" for t in item.iter(ns + "t")) for item in strings_root]
    rows = []
    for _, row in ET.iterparse(book.open("xl/worksheets/sheet1.xml"), events=("end",)):
        if row.tag != ns + "row" or int(row.attrib["r"]) < 4:
            continue
        cells = {}
        for cell in row.findall(ns + "c"):
            ref = cell.attrib["r"]
            value_node = cell.find(ns + "v")
            if value_node is None:
                continue
            value = value_node.text
            if cell.attrib.get("t") == "s":
                value = strings[int(value)]
            cells["".join(c for c in ref if c.isalpha())] = value
        try:
            date_serial = float(cells.get("A", ""))
            values = [float(cells[column]) for column in "CDEFGH"]
        except (KeyError, ValueError):
            row.clear()
            continue
        if date_serial > 10000:
            rows.append({
                "date_serial": date_serial,
                "eu_eurosuper95_per_1000l": values[0],
                "eu_diesel_per_1000l": values[1],
                "eu_heating_oil_per_1000l": values[2],
                "eu_low_sulphur_fuel_oil_per_t": values[3],
                "eu_high_sulphur_fuel_oil_per_t": values[4],
                "eu_motor_lpg_per_1000l": values[5],
            })
        row.clear()
df = pd.DataFrame(rows)
df["date"] = pd.Timestamp("1899-12-30") + pd.to_timedelta(df.pop("date_serial"), unit="D")
df = df.sort_values("date")
df.to_parquet("eu_weekly_oil_prices.parquet", index=False)
print(len(df), "weekly EU observations", df["date"].min().date(), "to",
      df["date"].max().date(), "across", len(df.columns) - 1, "price series")
```
Run by us on 2026-11-01 (python 3.13.5 | pandas 3.1.0 | pyarrow 26.0.0 | requests 2.34.2). Result: 1090 weekly EU observations 2005-01-03 to 2026-10-26 across 6 price series

## Limits
- Rate limit: Not stated.
- The history file is a multi-sheet XLSX workbook rather than a normalized table.
- Country and product availability differ across the workbook.

## Quirks
- Dates are stored as Excel serial numbers in the checked history workbook.
- The first rows form a multi-level header with product names and units; the example assigns explicit names only to the six EU aggregate price columns.
- Taxed and untaxed prices are separate sheets, and some fuel-oil or LPG cells are blank.

## Terms and attribution
- EU-owned website content is CC BY 4.0 unless an individual notice states otherwise.
- Credit the source and indicate changes; third-party rights and protected marks are excluded.
- Terms page: https://commission.europa.eu/legal-notice_en

## Sources
- https://energy.ec.europa.eu/data-and-analysis/weekly-oil-bulletin_en: "To improve the transparency of oil prices and to strengthen the internal market, the European Commission's Oil Bulletin presents weekly consumer prices for petroleum products in EU countries."
- https://energy.ec.europa.eu/data-and-analysis/weekly-oil-bulletin_en: "Information and maps showing weekly updates on prices of petroleum products in all EU countries"
- https://commission.europa.eu/legal-notice_en: "This means that reuse is allowed, provided appropriate credit is given and changes are indicated."

## Help improve this skill
Missing a source, or see something to improve? Call the MCP tool suggest_source or feedback (or POST /api/suggest or /api/feedback). Reviewed ideas: https://signal-lab-jet.vercel.app/agent-suggestions

Information for educational purposes only. Free tiers, limits and licenses belong to the providers and may change; check the provider's own terms before using the data.
