Страница 1 из 9

[Скрипт] Алиса и Википедия

Добавлено: Ср июл 08, 2015 12:46 pm
Mescaline
Доброго времени суток!
В одном из видео наблюдал, как Алиса во время демонстрации говорила "я могу дать ответы на вопросы "что такое" или "кто такой"", но на форуме не нашел (хотя, по правде говоря, не очень-то искал) примеров реализации. Поэтому понемногу решил разобраться сам.
Беглый поиск в гугле дал две интересные (на первый взгляд) ссылки:
http://www.ibm.com/developerworks/ru/li ... wikipedia/
http://habrahabr.ru/post/104480/
Но в первой статье используется отдельный фреймворк, поэтому с моим скудными знаниями не стал даже пытаться, а на Хабре статья датирована 2010 годом. В целом она дает общие представления, но там есть два нюанса:
1) С тех пор Википедия перешла на https
2) Параметр inprop больше не существует в текущей версии MediaWiki

Полный актуальный список запросов и параметров можно найти здесь.
Если коротко о главном: нам нужен запрос action с параметром opensearch, сам запрос поиска search, параметром которого является наше слово и format, параметром которого будет (в моем случае) xml.

В итоге запрос выглядит так:

Код: Выделить всё

'https://ru.wikipedia.org/w/api.php?action=opensearch&search='.$request.'&format=xml' 
, где $request - наш запрос.
$request мы перехватываем стандартно $request = $matches[1];

У себя я создал шаблон поведения вида "что такое (.+)". Код:

Код: Выделить всё

//устанавливаем кодировки
header("Content-type: text/html;charset=utf-8");
mb_internal_encoding("UTF-8");

$word = $request = $matches[1]; //получаем искомое слово
$space_replace = preg_match_all("#\s#isu", $request, $s); //проверяем, есть ли пробелы в запросе
if ($space_replace === 1) { //если есть
    $request = preg_replace("#\s#", '_', $request); //меняем их на _
}

$url = 'https://ru.wikipedia.org/w/api.php?action=opensearch&search='.$request.'&format=xml'; //формируем запрос
$ch = curl_init(); //инициируем curl
curl_setopt($ch, CURLOPT_URL, $url); //передаем url
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); //возвращаем результат в виде строки
curl_setopt($ch, CURLOPT_USERAGENT, 'MyBot/1.0 (http://www.mysite.com/)'); //имитируем браузер
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //отключаем проверку ssl-сертификата узла

$result = curl_exec($ch); //выполяем curl

