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.

İçindekiler
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:
composer require simplehtmldom/simplehtmldom
# Could not find a version of package simplehtmldom/simplehtmldom matching
# your minimum-stability (stable).
composer require simplehtmldom/simplehtmldom:2.0-RC2Sü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:
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ırDikkat: 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:
Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated
in …/simplehtmldom/HtmlDocument.php on line 269Uyarı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:
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/1XPath'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:
$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 DOM | DOMDocument + DOMXPath | Dom\HTMLDocument | |
|---|---|---|---|
| Kurulum | Composer ya da klasörü kopyalamak | PHP ile gelir | PHP 8.4 ile gelir |
| Seçici | CSS | XPath | CSS (querySelector) |
| Türkçe karakter | Denemede sorunsuz | Kodlama bildirilmeli | Kodlama parametreyle de verilebilir |
| Bakım | Son sürüm 2019 | PHP ile güncellenir | PHP 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.
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ı.
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.
Bu konuda bir projeniz mi var?
İhtiyacınızı birkaç cümleyle anlatın; uygun yaklaşımı birlikte belirleyelim.
İ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.
