İçeriğe geç
CılgınYazılım

Web Geliştirme ve Frameworkler kod örnekli

PHP Simple HTML DOM Parser Kullanımı ve DOMDocument

Simple HTML DOM hâlâ çalışıyor ama 2019'dan beri yeni sürümü yok. PHP'nin kendi DOM araçları aynı işi ek paket olmadan yapıyor.

Çılgın Yazılım 6 dk okuma

PHP Simple HTML DOM Parser Kullanımı ve DOMDocument
İçindekiler
  1. Kurulum: Composer ya da klasörü kopyalamak
  2. Seçiciyle veri çekmek
  3. PHP 8'de görülen uyarı ve boyut sınırı
  4. Ek paket olmadan: DOMDocument ve DOMXPath
  5. PHP 8.4 ve sonrası: Dom\HTMLDocument ile querySelector
  6. Hangisini seçmeli?
  7. CodeIgniter 4'te sayfayı indirip ayrıştırmak
  8. Başka sitelerden veri çekerken

PHP Simple HTML DOM Parser, bir HTML belgesini yükleyip içindeki öğeleri jQuery'ye benzeyen CSS seçicileriyle bulmanızı sağlayan açık kaynak bir kütüphanedir. Composer paketinin son sürümü 9 Kasım 2019 tarihli 2.0-RC2; PHP 8.2'de çalışıyor, boş kurucuyla kullanıldığında deprecation uyarısı veriyor. Yeni bir projede aynı işi ek paket kurmadan PHP'nin DOMDocument ve DOMXPath sınıfları, PHP 8.4'ten itibaren de Dom\HTMLDocument yapıyor.

Örnekler PHP 8.2.12'de çalıştırıldı; PHP 8.4 örneği php.net'in sürüm notundaki örnekten uyarlandı, yerelde 8.4 olmadığı için çalıştırılmadı.

Kurulum: Composer ya da klasörü kopyalamak

Paket Packagist'te simplehtmldom/simplehtmldom adıyla duruyor ve PHP 5.6 ve üstünü istiyor. İlk tuzak kurulumda: paketin kararlı sürümü yok, yalnız bir sürüm adayı (RC) var. Bu yüzden sürüm yazmadan verilen komut durur:

Bash
composer require simplehtmldom/simplehtmldom
# Could not find a version of package simplehtmldom/simplehtmldom matching
# your minimum-stability (stable).

composer require simplehtmldom/simplehtmldom:2.0-RC2

Sürümü komutta açıkça yazmak, projenin minimum-stability ayarını düşürmekten daha güvenlidir; o ayar yalnız bu paketi değil, bütün bağımlılıkları kararsız sürümlere açar.

str_get_html() ve file_get_html() fonksiyonları simple_html_dom.php dosyasından gelir. 2.0-RC2'de bu dosya tek başına yetmez; HtmlDocument.php, HtmlNode.php, constants.php ve Debug.php dosyalarını aynı klasörden çağırır, yalnız onu kopyalarsanız "include_once(HtmlDocument.php): Failed to open stream" alırsınız. Composer da bu dosyayı otomatik yüklemez: Composer kurulumunda file_get_html() tanımsızdır. Sınıf adıyla çalışın ya da dosyayı elle require edin.

Seçiciyle veri çekmek

Aşağıdaki örnek, bir ürün listesinden ad, bağlantı ve fiyatı alır. find() seçiciye uyan öğeleri döndürür; ikinci parametre verilirse o sıradaki tek öğeyi verir:

PHP
require 'vendor/autoload.php';
use simplehtmldom\HtmlDocument;

$html = '<ul><li class="urun"><a href="https://cilginyazilim.com/p/1">Çağrı Şişesi</a><span class="fiyat">149,90 ₺</span></li></ul>';

// HTML kurucuya verilir: boş kurucu PHP 8.1+'da trim(null) uyarısı üretir
$belge = new HtmlDocument($html);