$data = fopen('test.xml', 'w'); //открываем файл для записи
fputs($data, $result); //записываем результат выполнения
fclose($data); //закрываем
$file = 'test.xml'; //указываем файл
$data_xml = simplexml_load_file($file); //загружаем его и раскладываем на массив
$text = $text1 = $data_xml->Section[0]->Item[0]->Text[0]; //получаем первый найденный вариант
$description = $data_xml->Section[0]->Item[0]->Description[0]; //получаем определение слова 
$description = mb_convert_encoding($description, 'UTF-8', 'UTF-8'); //конвертируем utf-8 без bom в простой utf-8
if (empty($description)) { //если ничего не нашлось
    say ("Я не знаю такого слова.");
} elseif (!empty($description)) { //иначе
    $text = preg_replace("#ё#", 'е', $text); //меняем "ё" на "е"
    $text = mb_strtolower($text, 'utf-8'); //меняем регистр всех букв на нижний
        if ($word === $text) { //если первый результат равен введенному слову
            function utf8_str_split($str) { 
            // переводим каждый символ в массив строк
            $split=1; 
            $array = array(); 
           for ( $i=0; $i < strlen( $str ); ){  
            $value = ord($str[$i]); //возвращаем ASCII-код сиволов и проверяем их на корректность
            if($value > 127){ 
                if($value >= 192 && $value <= 223) 
                    $split=2; 
                elseif($value >= 224 && $value <= 239) 
                    $split=3; 
                elseif($value >= 240 && $value <= 247) 
                    $split=4; 
                }else{ 
                $split=1; 
                } 
            $key = NULL; 
            for ( $j = 0; $j < $split; $j++, $i++ ) { 
                $key .= $str[$i]; 
            } 
            array_push( $array, $key ); 
            } 
        return $array; 
           } 
/** 
 * Функция вырезки
 * @param <string> $str 
 * @return <string> 
 */ 
            function clearstr($str){ 
                $sru = 'ёйцукенгшщзхъфывапролджэячсмитьбю'; 
                $s1 = array_merge(utf8_str_split($sru), utf8_str_split(strtoupper($sru)), range('A', 'Z'), range('a','z'), range('0', '9'), array('&',' ','#',';','%','?',':','(',')','-','_','=','+','[',']',',','.','/','\\')); 
                $codes = array(); 
                for ($i=0; $i<count($s1); $i++){ 
                    $codes[] = ord($s1[$i]); 
                } 
                $str_s = utf8_str_split($str); 
                for ($i=0; $i<count($str_s); $i++){ 
                    if (!in_array(ord($str_s[$i]), $codes)){ 
                            $str = str_replace($str_s[$i], '', $str); 
                    } 
                } 
             return $str; 
            } 

        $res = clearstr($description); //удаляем спецсимволы
        $res = preg_replace("#ё#isu", 'е', $res); //меняем ё на е
        //делаем первую букву большой
        $first = mb_substr($word, 0,1, 'utf-8'); //возвращаем 1 букву искомой фразы
        $last = mb_substr($word,1); //возвращаем остальное
        $first = mb_strtoupper($first, 'utf-8'); //переводим первую букву в верхний регистр
        $last = mb_strtolower($last, "utf-8"); //все остальное - в нижний
        $word = $first.$last; //склеиваем

        $rep = $word .", это "; //искомое слово + фраза замены

        $tracking = preg_replace("#".$word."#isum", $rep, $res,1); //заменяем первое слово определения нашим словом поиска и замены
        $tracking = preg_replace("#\s\(.*?\)#isu", '', $tracking); //удаляем все остальные круглые скобки и их содержимое

        say($tracking);
            } elseif ($word != $text){ //если искомое слово не найдено в первом варианте
                $text2 = $data_xml->Section[0]->Item[1]->Text[0]; //получаем второе совпадение
                $text3 = $data_xml->Section[0]->Item[2]->Text[0]; //получаем третье совпадение
                $say = "По Вашему запросу совпадений не найдено. Похожие результаты: ";
                //$say.=$text1 .", " .$text2 ." и " .$text3 .".";
                $say.=$text1; //первый похожий резальтат
                if ($text2 ==='') { //если во втором результате пусто
                    $say.="."; //ставим точку
                } else { //иначе
                    $say.= ", " .$text2; //ставим запятую и дописываем второй вариант
                        if ($text3 === '') { //если в третьем результате пусто
                            $say.="."; //ставим точку
                        } else { //иначе
                            $say.=" и " .$text3; //ставим запятую и дописываем третий вариант
                        }
                }
                say($say);
            }
} 
В принципе я подписал все, что можно, но на всякий случай поясню принцип:
1) Перехватываем запрос
2) Проверяем, есть ли в запросе пробелы (если есть, меняем их на _). Это нужно для корректного формирования ссылки запроса
3) Инициируем cURL, устанавливаем две важные опции, без которых запрос по https не пройдет: USERAGENT и SSL_VERIFYPEER
4) Выполняем запрос, сохраняем результат локально в файл test.xml
5) Загружаем полученный xml и раскладываем его на массив
У меня не получилось заставить работать напрямую с результатом запроса, даже через new SimpleXMLElement, может я просто неправильно что-то делал, буду рад, если кто-то предложит менее "топорный" вариант обработки
6) Из всего xml получаем первый найденный результат и определение слова, конвертируем. У меня без конвертации символы отображались не корректно, notepad++ детектировал кодировку UTF-8 without BOM, поэтому возвращаем нормальный utf-8
7) Далее проверяем, есть ли вообще в xml хотя бы 1 строка Description. Если ее нет, значит в Википедии нет искомого слова, а значит Алиса говорит "Я не знаю такого слова"
8) Если есть:
- заменяем все "ё" на "е" в значении поля Text (нужно для сравнения введенного нами запроса и находящегося в xml)
- переводим слово из поля Text в нижний регистр (также для сравнения)
- сравниваем слово из нашего запроса и возвращенным значением Text
- если они идентичны, значит поиск удался и запускаются функции удаления спецсимволов
Все дело в том, что в Википедии используются специальные символы повсеместно - начиная от букв с ударениями и заканчивая дефисами. Код функций был взят отсюда, за что автору большое спасибо. Цикл возвращает ASCII-код каждого символа и сравнивает полученное значение, после чего удаляет все, что попадает под критерии поиска.
- меняем "ё" на "е" в определении искомого слова
- делаем первую букву нашего запроса большой (т.е., если Вы ввели в запрос "что такое берёза", на данном этапе слово "берёза" будет выглядеть как "Береза"). Это нужно для подстановки в ответ Алисы
- далее берем наше слово и подставляем после него ", это ". Так как дефис наши функции выше удалили как спецсимвол, нам нужно что-то, чтобы сделать эмоциональную паузу между словом и его определением. Я использую запятую и слово "это"
- заменяем первое слово определения (это слово, которое мы искали, с него начинается любое определение) нашей конструкцией
- удаляем все круглые скобки вместе с их содержимым (часто в скобках на Википедии пишут различные сокращения, формулы и прочий текст, который Алиса без нашей помощи корректно интерпретировать не сможет, поэтому я просто удаляю все в них)
- говорим получившуюся фразу
Приведу пример. Исходный запрос "что такое береза" возвращает (в "сыром" виде) вот такое определение:
Берёза (лат. Betula) — род листопадных деревьев и кустарников семейства Берёзовые (Betulaceae). Берёза широко распространена в Северном полушарии; на территории России принадлежит к числу наиболее распространённых древесных пород.
Благодаря обработке Алиса скажет:
"Береза, это род листопадных деревьев и кустарников семейства Березовые. Береза широко распространена в Северном полушарии; на территории России принадлежит к числу наиболее распространенных древесных пород."
- если искомое слово не найдено, получаем значение второго и третьего найденного результата
- если во втором результате пусто (т.е. найдено только 1 совпадение с искомым словом), то к фразе прибавляется значение первого Text, иначе после первого ставится запятая и дописывается второе
- аналогично с третьим результатом
В итоге, при частичном совпадении, Алиса выдаст 3 результата, похожих на наш запрос. Можно добавить и больше, на вкус и цвет :)
Например, при запросе "что такое кокос", Алиса скажет
"По Вашему запросу совпадений не найдено. Похожие результаты: Кокосовая пальма, Кокосовые острова и Кокосовое масло"

