Merhaba,

Bu yazımda, One Piece manga bölümlerini otomatik olarak indiren ufak bir Python projesi örneğini anlatmak istedim. Kimisi için öğretici bir içerik olabileceğini düşündüm.

İyi okumalar. 🏴‍☠️
One Piece logosu

Manga sitesi​

One Piece mangasını okuyabileceğimiz bir siteyi kullanacağız bu projede: Read One Piece Manga Online.

Siteye girdiğiniz gibi bölümler "One Piece Chapter xx" diye karşınıza çıkacak. Herhangi birine girip sayfanın en üstündeki Available Chapters'tan istediğiniz bölümü seçebiliyorsunuz. Örnek olarak şu bölümü seçebiliriz: One Piece Chapter 183.

Bölüm sayfası bağlantı formatı​

Yukarıda bağlantısını paylaştığım 183. bölümün ilk sayfasının bağlantısını inceleyelim. Bir sayfanın bağlantısına erişmek için sayfa resmine sağ tıklayıp ister yeni sekmede açabilir, ister "Resim adresini kopyala" seçeneğiyle bağlantıyı kopyalayabilirsiniz. Ben Chrome'da gösterdim, diğer tarayıcılarda da benzer seçenekler vardır tahminimce:

Bölüm 183 ilk sayfa bağlantısı elde etme

İlk sayfanın bağlantısı şu şekilde: https://cdn.readonepiece.com/file/mangap/2/10183000/1.jpeg.
Mesela 4. sayfanın bağlantısı da şu: https://cdn.readonepiece.com/file/mangap/2/10183000/4.jpeg.

Göze çarpanlar:
  • https://cdn.readonepiece.com/file/mangap/2/10183000/ kısmı iki bağlantı için de aynı. Sadece son kısımlar farklı: 1.jpeg ve 4.jpeg. Buradaki numaralar, sayfa numarasını temsil ediyor gibi.
  • /2/ biraz anlamsız duruyor. Hiçbir bölüm/sayfa için değişmediğini, başka bölümleri ve sayfaları inceleyerek görebilirsiniz.
  • 10183000, bölüm numarasını temsil ediyor gibi. Gerçekten de öyle. Farklı bir bölüme giderek bunu doğrulayabilirsiniz.
Tahmin edebileceğiniz üzere bu değişken kısımlar (bölüm ve sayfa numaraları) barındıran bağlantı formatını, kodumuzda taslak olarak kullanıp ilgili kısımlara bölüm ve sayfa numaralarını yerleştirerek asıl bağlantıları elde edeceğiz.

Python Kodu​

Sayfa bağlantı formatını kullanarak sayfa bağlantısı elde etme​

Elimizde bir taslak format var ve bunun içine bölüm ve sayfa numaralarını yerleştirmek istiyoruz. Bunun için f-string kullanabiliriz:
[CODE lang="python" title="Sayfa bağlantısı elde etme fonksiyonu"]def get_page_url(chapter: int, page: int) -> str:
return f"https://cdn.readonepiece.com/file/mangap/2/1{str(chapter).rjust(4, '0')}000/{page}.jpeg"[/CODE]
Tabii burada üstünde durulması gereken önemli bir detay var: str(chapter).rjust(4, "0"). Öncesinde bilerek es geçtim ama bölüm numarası kısmında (10183000) bölüm numarasının hep 4 haneli olmasına dikkat edin. 183. bölümün numarası 0183 iken 1. bölümün numarası 0001 şeklinde alınıyor. İşte bunun için bize kolaylık sağlayan bir fonksiyon olan rjust fonksiyonunu kullanıyoruz. rjust'ın ilk parametresi istenilen karakter sayısıyken ikinci parametresiyse istenilen karakter sayısına ulaşana kadar sola eklenecek karakter, yani baştaki string'imiz sağa yatık hale getiriliyor. Detaylara buradan ulaşabilirsiniz. Biz bölüm numarasını 4 haneye kadar 0'larla uzatmak istediğimiz için gerektiği şekilde kullandık.

Sayfa bağlantısına GET isteği atıp sayfa verisini indirme​

Bu kısım oldukça basit. Yapmanın birçok yolu var fakat ben requests kütüphanesini kullanmayı uygun gördüm:
[CODE lang="python" title="Sayfa verisini çekme fonksiyonu"]def get_page_GET_response(chapter: int, page: int):
response = requests.get(get_page_url(chapter, page), stream=True)
return response[/CODE]
Buradaki response nesnesinin içeriğini incelemek için resmi dokümantasyonu okuyabilirsiniz veya favori IDE'nizin otomatik tamamlama özelliğini kullanabilirsiniz (varsa). Dikkat edin ki elimize direkt bir resim gelmeyecek ama resmin ikili (binary) verisi gelecek. Veriyi incelemek için response.content, response.text, response.raw gibi alt nesneleri kullanabilirsiniz.
Buradaki stream=True ayarı, bir sonraki aşama için gerekti. Ben de yazıyı yazarken bir yandan kodunu yazıyordum, internette araştırdığıma göre bu olmadan kod çalışmıyor.

Sayfa verisini görsele dönüştürüp görseli bilgisayara kaydetme​

Elde ettiğimiz ikili veriyi bilgisayarımıza kaydetmek için pillow kütüphanesinin Image modülünü kullanabiliriz:
[CODE lang="python" title="Sayfa verisinden Image nesnesi oluşturma fonksiyonu"]def convert_page_data_to_image(page_response: requests.Response):
return Image.open(page_response.raw)[/CODE]
Bunun için şu StackOverflow cevabından yardım aldım. İşte burada stream=True ayarı kullanılmış. Bu olmadan kod hata veriyordu.

