はじめに
顧客のニーズに合わせて特定のサービスのAPIを操作するラッパーを実装し、同様のタスクを基本的に単純にする必要がある場合もありますが、サービスにこのAPIが常にあるとは限らない、またはコンテンツページ全体を解析する必要があるという考えが生じます。
この記事の例として、XenForoフォーラムの
デモと事前作成されたトピックを使用します。ここから、見出し、作成時間、トピックテキスト自体の一般的なデータを解析します。解析は承認されたフォーラムアカウントで実行されます。 他のすべてのデータは、類推によって取得できます。
パーサー自体は、Yii2で便利に使用するためのコンポーネントとして実装されています。
何が必要ですか
- cURLライブラリはページの解析に使用されるため、事前にインストールする必要があります。
- Yiiは(解析プロセスのログ記録、解析自体の呼び出し、結果のレンダリングのために)あまり使用されないため、パーサー自体の状態を表示するためだけに使用されます。 したがって、このようなタスクには、 Yii2 minimalを使用するだけで十分です 。
始めましょう
ParserXenforoコンポーネントを作成します。 イベントは必要ないので、Objectから継承するだけで十分です。
<?php namespace app\components; use Yii; use \yii\base\Object; class ParserXenforo extends Object { }
ページをロードするには、プロパティと定数を追加する必要があります。 ホスト、ユーザー名、パスワード、curlOptプロパティ自体は、コンポーネント設定で設定されます。
<?php namespace app\components; use Yii; use \yii\base\Object; class ParserXenforo extends Object { const REQUEST_URI_LOGIN = 'login/login'; const COOKIES_FILE_NAME = 'cookies.txt'; private $_data; public $host; public $username; public $password; public $curlOpt; }
ページ読み込みメソッドを追加します。
最初に、curlOptに格納されるヘッダーとユーザーエージェントの設定値を取得するためのメソッドを実装し、将来的にはcURLに渡されます
protected function getCurlOpt($nameOpt) { if ($nameOpt !== 'userAgent' && $nameOpt !== 'header') { return false; } return $this->curlOpt[$nameOpt]; }
フォーラムで承認するには、POSTを介してユーザーのログインとパスワードを転送する必要があります。 これを行うには、認証URL(ホスト+認証URL)を作成します
protected function getLoginUrl() { return $this->host . self::REQUEST_URI_LOGIN; }
そして、リクエストのPOST行
protected function createPostRequestForCurl() { return 'login=' . $this->username . '&password=' . $this->password . '&remember=1'; }
認証を保存するために、実行時にCookieファイルを使用します。 このファイルのフルパスを取得するには、エイリアスパスからフルパスを受け取り、ファイル名を追加するメソッドを作成します。
protected function getPathToCookieFile($cookieFileName = self::COOKIES_FILE_NAME) { return Yii::getAlias('@app/runtime') . DIRECTORY_SEPARATOR . $cookieFileName; }
渡されたパラメーターを使用して、ページの解析メソッドを実装します。 まず、承認アクションに進み、POST値を転送して、渡されたURLに戻りますが、既に承認済みアカウントにあります。 念のため。 たとえば、私はこのフォーラムで、権限のないユーザーからコンテンツを隠すためのモジュールをインストールすることをよく見ました。
データを_dataに正常にロードした後、データがロードされたことをYii :: info()メソッドで記録します。
public function loadUsingCurl($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->loginUrl); curl_setopt($ch, CURLOPT_FAILONERROR, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_REFERER, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getCurlOpt('header')); curl_setopt($ch, CURLOPT_COOKIEFILE, $this->pathToCookieFile); curl_setopt($ch, CURLOPT_COOKIEJAR, $this->pathToCookieFile); curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); curl_setopt($ch, CURLOPT_USERAGENT, $this->getCurlOpt('userAgent')); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $this->createPostRequestForCurl()); $this->_data = curl_exec($ch); if (curl_exec($ch) === false) { throw new \Exception(curl_errno($ch) . ': ' . curl_error($ch)); } curl_close($ch); Yii::info(Yii::t('app', 'Loading data page')); return $this; }
コンポーネントの基本部分が実装されます。 次に、コンポーネントに接続して構成する必要があります。 たとえば、コンポーネントのある場所、ベースURL、認証データなど、ユーザーエージェントでコンピューターのデータを指定します。
承認のパラメーターは、admin:admin demoで提供されました。 彼らはそれを数日間、またはむしろ2014年3月24日午前7時26分までしか与えませんでした
.... 'components' => [ ... 'parser' => [ 'class' => 'app\components\ParserXenforo', 'host' => 'http://9af5766eb2759a49.demo-xenforo.com/130/index.php?', 'username' => 'admin', 'password' => 'admin', 'curlOpt' => [ 'userAgent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36', 'header' => [ 'Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1', 'Accept-Language: en-US,en;q=0.8,ru;q=0.6,uk;q=0.4', 'Accept-Charset: Windows-1251, utf-8, *;q=0.1', 'Accept-Encoding: deflate, identity, *;q=0', ] ] ], ... ], ....
コントローラーで、アクションを呼び出してパフォーマンスを確認し、すべてがうまくいったかどうかをapp.logのログで確認できます
$urlThread = 'http://9af5766eb2759a49.demo-xenforo.com/130/index.php?threads/some-thread.1/'; $dataParse = Yii::$app->parser->loadUsingCurl($urlThread);
データ解析
ページのDOMDocumentクラスのオブジェクトを取得し、それを保存するプロパティを追加するメソッドを作成することから始めましょう。 libxmlエラーを無効にする前に、ロード後に逆の操作を行います。 ページの解析に関するいくつかの問題を回避するため。 その結果、ページのDOMを取得してさらに作業を進めることができます。 正規表現も使用できます。 ただし、この場合はDOMを使用する方が便利です。
public function createDomDocument() { $this->_dom = new \DOMDocument(); libxml_use_internal_errors(true); if ($this->_dom->loadHTML($this->_data)) { Yii::info(Yii::t('app', 'Create DomDocument')); } else { Yii::info(Yii::t('app', 'An error occurred when creating an object of class DOMDocument')); } libxml_use_internal_errors(false); return $this; }
XPath式を実行して必要なデータを取得するのに便利なように、DOMXPathクラスの新しいオブジェクトを取得する方法に目を向けます。
public function createDomXpath() { $this->_xpath = new \DOMXPath($this->_dom); Yii::info(Yii::t('app', 'Create DomXpath')); return $this; }
さて、これでXPathクエリに安全に進み、タイトル、タイムスタンプ、コンテンツなどのデータを取得できます。
最初にヘッダーを取得し、_titleプロパティを追加します
public function parseTitle() { $xpathQuery = '*//h1'; $nodes = $this->_xpath->query($xpathQuery, $this->_dom); if ($nodes->length === 0) { Yii::info(Yii::t('app', 'Error parse title')); } $this->_title = $nodes->item(0)->nodeValue; Yii::info(Yii::t('app', 'Parse title')); return $this; }
トピックの追加のタイムスタンプ
public function parseTimestamp() { $xpathQuery = '*//p[@id="pageDescription"]/a/abbr'; $nodes = $this->_xpath->query($xpathQuery, $this->_dom); if ($nodes->length === 0) { Yii::info(Yii::t('app', 'Error parse timestamp')); return $this; }
最終取得コンテンツ
public function parseContent() { $xpathQuery = '*//blockquote[@class="messageText ugc baseHtml"]'; $nodes = $this->_xpath->query($xpathQuery, $this->_dom); if ($nodes->length === 0) { Yii::info(Yii::t('app', 'Error parse content')); return $this; } $this->_content = $nodes->item(0)->nodeValue; Yii::info(Yii::t('app', 'Parse content')); return $this; }
少し戻って、作成したXPathリクエストをより詳細に検討してみましょう。
- '* // h1' DOM h1を調べます。 * // DOM全体の検索を意味する
- * // p [@ id = "pageDescription"] / a / abbr abbr要素とのリンクがあるpc id pageDescription要素を探している
- * // blockquote [@ class = "messageText ugc baseHtml"]クラスmessageText ugc baseHtmlの引用を探しています
解析を完了するためのメソッドを作成します(まったく必要ない場合がありますが、データ解析が完了し、すべてのデータが受信されたかどうかがより明確に表示されます)、および受信したデータにアクセスするためのメソッド
public function endParse() { if (isset($this->_content, $this->_timestamp, $this->_content)) { Yii::info(Yii::t('app', 'End parse')); } else { Yii::info(Yii::t('app', 'Some data were not received')); } return $this; } public function getTitle() { return $this->_title; } public function getTimestamp() { return $this->_timestamp; } public function getContent() { return $this->_content; }
出力結果
コンポーネントは準備ができていると言えます。必要なアクションをコントローラーのアクションに追加することで、その動作を確認し、その出力を表示できます。
$urlThread = 'http://9af5766eb2759a49.demo-xenforo.com/130/index.php?threads/some-thread.1/'; $dataParse = Yii::$app->parser ->loadUsingCurl($urlThread) ->createDomDocument() ->createDomXpath() ->parseTitle() ->parseTimeStamp() ->parseContent() ->endParse(); return $this->render('index', ['data' => $dataParse]);
<?php $this->title = 'My Yii Application'; ?> <div class="site-index"> <h1><?= $data->title; ?></h1> <p>Created At: <?= date('Ymd H:i:s', $data->timestamp); ?></p> <p><?= $data->content; ?></p> </div>
結果は同様の結果です。
おわりに
この記事では、XenForoフォーラムのトピックを解析する例を使用して、ページコンテンツパーサーをYiiのコンポーネントとして作成する方法を検討しました。
同様に、他のデータの構文解析を行うことも、原則としてすべてのフォーラムトピックなど、構文解析用に作成したクラスを使用する少し異なるクラスを作成することもできます。
- ページ区切りがある場合は取得します。
- トピックへのリンクを受け取るページを調べ、何らかの中間ストレージに書き込みます
- これらのリンクからコンテンツを取得します。
この記事では理論的な側面については触れませんでしたが、この記事は、それほどリアルではないが単純な例でページデータを取得する方法を示すことを目的としています。
サンプルコードへのリンクはリソースにあります。
資源
Yii2ミニマルの説明Yii2ドキュメントロシア語のXpath 1.0仕様ソースコードリポジトリ