Вот в общем-то и все. Из того, что хочется реализовать еще:
1) Обработка запроса "кто такой (.+)". Алгоритм там будет отличаться из-за большого количества вариаций совпадений (например, по запросу "кто такой аль пачино", Алиса ничего не найдет, если применить этот алгоритм, из-за того, что Википедия возвращает в Text результат "Пачино, Аль" и строка уже не будет === введенному запросу, соответственно, условие поиска совпадения должно быть другим.
2) Обработка текста на предмет "трудных" для Алисы слов. Например, если в запросе будут римские цифры, Алиса (естественно) прочитает их не правильно, а учитывая, что после них идет какое-то слово, нужно еще и склонить это число в нужной форме.

Как обычно, поправки и советы приветствуются :)

Re: [Скрипт] Алиса и Википедия

Добавлено: Ср июл 08, 2015 2:03 pm
skysilver
А вот эту ветку не читали? http://majordomo.smartliving.ru/forum/v ... =30#p13786
Вроде как, все уже давно работает. :)

Re: [Скрипт] Алиса и Википедия

Добавлено: Ср июл 08, 2015 3:19 pm
Mescaline
skysilver писал(а):А вот эту ветку не читали? http://majordomo.smartliving.ru/forum/v ... =30#p13786
Вроде как, все уже давно работает. :)
Читал, настроил, 3-4 раза спросил и все, теперь не хочет отвечать, может я что-то намудрил. Ну, в любом случае, если тема не актуальна, просто удалят, да и все :)

