Парсим WikiPedia — пособие для новичков
В одном из своих прошлых постов я рассказывал про инструменты парсельщика. Сейчас я хочу показать это все на простом примере — парсинге 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_года
то страница без проблем спарсится. В чем тут косяк, и куда копать — неизвестно. Возможно, из-за того, что википедия большая, она сидит на куче серваков, и их настройки могут отличаться. Поэтому, имеет смысл попробовать спарсить с помощью сокетов и отправив все необходимые заголовки для имитации браузера. Я не стал проверять, лениво. :)
Итак, страница спарсена. Теперь надо найти блоки повторяющихся элементов для создания паттерна. Возьмем последний пример и посмотрим код (кликабельно):
Тот кусок кода, что я выделил — и есть отдельный блок, который повторяется для каждого эпизода. Он и будет нашим паттерном. Если заменить все изменяющиеся символы на их диапазоны, получим готовый паттерн:
<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 и запихать результаты в БД.
Вот собсна и все :)