foreach ($belge->find('li.urun') as $li) {
    $baglanti = $li->find('a', 0);
    $fiyat    = $li->find('span.fiyat', 0);
    if ($baglanti === null || $fiyat === null) {
        continue; // sitenin HTML'i değişti: sessizce geçme, günlüğe yaz
    }
    echo trim($baglanti->plaintext), ' | ', $baglanti->href, ' | ', trim($fiyat->plaintext), "\n";
}
// Çağrı Şişesi | /p/1 | 149,90 ₺

unset($belge); // 2.0-RC2'de clear() boş bir çağrıdır; belleği unset bırakır

Dikkat: aranan öğe yoksa find('…', 0) null döndürür ve zincirdeki bir sonraki ->plaintext hataya dönüşür. Başka bir sitenin HTML'i değiştiğinde ilk kırılan yer burasıdır; örnekteki gibi her tekil find() sonucunu kullanmadan önce kontrol edin.

PHP 8'de görülen uyarı ve boyut sınırı

Nesne boş kurulup HTML sonradan load() ile verildiğinde, error_reporting(E_ALL) altında şu satır çıkıyor:

Metin
Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated
in …/simplehtmldom/HtmlDocument.php on line 269

Uyarının kaynağı yükleme değil, boş kurucudur: new HtmlDocument() içeride trim(null) çağırır. str_get_html() ve file_get_html() de nesneyi böyle kurduğu için her çağrıda uyarı verir. HTML'i doğrudan kurucuya verdiğimizde (new HtmlDocument($html)) uyarı hiç çıkmadı. PHP 8.1'den beri dahili fonksiyonlara null geçmek deprecation uyarısı verir; düzeltme ana dalda Nisan 2022'den beri duruyor ama hiçbir sürüme girmedi.