Sonrasında bu Image nesnesinin save metodunu kullanabiliriz:
[CODE lang="python" title="Resmi bilgisayara kaydetme fonksiyonu"]def save_image(image: Image.Image, filepath: str):
with open(filepath, "wb") as f:
image.save(f)[/CODE]
Resim ikili veri olduğu için kaydetmek istediğimiz dosyayı wb (write binary, "ikili yaz") modunda açmamız gerekiyor. Direkt w modunda açarsak zaten metot hata veriyor.

Tüm bir bölümü indirme​

Yalnızca bir sayfayı nasıl indireceğimizi gördük. Şimdiyse sıra bir bölümün tüm sayfalarını sırayla indirmeye geldi. Ben burada şöyle bir mantıkla hareket ettim: Sayfa numarasını bir bir artır, ne zaman ki bağlantıya gönderilen istek hata döndürürse o bölümün tüm sayfaları gezilmiş demektir.
[CODE lang="python" title="Tüm bir bölümü indirme kodu"]ap = ArgumentParser()
ap.add_argument("-b", "--bolum", type=int, required=True)
args = ap.parse_args()
chapter, page = args.bolum, 1

while True:
res = get_page_GET_response(chapter, page)
if not res.ok:
print(f"{chapter}. bölümün tüm sayfaları indirildi ({page - 1})")
break
image = convert_page_data_to_image(res)
filedir = f"chapters/{chapter}"
os.makedirs(filedir, exist_ok=True)
filename = f"{page}.jpg"
save_image(image, os.path.join(filedir, filename))
print(f"{chapter}. bölüm {page}. sayfa indirildi")
page += 1[/CODE]
  • Bölüm numarasını, programı çalıştırırken verebilmek için argparse kütüphanesinden ArgumentParser'ı kullandım. Bunun yerine sys.argv'yi kullanmak da tercih edilebilirdi ama bu yöntemi daha uygun buldum.
    • Örneğin py main.py -b 183, 183. bölümü indirtir. (Sizde py komutu çalışmayabilir, python, python3 vb. çalışabilir.)
  • Sayfa numarasını (page) 1'den başlatıp while döngüsünde bir bir artırıyoruz.
  • Ne zaman ki isteğe döndürülen cevabın HTTP durum kodu 200 değilse (if not res.ok), normal koşullar altında bir önceki iterasyonda son sayfaya gelmişiz demektir.
  • Dosyalama sistemi olarak şöyle düşündüm:
    • Bölümler birer klasör olup chapters üst klasörü altında bulunacak. Bölüm klasörlerinin isimleri direkt bölüm numaraları olacak.
    • Sayfalar, bölüm klasörlerinin altında {sayfa numarası}.jpg isminde birer JPG dosyası olacak.
    • Örneğin 183. bölümün 10. sayfasının konumu chapters/183/10.jpg olacak.
  • Bu dosyalama sistemine göre öncelikle bölüm klasörü yoksa onu oluşturmamız gerekecek: chapter/{bölüm numarası}/. Bunun için os.makedirs(filedir, exist_ok=True) satırını yazdım. Burada exist_ok=True ayarı, eğer klasör zaten varsa oluşturmaya kalkmamasını ve dolayısıyla hata vermemesini sağlıyor.
Tüm kodu aşağıda paylaştım:
[CODE lang="python" title="Kodun son hali"]import os
from argparse import ArgumentParser

import requests
from PIL import Image


def get_page_url(chapter: int, page: int) -> str:
return f"https://cdn.readonepiece.com/file/mangap/2/1{str(chapter).rjust(4, '0')}000/{page}.jpeg"


def get_page_GET_response(chapter: int, page: int) -> requests.Response:
response = requests.get(get_page_url(chapter, page), stream=True)
return response


def convert_page_data_to_image(page_response: requests.Response) -> Image.Image:
return Image.open(page_response.raw)


def save_image(image: Image.Image, filepath: str) -> None:
with open(filepath, "wb") as f:
image.save(f)


ap = ArgumentParser()
ap.add_argument("-b", "--bolum", type=int, required=True)
args = ap.parse_args()
chapter, page = args.bolum, 1

while True:
res = get_page_GET_response(chapter, page)
if not res.ok:
print(f"{chapter}. bölümün tüm sayfaları indirildi ({page - 1})")
break
image = convert_page_data_to_image(res)
filedir = f"chapters/{chapter}"
os.makedirs(filedir, exist_ok=True)
filename = f"{page}.jpg"
save_image(image, os.path.join(filedir, filename))
print(f"{chapter}. bölüm {page}. sayfa indirildi")
page += 1
[/CODE]

Geliştirme Önerileri​

  • Programın, verilen bölüm numara aralığına göre çalışması sağlanabilir.
  • Kodu temizleştirmek için chapter ve page bilgilerini içinde barındıran bir Page sınıfı yazılıp gerekli yardımcı metotlar implemente edilebilir.
  • Bir sayfa önceden indirilmişse tekrar indirilmeyebilir.

Okuduğunuz için teşekkürler. 🏴‍☠️

Luffy gülümsüyor
 
Bu cidden lazım olan bir projeydi bazı siteler düzgün okuma şansı vermiyor maalesef. One Piece mangasına başlamayı düşünüyordum.
 
Hatta ilk fikrim (aslında ikinci) Google'da verilen girdiyle resim aratıp ilk çıkan resimleri indirmekti ama buna göre çok daha karışıktı o. Belki bir gün ona da uğraşırım.