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

Не требует установки программ или изменения файлов

Модераторы: immortal, newz20

Mescaline
Сообщения: 29
Зарегистрирован: Вт июн 09, 2015 6:03 pm
Благодарил (а): 4 раза
Поблагодарили: 15 раз

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

Сообщение Mescaline » Ср июл 08, 2015 12:46 pm

Доброго времени суток!
В одном из видео наблюдал, как Алиса во время демонстрации говорила "я могу дать ответы на вопросы "что такое" или "кто такой"", но на форуме не нашел (хотя, по правде говоря, не очень-то искал) примеров реализации. Поэтому понемногу решил разобраться сам.
Беглый поиск в гугле дал две интересные (на первый взгляд) ссылки:
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) Обработка текста на предмет "трудных" для Алисы слов. Например, если в запросе будут римские цифры, Алиса (естественно) прочитает их не правильно, а учитывая, что после них идет какое-то слово, нужно еще и склонить это число в нужной форме.

Как обычно, поправки и советы приветствуются :)
За это сообщение автора Mescaline поблагодарили (всего 10):
devoff (Чт июл 09, 2015 12:02 pm) • sega6549 (Пт авг 21, 2015 12:11 pm) • AirKing (Вт авг 25, 2015 8:37 pm) • mirsum (Сб сен 05, 2015 10:28 pm) • igoryan (Вт сен 15, 2015 8:38 pm) • th1s (Вс май 21, 2017 9:32 pm) • VladPTZ (Пт фев 02, 2018 10:29 am) • TrDA (Чт апр 12, 2018 12:57 pm) • Svetлая (Пн май 07, 2018 12:55 am) • antpino (Пт май 25, 2018 6:57 am)
Рейтинг: 11.63%
skysilver
Сообщения: 3006
Зарегистрирован: Чт авг 21, 2014 8:28 am
Откуда: Киров, Россия
Благодарил (а): 400 раз
Поблагодарили: 1753 раза
Контактная информация:

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

Сообщение skysilver » Ср июл 08, 2015 2:03 pm

А вот эту ветку не читали? http://majordomo.smartliving.ru/forum/v ... =30#p13786
Вроде как, все уже давно работает. :)
MajorDoMo (GitHub) на Cubietruck. ОС Debian 7 (wheezy) (kernel 3.4.105) с переносом на HDD.
Мой CONNECT | Блоги | Telegram
Mescaline
Сообщения: 29
Зарегистрирован: Вт июн 09, 2015 6:03 pm
Благодарил (а): 4 раза
Поблагодарили: 15 раз

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

Сообщение Mescaline » Ср июл 08, 2015 3:19 pm

skysilver писал(а):А вот эту ветку не читали? http://majordomo.smartliving.ru/forum/v ... =30#p13786
Вроде как, все уже давно работает. :)
Читал, настроил, 3-4 раза спросил и все, теперь не хочет отвечать, может я что-то намудрил. Ну, в любом случае, если тема не актуальна, просто удалят, да и все :)
devoff
Сообщения: 181
Зарегистрирован: Пт апр 04, 2014 10:17 pm
Благодарил (а): 37 раз
Поблагодарили: 54 раза

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

Сообщение devoff » Чт июл 09, 2015 12:01 pm

Тема хорошая, конечно нужно ее в отдельную тему вывести. Возможности весь форум перелопачивать нет, а что бы поиском, надо знать, что искать.
sega6549
Сообщения: 530
Зарегистрирован: Чт авг 20, 2015 11:32 am
Откуда: Ярославль
Благодарил (а): 61 раз
Поблагодарили: 75 раз
Контактная информация:

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

Сообщение sega6549 » Пт авг 21, 2015 11:56 am

офгенно) вот толо проблема у меня в том что алиса проговаривает а просто в чат пишет если что то нашлп или не нашла, подскажите как поправить на основе шаблона поведения, я предпологаю что где то надо поставить цыферку 1 у меня такое было по шаблону повтори
Тут можно задать вопрос по MajorDoMo VK
Канал по MajorDoMo youtube
sega6549
Сообщения: 530
Зарегистрирован: Чт авг 20, 2015 11:32 am
Откуда: Ярославль
Благодарил (а): 61 раз
Поблагодарили: 75 раз
Контактная информация:

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

Сообщение sega6549 » Пт авг 21, 2015 12:10 pm

о сам допер)
//устанавливаем кодировки
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(текст) и в итоге проговаривает все)
Тут можно задать вопрос по MajorDoMo VK
Канал по MajorDoMo youtube
Ivan
Сообщения: 1473
Зарегистрирован: Сб окт 12, 2013 11:03 pm
Благодарил (а): 49 раз
Поблагодарили: 327 раз

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

Сообщение Ivan » Пт авг 21, 2015 4:54 pm

А зачем всё это. Алиса в облаке сама это всё делает
Linux, Raspberry PI, MySensors
Connect: http://connect.smartliving.ru/profile/53
Мои проекты: http://smartliving.ru/profile/4
Mescaline
Сообщения: 29
Зарегистрирован: Вт июн 09, 2015 6:03 pm
Благодарил (а): 4 раза
Поблагодарили: 15 раз

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

Сообщение Mescaline » Вт авг 25, 2015 4:57 pm

Ivan писал(а):А зачем всё это. Алиса в облаке сама это всё делает
Изучать что-либо лично мне намного проще, когда есть цель что-то сделать. Вот и сделал скрипт, заодно немного прокачал скилл, так сказать :)
Тем более, что, как я написал выше, у меня эта функция перестала работать.
Здорово, что Алиса в облаке это сама делает, но у этого есть свои минусы и плюсы.
AirKing
Сообщения: 87
Зарегистрирован: Вс фев 01, 2015 10:43 pm
Откуда: Муром
Благодарил (а): 16 раз
Поблагодарили: 7 раз

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

Сообщение AirKing » Вт авг 25, 2015 8:36 pm

Облако говорите? А где увидеть список команд?
Получи Cashback до 18% с покупок
Ivan
Сообщения: 1473
Зарегистрирован: Сб окт 12, 2013 11:03 pm
Благодарил (а): 49 раз
Поблагодарили: 327 раз

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

Сообщение Ivan » Вт авг 25, 2015 10:14 pm

Linux, Raspberry PI, MySensors
Connect: http://connect.smartliving.ru/profile/53
Мои проекты: http://smartliving.ru/profile/4
Ответить