PHPでスクレイピング

```php
$html = '<html>
	<head>
            <meta content="htmlスクレイピング - Qiita" property="og:title">
            <meta content="https://cdn.qiita.com/assets/qiita-fb-2887e7b4aad86fd8c25cea84846f2236.png" property="og:image">
            <meta content="ogのdescription" property="og:description">
        </head>
	<body>
		<p id="first">上</p>
		<p id="second">中</p>
		<p id="third" class="test">下</p>
		<div>sampleA</div>
		<div>
			<p class="test">sampleB</p>
		</div>
		<div id="area">
			<p>
				<span>sampleC</span>
			</p>
			<div>sampleD</div>
            sampleE
		</div>
	</body>
</html>';

$dom_document = new DOMDocument();
@$dom_document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$xml_object = simplexml_import_dom($dom_document);
```

# DOMDocumentを使う

```php
foreach ($dom_document->getElementsByTagName('p') as $item)
{
    foreach ($item->childNodes as $node)
    {
        var_dump($node->nodeValue, $node->textContent);
    }
}
```

# SimpleXMLElementを使う

## 基本

```php
(string)$xml_object->body->p[0];
// string(3) "上"
```

```php
(string)$xml_object->body->p[0]->attributes()->id;
// string(5) "first"
```

```php
(string)$xml_object->body->p[0]['id'];
// string(5) "first"
```

## xpath

### 要素の選択

##### 要素の内容で選択

```php
$xml_object->xpath('/html/body/p[.="中"]'); // ルートから指定
$xml_object->xpath('//body/p[.="中"]'); // 途中から指定
// array(1) { [0]=> object(SimpleXMLElement)#3 (2) { ["@attributes"]=> array(1) { ["id"]=> string(6) "second" } [0]=> string(3) "中" } }
```

##### 要素の属性で選択

```php
$xml_object->xpath('//p[@id="first"]');
// array(1) { [0]=> object(SimpleXMLElement)#3 (2) { ["@attributes"]=> array(1) { ["id"]=> string(5) "first" } [0]=> string(3) "上" } }

$xml_object->xpath('//p[@class="test"]');
// array(1) { [0]=> object(SimpleXMLElement)#3 (2) { ["@attributes"]=> array(2) { ["id"]=> string(5) "third" ["class"]=> string(4) "test" } [0]=> string(3) "下" } }
```

##### 要素の順番で選択(最初の要素は1)

```php
$xml_object->xpath('//p[2]');
// array(1) { [0]=> object(SimpleXMLElement)#3 (2) { ["@attributes"]=> array(1) { ["id"]=> string(6) "second" } [0]=> string(3) "中" } }
```

##### 特定の子要素を持つ要素を選択

```php
$xml_object->xpath('//div[p]'); // 直近の子要素にpを持つdiv
// array(2) { [0]=> object(SimpleXMLElement)#3 (1) { ["p"]=> string(7) "sampleB" } [1]=> object(SimpleXMLElement)#4 (2) { ["p"]=> object(SimpleXMLElement)#5 (1) { ["span"]=> string(7) "sampleC" } ["div"]=> string(7) "sampleD" } }
```

##### 階層を使用して選択

```php
$xml_object->xpath('//div/p[1]/../../p[1]');
// array(1) { [0]=> object(SimpleXMLElement)#3 (2) { ["@attributes"]=> array(1) { ["id"]=> string(5) "first" } [0]=> string(3) "上" } }
```

### 値の取得

`$xml_object->xpath($xpath_string)`は取得できない場合は空配列、エラーの場合はfalseを返します。
[xpath](http://php.net/manual/ja/simplexmlelement.xpath.php)

`if ( ! empty($xml_object->xpath($xpath_string)))`で判定し、オブジェクトを持っているものを前提とすること。

##### 要素の内容を取得

```php
(string)$xml_object->xpath('//p[2]')[0];
// string(3) "中"
```

##### 要素の属性を取得

```php
(string)$xml_object->xpath('//p[2]/@id')[0];
// string(6) "second"
```

```php
(string)$xml_object->xpath('//p[3]/@class')[0];
// string(4) "test"
```

```php
(string)$xml_object->xpath('//meta[@property="og:title"]/@content')[0];
// string(33) "htmlスクレイピング - Qiita"
```

```php
(string)$xml_object->xpath('//meta[@property="og:image"]/@content')[0];
// string(74) "https://cdn.qiita.com/assets/qiita-fb-2887e7b4aad86fd8c25cea84846f2236.png"
```

```php
(string)$xml_object->xpath('//meta[@property="og:description"]/@content')[0];
// string(16) "ogのdescription"
```

---
xpathを分けて記述することもできます

```php
$xml_object->xpath('//div')[1]->xpath('p');
// array(1) { [0]=> object(SimpleXMLElement)#6 (2) { ["@attributes"]=> array(1) { ["class"]=> string(4) "test" } [0]=> string(7) "sampleB" } }
```

#### 指定した要素内のテキストを全て取得したい場合

**SimpleXMLElement内にSimpleXMLElementを持っている場合は(string)では内包されたSimpleXMLElement内のテキストは取得できません。
(string)では直下のタグの付いていないテキストのみしか取得できません。**

```php
$xml_object->xpath('//div[@id="area"]');
```

```
array(1) {
  [0]=>
  object(SimpleXMLElement)#10948 (3) {
    ["@attributes"]=>
    array(1) {
      ["id"]=>
      string(4) "area"
    }
    ["p"]=>
    object(SimpleXMLElement)#10950 (1) {
      ["span"]=>
      string(7) "sampleC"
    }
    ["div"]=>
    string(7) "sampleD"
  }
}
```

```php
(string)$xml_object->xpath('//div[@id="area"]')[0];
```

```
string(87) "


                    sampleE
                "
```

配下のテキストを全て取得したい場合は以下のようにタグを削除して取得します。

```php
strip_tags($xml_object->xpath('//div[@id="area"]')[0]->asXml());
```

```
string(147) "

                        sampleC

                    sampleD
                    sampleE
                "
```

**`/@`の指定があるxpathは以下のようになってしまいますので注意**（`/@id`だと`id="XXXXXX"`になってしまいます）

```php
(string)$xml_object->xpath('//@id')[0];
```

```
string(5) "first"
```

```php
strip_tags($xml_object->xpath('//@id')[0]->asXml());
```

```
string(11) " id="first""
```

実際にスクレイピングで使ってみたらHTMLエンティティ化された文字列が来るものもあったので下記のように使ったりしました。

```php
mb_ereg_replace('\s', '', html_entity_decode($string, ENT_QUOTES));// 全角スペースを除く空白文字は削除
```