Data publikacji: 25.09.2026

Laptop z nałożoną grafiką strzałek z kropek skierowanych w górę

Konwersja obrazków PNG do WebP w Pythonie – praktyczny przykład z regex

Spis treści

Z artykułu dowiesz się, jak za pomocą wyrażeń regularnych i biblioteki Pillow zamienić obrazki PNG w pliku Markdown na WebP.

Konwersja obrazków PNG do WebP w pliku Markdown

W przykładzie mamy plik Markdown z treścią zadań szkoleniowych, w którym obrazki są osadzone bezpośrednio w tekście w formacie PNG zakodowanym w Base64. Chcemy zmniejszyć rozmiar pliku, zamieniając obrazki na nowocześniejszy format WebP.

Do wyszukania i podmiany obrazków użyjemy funkcji re.search() oraz re.sub() z modułu re w Pythonie, a do konwersji obrazków biblioteki Pillow.

Plan działania:

  • przeszukać kolejne linie dokumentu
  • odnaleźć linie z osadzonymi obrazkami, czyli zawierające znacznik <img z danymi PNG
  • dla każdego obrazka:
    • odczytać dane obrazka zakodowane w Base64
    • przekonwertować je do formatu WebP
    • ponownie zakodować wynik w Base64 i zapisać go w tym samym miejscu dokumentu.

Do konwersji będą potrzebne wyrażenia regularne. Ich szczegółowy opis znajdziesz w artykule Wyrażenia regularne w Pythonie.

Krok 1 - wczytaj przykładowy dokument do programu

Przykładowy dokument:

### PB1.213
Wykonaj poniższe polecenia używając tabeli **raporty_sprzedazowe**:
- dodaj nową stronę
- wstaw wykres liniowy, na którym zaprezentujesz sprzedaż produktów per rok
<img style="width: 10px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAFUlEQVR4nGNU2ujDgBsw4ZFjGLnSAGaDATMBem2QAAAAAElFTkSuQmCC"/>
- wstaw filtr, aby można było filtrować wizualizację po imieniu sprzedawcy
- zmodyfikuj wykres zgodnie z wzorem na poniższym obrazku

Zapisz przykładowy dokument w pliku kurs_PB1_zadania.md w tym samym folderze, w którym zapisany jest skrypt Pythona. Następnie wczytaj plik i rozbij go na linie:

fn = "kurs_PB1_zadania.md"
md = open(fn, "r", encoding="utf-8").read().splitlines()

W przykładzie:

  • metoda read() wczytuje cały plik jako jeden napis
  • splitlines() dzieli plik na osobne linie
  • parametr encoding="utf-8" zapewnia poprawne odczytanie polskich znaków.
  • zmienna md zawiera listę linii.

Jeśli pojawi się błąd FileNotFoundError upewnij się, że plik "kurs_PB1_zadania.md" jest w tym samym katalogu, w którym znajduje się aplikacja Python (plik *.py)

Krok 2 - znajdź linie z obrazkiem re.search()

Szukamy linii, które zawierają na początku <img i na końcu />. Dla każdej linii używamy wzorca <img.*/> i drukujemy znaleziony fragment.

Uwaga! W kroku pierwszym plik wczytany został do zmiennej md. IDLE przy każdym uruchomieniu skryptu (F5) restartuje powłokę. Restart kasuje wszystkie wcześniej utworzone zmienne.

Dlatego wczytywanie pliku musi zostać w tym samym skrypcie, w którym dalej pracujesz i używasz zmiennej md.

Ewentualnie możesz uruchamiać kolejne polecenia w powłoce Shell. Jeśli dopiero zaczynasz pracę z Pythonem, zajrzyj do artykułu Jak rozpocząć pracę z IDLE w Pythonie.

import re
for line in md:
z = re.search(r'<img.*/>', line)
if z:
s = z.group()
print(s)

Wynik:

<img style="width: 10px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAFUlEQVR4nGNU2ujDgBsw4ZFjGLnSAGaDATMBem2QAAAAAElFTkSuQmCC"/>