Re: [Скрипт] Алиса и Википедия

Добавлено: Чт июл 09, 2015 12:01 pm
devoff
Тема хорошая, конечно нужно ее в отдельную тему вывести. Возможности весь форум перелопачивать нет, а что бы поиском, надо знать, что искать.

Re: [Скрипт] Алиса и Википедия

Добавлено: Пт авг 21, 2015 11:56 am
sega6549
офгенно) вот толо проблема у меня в том что алиса проговаривает а просто в чат пишет если что то нашлп или не нашла, подскажите как поправить на основе шаблона поведения, я предпологаю что где то надо поставить цыферку 1 у меня такое было по шаблону повтори

Re: [Скрипт] Алиса и Википедия

Добавлено: Пт авг 21, 2015 12:10 pm
sega6549
о сам допер)
//устанавливаем кодировки
header("Content-type: text/html;charset=utf-8");
mb_internal_encoding("UTF-8");

$word = $request = $matches[1]; //получаем искомое слово
$space_replace = preg_match_all("#\s#isu", $request, $s); //проверяем, есть ли пробелы в запросе
if ($space_replace === 1) { //если есть
$request = preg_replace("#\s#", '_', $request); //меняем их на _
}

$url = 'https://ru.wikipedia.org/w/api.php?acti ... format=xml'; //формируем запрос
$ch = curl_init(); //инициируем curl
curl_setopt($ch, CURLOPT_URL, $url); //передаем url
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); //возвращаем результат в виде строки
curl_setopt($ch, CURLOPT_USERAGENT, 'MyBot/1.0 (http://www.mysite.com/)'); //имитируем браузер
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //отключаем проверку ssl-сертификата узла

$result = curl_exec($ch); //выполяем curl

