Here is a simple web scraping example using the PHP DOM that tries to get the largest text body of a HTML document. I needed it for a spider that had to show a short description for a page. It assumes that document annotation can be the largest <div>, <td> or <p> element in the page.
In the example I show a way to prevent a bug in the DOM as it sometimes just doesn't recognize html encoding. It seems to work if you put charset meta tag right after the head tag of the document.
<?php
$ch= curl_init();
curl_setopt ($ch, CURLOPT_URL, '...put url here...' );
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch,CURLOPT_VERBOSE,1);
curl_setopt($ch, CURLOPT_USERAGENT, 'set sth...');
curl_setopt ($ch, CURLOPT_REFERER, '...set sth...'); //just a fake referer
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch,CURLOPT_POST,0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 20);
$html= curl_exec($ch);
$html1= curl_getinfo($ch);
//try to get page encoding as it was sent from server
if ($html1['content_type']){
$arr= explode('charset=',$html1['content_type']);
$csethdr= strtolower(trim($arr[1]));
} else {
$csethdr= false;
}
$cset= false;
$arr= array();
//This has to replace page meta tags for charset with utf-8, but it doesn't actually help(see the bug info).
if (preg_match_all(
'/(<meta\s*http-equiv="Content-Type"\s*content="[^;]*;
\s*charset=([^"]*?)(?:"|\;)[^>]*>)/' //merge this line
,$html,$arr,PREG_PATTERN_ORDER)){
$cset= strtolower(trim($arr[2][0]));
if ($cset!='utf-8'||$cset!=$csethdr){
$new= str_replace($arr[2][0],'utf-8',$arr[1][0]);
$html= str_replace($arr[1][0],$new,$html);
$cset= $csethdr;
} else {
$cset= false;
}
if ($cset=='utf-8'){
$cset= false;
}
}
unset($arr);
if ($cset){
$html= iconv($cset,'utf-8',$html);
}
unset($cset);
//solve dom bug
$html=preg_replace('/<head[^>]*>/','<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
',$html);
$dom= new DOMDocument();
$dom->loadHTML($html);
$dom->preserveWhiteSpace = false;
function getMaxTextBody($dom){
$content = $dom->getElementsByTagname('div');
$content2= $dom->getElementsByTagname('td');
$content3= $dom->getElementsByTagname('p');
$new= array();
foreach ($content as $value) {
$new[]= $value;
unset($value);
}
unset($content);
foreach ($content2 as $value) {
$new[]= $value;
unset($value);
}
unset($content2);
foreach ($content3 as $value) {
$new[]= $value;
unset($value);
}
unset($content3);
$maxlen= 0;
$result= '';
foreach ($new as $item)
{
$str= $item->nodeValue;
if (strlen($str)>$maxlen){
$content1= $item->getElementsByTagName('div');
$content2= $item->getElementsByTagname('td');
$content3= $item->getElementsByTagname('p');
$contentnew= array();
foreach ($content1 as $value) {
$contentnew[]= $value;
unset($value);
}
unset($content1);
foreach ($content2 as $value) {
$contentnew[]= $value;
unset($value);
}
unset($content2);
foreach ($content3 as $value) {
$contentnew[]= $value;
unset($value);
}
unset($content3);
if (count($contentnew)==0){
$result= $str;
} else {
foreach ($contentnew as $value) {
$str1= getMaxTextBody($value);
$str2= $value->nodeValue;
//let's say largest body has more than 50% of the text in its parent
if (strlen($str1)*2<strlen($str2)){
$str1= $str2;
}
if (strlen($str1)*2>strlen($str)&&strlen($str1)>$maxlen){
$result= $str1;
} elseif (strlen($str1)>$maxlen){
$result= $str1;
}
$maxlen= strlen($result);
}
}
$maxlen= strlen($result);
unset($contnentnew);
}
}
unset($new);
return $result;
}
print getMaxTextBody($dom);
?>
DOMDocument クラス
導入
HTML ドキュメントあるいは XML ドキュメント全体を表し、 ドキュメントツリーのルートとなります。
クラス概要
プロパティ
- actualEncoding
-
非推奨。ドキュメントの実際のエンコーディング。 読み込み専用で、 encoding と同等の内容です。
- config
-
非推奨。 DOMDocument::normalizeDocument() を実行する際に使用する設定。
- doctype
-
このドキュメントに関連付けられた文書型宣言
- documentElement
-
ドキュメントの子ノードであるドキュメント要素に対し、 直接アクセスするために便利な属性
- documentURI
-
ドキュメントの位置。未定義の場合は NULL
- encoding
-
XML 宣言で指定したドキュメントのエンコーディング。 この属性は、DOM Level 3 の最終的な仕様には存在しません。 しかし、この実装で XML ドキュメントのエンコーディングを扱うにはこれを使用するしかありません。
- formatOutput
-
字下げや空白を考慮してきれいに整形した出力を行う。
- implementation
-
このドキュメントを処理する DOMImplementation オブジェクト
- preserveWhiteSpace
-
余分な空白を取り除かない。デフォルトは TRUE
- recover
-
プロプライエタリ。 リカバリーモードを有効にし、整形式でないドキュメントのパースを試みます。 この属性は DOM の仕様にはなく、libxml に固有のものです。
- resolveExternals
-
文書型宣言で外部エンティティを読み込む際に TRUE を設定する。 XML ドキュメントに文字エンティティを含める際に便利です。
- standalone
-
非推奨。 そのドキュメントがスタンドアローンかどうかを XML 宣言で指定したもの。 xmlStandalone に対応します。
- strictErrorChecking
-
エラー時に DOMException をスローする。デフォルトは TRUE
- substituteEntities
-
プロプライエタリ。 エンティティの置換を行うかどうか。 この属性は DOM の仕様にはなく、libxml に固有のものです。
- validateOnParse
-
DTD を読み込んで検証する。デフォルトは FALSE
- version
-
非推奨。 XML のバージョン。 xmlVersion に対応します。
- xmlEncoding
-
XML 宣言の一部として、このドキュメントのエンコーディングを 指定する属性。指定されていない場合や不明な場合 (たとえば ドキュメントがメモリ上に存在する場合など) は NULL
- xmlStandalone
-
XML 宣言の一部として、このドキュメントがスタンドアローンか どうかを指定する。指定されていない場合は FALSE
- xmlVersion
-
XML 宣言の一部として、このドキュメントのバージョン番号を指定する。 バージョン番号が定義されておらず、ドキュメントが "XML" の機能を サポートしている場合は、値は "1.0"
目次
- DOMDocument::__construct — 新しい DOMDocument オブジェクトを作成する
- DOMDocument::createAttribute — 新しい属性を作成する
- DOMDocument::createAttributeNS — 関連付けられた名前空間に新しい属性を作成する
- DOMDocument::createCDATASection — 新しい cdata ノードを作成する
- DOMDocument::createComment — 新しい comment ノードを作成する
- DOMDocument::createDocumentFragment — 新しい文書片を作成する
- DOMDocument::createElement — 新しい要素ノードを作成する
- DOMDocument::createElementNS — 関連付けられた名前空間に新しい要素を作成する
- DOMDocument::createEntityReference — 新しいエンティティ参照ノードを作成する
- DOMDocument::createProcessingInstruction — 新しい PI ノードを作成する
- DOMDocument::createTextNode — 新しいテキストノードを作成する
- DOMDocument::getElementById — id に対応する要素を検索する
- DOMDocument::getElementsByTagName — 指定したタグ名に対応するすべての要素を検索する
- DOMDocument::getElementsByTagNameNS — 指定した名前空間で、タグ名に対応するすべての要素を検索する
- DOMDocument::importNode — 現在のドキュメントにノードをインポートする
- DOMDocument::load — ファイルから XML を読み込む
- DOMDocument::loadHTML — 文字列から HTML を読み込む
- DOMDocument::loadHTMLFile — ファイルから HTML を読み込む
- DOMDocument::loadXML — 文字列から XML を読み込む
- DOMDocument::normalizeDocument — ドキュメントを正規化する
- DOMDocument::registerNodeClass — 基底ノード型を作成する際に使用する拡張クラスを登録する
- DOMDocument::relaxNGValidate — ドキュメントを relaxNG で検証する
- DOMDocument::relaxNGValidateSource — ドキュメントを relaxNG で検証する
- DOMDocument::save — 内部の XML ツリーをファイルに出力する
- DOMDocument::saveHTML — 内部のドキュメントを HTML 形式の文字列として出力する
- DOMDocument::saveHTMLFile — 内部のドキュメントを HTML 形式でファイルに出力する
- DOMDocument::saveXML — 内部の XML ツリーを文字列として出力する
- DOMDocument::schemaValidate — スキーマに基づいてドキュメントを検証する
- DOMDocument::schemaValidateSource — スキーマに基づいてドキュメントを検証する
- DOMDocument::validate — DTD に基づいてドキュメントを検証する
- DOMDocument::xinclude — DOMDocument オブジェクト内の XIncludes を置換する
DOMDocument
30-Nov-2008 09:59
15-May-2008 04:58
To indent a XML in a pretty way I use:
<?
$sXML = '<root><element><key>a</key><value>b</value></element></root>';
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$doc->loadXML($sXML);
echo $doc->saveXML();
?>
11-Apr-2008 10:48
Showing a quick example of how to use this class, just so that new users can get a quick start without having to figure it all out by themself. ( At the day of posting, this documentation just got added and is lacking examples. )
<?php
// Set the content type to be XML, so that the browser will recognise it as XML.
header( "content-type: application/xml; charset=ISO-8859-15" );
// "Create" the document.
$xml = new DOMDocument( "1.0", "ISO-8859-15" );
// Create some elements.
$xml_album = $xml->createElement( "Album" );
$xml_track = $xml->createElement( "Track", "The ninth symphony" );
// Set the attributes.
$xml_track->setAttribute( "length", "0:01:15" );
$xml_track->setAttribute( "bitrate", "64kb/s" );
$xml_track->setAttribute( "channels", "2" );
// Create another element, just to show you can add any (realistic to computer) number of sublevels.
$xml_note = $xml->createElement( "Note", "The last symphony composed by Ludwig van Beethoven." );
// Append the whole bunch.
$xml_track->appendChild( $xml_note );
$xml_album->appendChild( $xml_track );
// Repeat the above with some different values..
$xml_track = $xml->createElement( "Track", "Highway Blues" );
$xml_track->setAttribute( "length", "0:01:33" );
$xml_track->setAttribute( "bitrate", "64kb/s" );
$xml_track->setAttribute( "channels", "2" );
$xml_album->appendChild( $xml_track );
$xml->appendChild( $xml_album );
// Parse the XML.
print $xml->saveXML();
?>
Output:
<Album>
<Track length="0:01:15" bitrate="64kb/s" channels="2">
The ninth symphony
<Note>
The last symphony composed by Ludwig van Beethoven.
</Note>
</Track>
<Track length="0:01:33" bitrate="64kb/s" channels="2">Highway Blues</Track>
</Album>
If you want your PHP->DOM code to run under the .xml extension, you should set your webserver up to run the .xml extension with PHP ( Refer to the installation/configuration configuration for PHP on how to do this ).
Note that this:
<?php
$xml = new DOMDocument( "1.0", "ISO-8859-15" );
$xml_album = $xml->createElement( "Album" );
$xml_track = $xml->createElement( "Track" );
$xml_album->appendChild( $xml_track );
$xml->appendChild( $xml_album );
?>
is NOT the same as this:
<?php
// Will NOT work.
$xml = new DOMDocument( "1.0", "ISO-8859-15" );
$xml_album = new DOMElement( "Album" );
$xml_track = new DOMElement( "Track" );
$xml_album->appendChild( $xml_track );
$xml->appendChild( $xml_album );
?>
although this will work:
<?php
$xml = new DOMDocument( "1.0", "ISO-8859-15" );
$xml_album = new DOMElement( "Album" );
$xml->appendChild( $xml_album );
?>
