В одном из своих прошлых постов я рассказывал про инструменты парсельщика. Сейчас я хочу показать это все на простом примере — парсинге WikiPedia. Это один из самых простых вариантов парсинга, но тем, кто никогда этим не занимался, возможно, будет полезно прочесть этот пост.

В парсинге Wikipedia есть два небольших нюанса. Первый — урлы в ней кириллические (мы есс-но говорим про русскую Вики). Второй — там есть всякие хитрые редиректы. Но, обо всем по-порядку.

Сначала мы пытаемся заполучить всю страницу в переменную. Что-то вроде:

$html = file_get_contents('http://ru.wikipedia.org/wiki/Парсинг');
echo $html;

Но при попытке это сделать мы получаем хуй. В виде 403 ошибки. Смотрим, какие заголовки посылаются браузером серверу:

GET /wiki/%D0%9F%D0%B0%D1%80%D1%81%D0%B8%D0%BD%D0%B3 HTTP/1.1

Ага, браузер посылает закодированный урл, т.к. передача кирилицы в заголовках GET не предусмотрена. Конвертнем нашу русскоязычную часть урла в шестнадцатиричный вид, используя ф-ю urlencode:

$ru_url = urlencode ('Парсинг');
$html = file_get_contents('http://ru.wikipedia.org/wiki/'.$ru_url);
echo $html;

Этим мы отработали первый нюанс. Запускаем скрипт — и снова хуй! Почему? А дело в том, что текст скрипта должен быть набран в utf8 кодировке. То есть, слово «Парсинг» должно быть в utf8, тогда преобразование будет правильным. Поменяли кодировку скрипта — и скрипт заработал! Ура!

Тут следует напомнить про второй нюанс. Я, если честно, так в нем до конца и не разобрался. Дело в том, что при использовании в кириллических урлах кавычек типа елочки (») парсятся не все страницы. Скажем, если мы захотим спарсить эту страницу:

http://ru.wikipedia.org/wiki/Список_эпизодов_мультсериала_«Войны_клонов»_2003_года

то нихуя не получится. Во всяком случае, у меня не получилось. Однако, если попробовать спарсить такой:

http://ru.wikipedia.org/wiki/Список_эпизодов_мультсериала_«Войны_клонов»_2008_года

то страница без проблем спарсится. В чем тут косяк, и куда копать — неизвестно. Возможно, из-за того, что википедия большая, она сидит на куче серваков, и их настройки могут отличаться. Поэтому, имеет смысл попробовать спарсить с помощью сокетов и отправив все необходимые заголовки для имитации браузера. Я не стал проверять, лениво. :)

Итак, страница спарсена. Теперь надо найти блоки повторяющихся элементов для создания паттерна. Возьмем последний пример и посмотрим код (кликабельно):
wiki
Тот кусок кода, что я выделил — и есть отдельный блок, который повторяется для каждого эпизода. Он и будет нашим паттерном. Если заменить все изменяющиеся символы на их диапазоны, получим готовый паттерн:

<tr bgcolor="#F2F2F2">
<td style="background-color: #BDCEF7;">[0-9]x[0-9]{2}</td>
<td style="background-color: #BDCEF7;"><i>(.*)</i> / (.*)</td>
</tr>
<tr>
<td colspan="2">(.*)</td>
</tr>
<tr>
<td colspan="2">Девиз серии: (.*)</td>
</tr>

Как его юзать и проверить, что он работает? Легко, пользуемся ф-ей preg_match_all:

$ru_url = urlencode ('Список_эпизодов_мультсериала_«Войны_клонов»_2008_года');
$html = file_get_contents('http://ru.wikipedia.org/wiki/'.$ru_url);
$pattern = '|<tr bgcolor="#F2F2F2">
<td style="background-color: #BDCEF7;">[0-9]x[0-9]{2}</td>
<td style="background-color: #BDCEF7;"><i>(.*)</i> / (.*)</td>
</tr>
<tr>
<td colspan="2">(.*)</td>
</tr>
<tr>
<td colspan="2">Девиз серии: (.*)</td>
</tr>|i';
preg_match_all ($pattern, $html, $res, PREG_SET_ORDER);
echo count($res);

Этот кусок кода — почти готовый скрипт парсинга. Выводит количество спарсенных единиц. В нашем случае — кол-во эпизодов. Если оно меньше, чем на странице Википедии — значит, где-то мы скосячили в паттерне. Если присмотреться, то да, косяк в цвете бэкграунда, он меняется в зависимости от сезона. Пропишем более универсальное:

<td style="background-color: #[A-Z0-9]{6};">[0-9]x[0-9]{2}</td>
<td style="background-color: #[A-Z0-9]{6};"><i>(.*)</i> / (.*)</td>

Тут может возникнуть вопрос — почему мы везде не делаем такие паттерны: (.*) Отвечаю — может попасть много чего лишнего туда, да и работает оно намного медленнее. И спасает от ошибок в будущем, т.к. и в википедии есть ошибки написания, которые потом могут боком встать при вставке инфы в БД.

Рекомендую хорошо почитать описание по ф-ции preg_match_all, почему используется ключ PREG_SET_ORDER и почему паттерн взят в ограничители || с i в конце. Чтобы посмотреть на то, что мы спарсили, достаточно заменить вывод:

echo count($res);

на:

print_r($res);

Ну а чтобы сохранить то, что мы спарсили, надо пройтись циклом по массиву $res и запихать результаты в БД.

Вот собсна и все :)