Jak działa wzorzec <img.*/>?

  • <img – dosłowny tekst
  • .* – dowolne znaki w dowolnej liczbie (atrybuty, dane obrazka). Może też być pusty tekst.
  • /> – dosłowny tekst

Linie bez obrazka nie pasują do wzorca, więc re.search() zwraca dla nich None i nic nie jest drukowane. Tekst przed <img (spacje, myślnik listy) nie należy do dopasowania – re.search() szuka w dowolnym miejscu linii.

Krok 3 - wyciągnij dane Base64 grupą

Nie potrzebujemy całego znacznika, tylko samych danych obrazka. Użyjemy wzorca z grupą:

r'<img.*image/png;base64,(.*?)"/>'

Wzorzec czytamy od lewej:

  • <img – początek znacznika
  • .* – dowolne atrybuty (np. style="width: 10px;" src="data:)
  • image/png;base64, – dosłowny tekst poprzedzający dane PNG
  • (.*?) – grupa, czyli fragment, który chcemy odczytać. Leniwe *? sprawia, że grupa kończy się na pierwszym "/>
  • "/> – cudzysłów zamykający atrybut src i koniec znacznika.
for line in md:
z = re.search(r'<img.*image/png;base64,(.*?)"/>', line)
if z:
base64_png = z.group(1)
print(base64_png)

Metoda group(1) zwraca zawartość pierwszej grupy (grupy numerujemy od 1). Do zmiennej base64_png trafia zakodowany w Base64 obrazek – bez atrybutów i znaczników.

Teraz dekodujemy obrazek z Base64, konwertujemy go z PNG do WebP biblioteką Pillow i ponownie kodujemy w Base64.

from PIL import ImageGrab # pip install pillow
import io
import base64
img = ImageGrab.Image.open(io.BytesIO(base64.b64decode(base64_png)))
imgByteArr = io.BytesIO()
img.save(imgByteArr, format='WebP')
imgByteArr = imgByteArr.getvalue()
base64_webp = base64.b64encode(imgByteArr).decode('UTF-8')

W przykładzie:

  • base64.b64decode(base64_png) – zamienia tekst Base64 z powrotem na dane binarne obrazka

  • io.BytesIO(...) – opakowuje te dane w obiekt, który zachowuje się jak plik w pamięci. Dzięki temu nie musimy zapisywać obrazka na dysku.

  • ImageGrab.Image.open() – otwiera obrazek z danych binarnych. Moduł ImageGrab udostępnia klasę Image z biblioteki Pillow, więc możemy z niej skorzystać bez osobnego importu.

  • imgByteArr = io.BytesIO() – tworzy nowy, pusty bufor w pamięci, do którego zapiszemy przekonwertowany obrazek.

  • img.save(imgByteArr, format='WebP') – zapisuje obrazek w formacie WebP do tego bufora.

  • imgByteArr.getvalue() – pobiera z bufora gotowe bajty pliku WebP i przypisuje je do zmiennej imgByteArr.

  • base64.b64encode(imgByteArr).decode('UTF-8') – koduje nowy obrazek w Base64 i zamienia wynik na zwykły napis.

Uwaga: Równoważnie można użyć importu from PIL import Image i wywołać bezpośrednio Image.open(). Moduł ImageGrab służy głównie do wykonywania zrzutów ekranu.

Do zainstalowania biblioteki Pillow, jeśli jeszcze jej nie masz, możesz użyć menedżera pakietów uv. W artykule Python uv znajdziesz instrukcje, w jaki sposób go używać.

Krok 4 - podmień obrazek re.sub()

Kodowanie Base64 zapisuje dane binarne obrazka w postaci tekstu, który można umieścić w pliku Markdown. Dane nowego obrazka zastąpią dane starego. Zamienimy też informację o typie z image/png na image/webp. Użyjemy funkcji re.sub():

img = ImageGrab.Image.open(io.BytesIO(base64.b64decode(base64_png)))
imgByteArr = io.BytesIO()
img.save(imgByteArr, format='WebP')
imgByteArr = imgByteArr.getvalue()
base64_webp = base64.b64encode(imgByteArr).decode('UTF-8')
line2 = re.sub( r'<img(.*?)image/png;base64,.*?"/>',
rf'<img\1image/webp;base64,{base64_webp}"/>',
line )
print(line2)

Wynik (dane Base64 skrócone):

<img style="width: 10px;" src="data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACwAQCdAS..."/>

W przykładzie:

  • we wzorcu grupa (.*?) dopasowuje wszystko pomiędzy <img a image/png – czyli atrybuty style="width: 10px;" src="data:. Chcemy je zachować bez zmian

  • w zamienniku \1 wstawia zawartość tej grupy z powrotem w to samo miejsce

  • image/png zamieniamy na image/webp, a stare dane – na nowe

  • przedrostek rf łączy dwa rodzaje napisów: r (surowy – \1 nie jest interpretowane przez Pythona) oraz f (f-string – {base64_webp} wstawia wartość zmiennej).

Dane Base64 zawierają tylko litery, cyfry oraz znaki +, / i =, więc można je bezpiecznie wstawić do zamiennika. Gdyby wstawiany tekst mógł zawierać backslash, lepiej użyć funkcji jako zamiennika (zobacz sekcję o re.sub() w artykule Wyrażenia regularne w Pythonie).

Uwaga: Dla przejrzystości pokazujemy ten fragment osobno. W pełnym programie znajduje się on wewnątrz pętli for line in md, w bloku if z:. Dzięki temu konwersja i zamiana są wykonywane dla każdej linii, w której znaleziono obrazek, a zmienna line zawsze wskazuje właściwą linię.

Kompletny skrypt

Na koniec łączymy wszystkie kroki w jeden skrypt. Program przetwarza plik linia po linii, zamienia obrazki PNG na WebP i zapisuje wynik w miejscu oryginalnego pliku.

from PIL import ImageGrab # pip install pillow
import io
import base64
import re
fn = r"kurs_PB1_zadania.md"
md = open(fn, "r", encoding="utf-8").read().splitlines()
wynik = []
for line in md:
z = re.search(r'<img.*image/png;base64,(.*?)"/>', line)
if z:
base64_png = z.group(1)
img = ImageGrab.Image.open(io.BytesIO(base64.b64decode(base64_png)))
imgByteArr = io.BytesIO()
img.save(imgByteArr, format='WebP')
imgByteArr = imgByteArr.getvalue()
base64_webp = base64.b64encode(imgByteArr).decode('UTF-8')
line2 = re.sub( r'<img(.*?)image/png;base64,.*?"/>',
rf'<img\1image/webp;base64,{base64_webp}"/>',
line )
line = line2
wynik.append(line)
with open(fn, "w", encoding="utf-8") as f:
f.write("\n".join(wynik))

Uwaga! Skrypt nadpisuje oryginalny plik Markdown, dlatego przed jego uruchomieniem warto zrobić kopię zapasową.

W przykładzie używamy pętli. Informacje o pętlach znajdziesz w artykule Pętle w Pythonie.

W kilkunastu linijkach kodu połączyliśmy trzy umiejętności: wyszukiwanie wzorców wyrażeniami regularnymi, przetwarzanie obrazów biblioteką Pillow oraz automatyczną modyfikację plików tekstowych. Ten sam schemat możesz zastosować do innych zadań, np. masowej zmiany linków, porządkowania dokumentacji czy przetwarzania raportów.

Szukasz okazji, aby w praktyce zmierzyć się z zaawansowanymi zagadnieniami Pythona? Sprawdź nasz kurs Python poziom zaawansowany.

Rafał Lelusz
Rafał Lelusz
Programista Python, inżynier ML
Udostępnij wpis:udostępnij Facebookudostępnij Linkedinudostępnij e-mail