İkinci ayrıntı kaynak kodda: file_get_html() ve str_get_html(), içerik boşsa ya da MAX_FILE_SIZE sınırını (2.0-RC2'de 2.621.440 bayt) aşıyorsa hata vermeden false döndürür. Sonraki ->find() satırı bu yüzden "Call to a member function find() on bool" hatasıyla durur; bu hatayı görüyorsanız önce bu sınıra bakın. Sınır yalnız bu iki fonksiyonda var: new HtmlDocument($html) 2,7 MB'lık bir belgeyi de yükledi. Fonksiyonda kalmanız gerekiyorsa file_get_html()'in beşinci parametresi ($maxLen) sınırı yükseltir.

Ek paket olmadan: DOMDocument ve DOMXPath

PHP'nin yerleşik DOM eklentisi aynı işi CSS yerine XPath sorgusuyla yapar. Tek bir tuzağı var: loadHTML() kodlama bilgisi olmayan bir parçayı UTF-8 değil ISO-8859-1 sayar. Aynı ürün listesini kodlama belirtmeden yüklediğimizde "Çağrı Şişesi" metni "ÃaÄrı ÅiÅesi" olarak çıktı. Başa kodlama bildirimi eklemek bunu düzeltir:

PHP
libxml_use_internal_errors(true);   // HTML5 etiketlerindeki uyarıları topla
$dom = new DOMDocument();
$dom->loadHTML('<?xml encoding="UTF-8">' . $html);
libxml_clear_errors();              // biriken uyarı listesini boşalt
$xp = new DOMXPath($dom);

foreach ($xp->query('//li[contains(concat(" ", normalize-space(@class), " "), " urun ")]') as $li) {
    $a = $xp->query('.//a', $li)->item(0);
    echo trim($a->textContent), ' | ', $a->getAttribute('href'), "\n";
}
// Çağrı Şişesi | /p/1

XPath'te sınıf eşleştirme uzun görünür, çünkü class niteliği birden çok sınıf taşıyabilir; contains(concat(…)) kalıbı "urun" sınıfını "urunler" gibi benzer adlardan ayırır.

PHP 8.4 ve sonrası: Dom\HTMLDocument ile querySelector

PHP 8.4, HTML5 kurallarına uyan yeni bir DOM API'si getirdi. Belge Dom\HTMLDocument::createFromString() ile oluşturulur ve CSS seçicisi doğrudan kullanılır:

PHP
$dom = Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
$fiyat = $dom->querySelector('li.urun span.fiyat');
echo $fiyat?->textContent;

Canlı sunucunuz 8.4 ya da üstündeyse yeni kod için ne Simple HTML DOM'a ne de XPath'e gerek kalır.

Hangisini seçmeli?

Üç seçeneği aynı ölçütlerle karşılaştırınca karar PHP sürümüne bağlanıyor:

Simple HTML DOMDOMDocument + DOMXPathDom\HTMLDocument
KurulumComposer ya da klasörü kopyalamakPHP ile gelirPHP 8.4 ile gelir
SeçiciCSSXPathCSS (querySelector)
Türkçe karakterDenemede sorunsuzKodlama bildirilmeliKodlama parametreyle de verilebilir
BakımSon sürüm 2019PHP ile güncellenirPHP ile güncellenir

Eski bir projede Simple HTML DOM çalışıyorsa acele taşımaya gerek yok; yeni kodu DOMDocument ya da 8.4'te Dom\HTMLDocument ile yazın.

CodeIgniter 4'te sayfayı indirip ayrıştırmak

Ayrıştırıcı sayfayı indirmez, yalnız okur. file_get_html($url) indirmeyi de üstlenir; ama üçüncü parametreye stream context verilmezse php.ini'deki default_socket_timeout (varsayılan 60 saniye) geçerlidir, 404'te de yalnız false döner. CI4'ün istemcisi de kendiliğinden korumaz: 4.6.3'te timeout varsayılanı 0 (sınırsız), connect_timeout ise 150 saniyedir. Uzak sayfayı zaman aşımı tanımlı bir HTTP istemcisiyle alıp ayrıştırıcıya metin olarak verin. Aşağıdaki kodu bu blogun liste sayfasının yerel kopyasına karşı çalıştırdık: durum 200 döndü ve ilk üç yazı başlığı okundu; olmayan bir adres 404 verdi.

PHP
use CodeIgniter\HTTP\Exceptions\HTTPException;

function basliklariOku(string $adres): array
{
    $istemci = single_service('curlrequest', ['timeout' => 5, 'connect_timeout' => 3, 'http_errors' => false]);
    try {
        $yanit = $istemci->get($adres, ['headers' => ['User-Agent' => 'OrnekOkuyucu/1.0']]);
    } catch (HTTPException $e) {
        log_message('warning', 'İndirilemedi: {adres}', ['adres' => $adres]);
        return []; // zaman aşımı ya da bağlantı hatası
    }
    if ($yanit->getStatusCode() !== 200) {
        return []; // 404, 500: ayrıştırmaya geçme
    }

    libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    $dom->loadHTML('<?xml encoding="UTF-8">' . $yanit->getBody());
    libxml_clear_errors();
    $xp = new DOMXPath($dom);
    $basliklar = [];
    foreach ($xp->query('//h2[contains(concat(" ", normalize-space(@class), " "), " cy-yazi-satir__baslik ")]/a') as $a) {
        $basliklar[] = trim($a->textContent);
    }

    return $basliklar;
}

Dikkat: service() paylaşılan örneği döndürür; aynı istekte istemci daha önce kurulduysa verdiğiniz timeout sessizce yok sayılır, single_service() bu yüzden. http_errors kapalıyken 404 ve 500 istisna fırlatmaz, ama zaman aşımı yine HTTPException fırlatır; çağrı bu yüzden try/catch içinde.

Başka sitelerden veri çekerken

basliklariOku() fonksiyonunu her sayfa isteğinde çağırmayın: sonuç boş değilse cache()->save('blog_basliklari', $basliklar, 3600) ile bir saat saklayın (süreyi seçmek için önbellek stratejileri). 200 yanıtla gelen boş dizi, seçicinin artık eşleşmediği anlamına gelir; log_message('warning', 'Seçici eşleşmedi: {adres}', ['adres' => $adres]) satırı kırılmayı ilk gün gösterir. robots.txt'nin yasakladığı yolları okumayın. Dış kaynağa bağımlı her işte zaman aşımı ve yeniden deneme kuralları için üçüncü parti API entegrasyonunda dayanıklılık yazısına bakabilirsiniz.

Simple HTML DOM'u bugün kırılgan yapan seçicileri değil, 2019'da dondurulmuş sürümüdür: PHP 8 uyarısının düzeltmesi ana dalda duruyor ama hiçbir sürüme girmedi. Eski projede kalıyorsa nesneyi new HtmlDocument($html) ile kurun, clear() yerine unset() kullanın, her tekil find() sonucunu null'a karşı kontrol edin ve str_get_html() false döndürürse önce 2.621.440 baytlık sınıra bakın. Yeni kodu PHP 8.3 ve öncesinde kodlama bildirimli DOMDocument ile, 8.4 ve sonrasında Dom\HTMLDocument ile yazın; sayfayı her durumda zaman aşımı tanımlı bir istemciyle indirin.

Konunun derinlemesine anlatımı için: php.net — PHP 8.4 sürüm notları.

Beğeniniz benzer içerikleri öne çıkarmamıza yardımcı olur.

Sık sorulan sorular

Simple HTML DOM Parser PHP 8 ile çalışır mı?

Çalışır. 2.0-RC2'yi PHP 8.2.12'de denedik; seçiciler doğru sonuç verdi. Nesne boş kurulduğunda (new HtmlDocument()) ve str_get_html() ile file_get_html() her çağrıldığında "trim(): Passing null" deprecation uyarısı çıkıyor. HTML'i kurucuya verirseniz uyarı hiç çıkmaz.

file_get_html neden false döndürüyor?

Üç durumda false döner: sayfa indirilemediğinde, boş geldiğinde ya da 2.621.440 baytlık MAX_FILE_SIZE sınırını aştığında. İndirme hatası 404, 403, zaman aşımı ya da kapalı allow_url_fopen olabilir; bu durumda bir de "Failed to open stream" uyarısı düşer. Hangisinin olduğunu false söylemez; sayfayı durum kodunu gösteren bir HTTP istemcisiyle indirin.

DOMDocument Türkçe karakterleri neden bozuyor?

loadHTML kodlama bilgisi olmayan bir parçayı UTF-8 değil ISO-8859-1 sayar; "Çağrı" metni "ÃaÄrı" gibi görünür. HTML'in başına <?xml encoding="UTF-8"> eklemek ya da belgede meta charset bulunması sorunu çözer.

DOMDocument loadHTML "Tag … invalid in Entity" uyarısı neden çıkar?

loadHTML'in kullandığı libxml ayrıştırıcısı HTML5 etiketlerini tanımaz; header, section, nav gibi her etiket için bu uyarıyı verir. Ağaç yine kurulur ve sorgular çalışır. Yüklemeden önce libxml_use_internal_errors(true) çağırın, iş bitince libxml_clear_errors() ile biriken listeyi boşaltın.

Yazan

Çılgın Yazılım

Ben Evren Çılgın; yazılım geliştirme, web teknolojileri ve sistem tasarımı alanlarında uzmanlaşmış bir geliştiriciyim. Uzun yıllardır hem frontend hem de backend tarafında üretken, ölçeklenebilir ve kullanıcı dostu çözümler üretiyorum.

Hakkımızda Bize ulaşın

Bu konuda bir projeniz mi var?

İhtiyacınızı birkaç cümleyle anlatın; uygun yaklaşımı birlikte belirleyelim.

Projenizi Anlatın

İlgili yazılar

Yorumlar

Henüz yorum yok. Sorunuzu ya da deneyiminizi ilk siz yazın.

Yorum yazın

Yorumunuz onaylandıktan sonra yayınlanır. Ekibimiz gerekirse konuyla ilgili bir yanıt da paylaşır.

Yayınlanmaz; yalnızca gerektiğinde size ulaşmak için.

En az 10 karakter.

Tüm yazılar