$data = fopen('test.xml', 'w'); //открываем файл для записи
fputs($data, $result); //записываем результат выполнения
fclose($data); //закрываем
$file = 'test.xml'; //указываем файл
$data_xml = simplexml_load_file($file); //загружаем его и раскладываем на массив
$text = $text1 = $data_xml->Section[0]->Item[0]->Text[0]; //получаем первый найденный вариант
$description = $data_xml->Section[0]->Item[0]->Description[0]; //получаем определение слова
$description = mb_convert_encoding($description, 'UTF-8', 'UTF-8'); //конвертируем utf-8 без bom в простой utf-8
if (empty($description)) { //если ничего не нашлось
say ("Я не знаю такого слова",1);
} elseif (!empty($description)) { //иначе
$text = preg_replace("#ё#", 'е', $text); //меняем "ё" на "е"
$text = mb_strtolower($text, 'utf-8'); //меняем регистр всех букв на нижний
if ($word === $text) { //если первый результат равен введенному слову
function utf8_str_split($str) {
// переводим каждый символ в массив строк
$split=1;
$array = array();
for ( $i=0; $i < strlen( $str ); ){
$value = ord($str[$i]); //возвращаем ASCII-код сиволов и проверяем их на корректность
if($value > 127){
if($value >= 192 && $value <= 223)
$split=2;
elseif($value >= 224 && $value <= 239)
$split=3;
elseif($value >= 240 && $value <= 247)
$split=4;
}else{
$split=1;
}
$key = NULL;
for ( $j = 0; $j < $split; $j++, $i++ ) {
$key .= $str[$i];
}
array_push( $array, $key );
}
return $array;
}
/**
* Функция вырезки
* @param <string> $str
* @return <string>
*/
function clearstr($str){
$sru = 'ёйцукенгшщзхъфывапролджэячсмитьбю';
$s1 = array_merge(utf8_str_split($sru), utf8_str_split(strtoupper($sru)), range('A', 'Z'), range('a','z'), range('0', '9'), array('&',' ','#',';','%','?',':','(',')','-','_','=','+','[',']',',','.','/','\\'));
$codes = array();
for ($i=0; $i<count($s1); $i++){
$codes[] = ord($s1[$i]);
}
$str_s = utf8_str_split($str);
for ($i=0; $i<count($str_s); $i++){
if (!in_array(ord($str_s[$i]), $codes)){
$str = str_replace($str_s[$i], '', $str);
}
}
return $str;
}

$res = clearstr($description); //удаляем спецсимволы
$res = preg_replace("#ё#isu", 'е', $res); //меняем ё на е
//делаем первую букву большой
$first = mb_substr($word, 0,1, 'utf-8'); //возвращаем 1 букву искомой фразы
$last = mb_substr($word,1); //возвращаем остальное
$first = mb_strtoupper($first, 'utf-8'); //переводим первую букву в верхний регистр
$last = mb_strtolower($last, "utf-8"); //все остальное - в нижний
$word = $first.$last; //склеиваем

$rep = $word .", это "; //искомое слово + фраза замены

$tracking = preg_replace("#".$word."#isum", $rep, $res,1); //заменяем первое слово определения нашим словом поиска и замены
$tracking = preg_replace("#\s\(.*?\)#isu", '', $tracking); //удаляем все остальные круглые скобки и их содержимое

say($tracking,1);
} elseif ($word != $text){ //если искомое слово не найдено в первом варианте
$text2 = $data_xml->Section[0]->Item[1]->Text[0]; //получаем второе совпадение
$text3 = $data_xml->Section[0]->Item[2]->Text[0]; //получаем третье совпадение
$say = "По Вашему запросу совпадений не найдено. Похожие результаты: ";
//$say.=$text1 .", " .$text2 ." и " .$text3 .".";
$say.=$text1; //первый похожий резальтат
if ($text2 ==='') { //если во втором результате пусто
$say.="."; //ставим точку
} else { //иначе
$say.= ", " .$text2; //ставим запятую и дописываем второй вариант
if ($text3 === '') { //если в третьем результате пусто
$say.="."; //ставим точку
} else { //иначе
$say.=" и " .$text3; //ставим запятую и дописываем третий вариант
}
}
say($say,1);
}
}
поставил цыфры 1 во всех say(текст) и в итоге проговаривает все)

Re: [Скрипт] Алиса и Википедия

Добавлено: Пт авг 21, 2015 4:54 pm
Ivan
А зачем всё это. Алиса в облаке сама это всё делает

Re: [Скрипт] Алиса и Википедия

Добавлено: Вт авг 25, 2015 4:57 pm
Mescaline
Ivan писал(а):А зачем всё это. Алиса в облаке сама это всё делает
Изучать что-либо лично мне намного проще, когда есть цель что-то сделать. Вот и сделал скрипт, заодно немного прокачал скилл, так сказать :)
Тем более, что, как я написал выше, у меня эта функция перестала работать.
Здорово, что Алиса в облаке это сама делает, но у этого есть свои минусы и плюсы.

Re: [Скрипт] Алиса и Википедия

Добавлено: Вт авг 25, 2015 8:36 pm
AirKing
Облако говорите? А где увидеть список команд?

Re: [Скрипт] Алиса и Википедия

Добавлено: Вт авг 25, 2015 10:14 pm
Ivan