Синонимы на php

При написании одной доски объявлений, заказчик, как водиться, попросил написать парсер объявлений с известных досок. 

На современном уровне развития антиспам систем, сграбить новые объявления не так и просто с задачей я справился. Пришлось применить парочку хитрых приемов с COM объектом Интернет Эксплорера. Но сейчас не про это.

Грош цена такому тексту для поисковых систем. Ведь текст не уникальный, а значит надо сделать так, чтобы он стал уникальным.

Простейшая замена одного слоа на другое по базе не составит труда для php. Ведь php это текстовый язык программирования, созданный для текстовых вычислений и преобразований. Грубо говоря php это просто шаблонизатор. Но я опять отвлекся. 

В php есть множество функций для замены одного слова на другое. Рассмотрим несколько из них. 

Функция, которая первой приходит в голову это strtr 

<?php
$a = strtr( 'Мобильный телефон Samsung обменяю на новый iphone' ,array(
   'Мобилный телефон'=>'Мобильник',
   'Samsung'=>'Самсунг', 'новый'=>'новенький','обменяю'=>'поменяю',
   'Iphone'=>'Айфон'));
// Мобильник Самсунг поменяю на новенький Айфорн

удобно, но не совсем. Функция чувствительна к регистру. Значит Samsung и samsung это разные слова. 

По опыту скажу, что доски объявлений это то еще сборище афоризмов и цитат, что только там не пишут, и грамотностью разумеется не блещут. Те одноклассники или вконтакте отдыхают по сравнению с досками объявлений. Почитайте сами, очень интересно http://statuso.ru/bezkategorii/ 

Но я вновь отвлекся. 

У большинства текстовых функций php, есть регистронезависимый аналог с буквой i по середине, обычно он не дружит с utf-8. Но у strtr его нет.

Пожалуй единственная функция, которая корректно работает с utf-8 и регистронезависема это preg_replace

Разумеется, нужно указывать верные флаги iu — i — регистр неважен, u — работаем с utf-8

Перепишем верхний код  

<?php
$a = preg_replace(
  array('#мобилный телефон#ui','#samsung#ui', '#новый#ui','#Обменяю#ui','#Iphone#ui'),
  array('Мобильник','Самсунг','новенький','поменяю','Айфон'),
'Мобильный телефон Samsung обменяю на новый iphone');
// Мобильник Самсунг поменяю на новенький Айфорн

Уже более приемлемо. Но, вот хранить массив ключей в таком виде не очень удобно. 

Поэтому я написал вот эту функцию

function _strtr($str,$repl_array){
  $keys = array_map(function($key){
                  return '#'.$key.'#ui';
               },array_keys($repl_array)); 
  $values = array_values($repl_array);
  return preg_replace($keys,$values,$str);
}
echo _strtr('Мобильный телефон Samsung обменяю на новый iphone',
  array(
     'мобильный телефон'=>'Мобильник',
     'samsung'=>'Самсунг', 'новый'=>'новенький','обменяю'=>'поменяю',
     'Iphone'=>'Айфон'
   )
);

Статья была бы не полной, без рандомизатора. Текст должен получаться разным.

function _strtr($str,$repl_array){
  $keys = array_map(function($key){
                  return '#'.$key.'#ui';
               },array_keys($repl_array)); 
  $values = array_values($repl_array);
  $values = array_map(function($value){
                  $variants = explode('|', $value);
                  return $variants[rand(0, count($variants) - 1)];
               },array_values($repl_array));
  return preg_replace($keys,$values,$str);
}
echo _strtr('Мобильный телефон Samsung обменяю на новый iphone',
  array(
     'мобильный телефон'=>'Мобильник|Фага|Телефон|Сотовый',
     'samsung'=>'Самсунг|LG|Nokia', 'новый'=>'новенький|обновленный|хороший','обменяю'=>'поменяю|дам в размен',
     'Iphone'=>'Айфон|IP|Nokia 3210'
   )
);

Результаты

Фага LG дам в размен на хороший IP
Телефон Самсунг поменяю на новенький IP
Телефон Самсунг дам в размен на обновленный Nokia 3210

Вот так и творится история ;) Разнообразие текстов зависит только от Вашей фантазии и размера базы синонимов

UPD Рандомные ключи

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

Однако у нас учебные цели, поэтому будем вновь работать с массивом объектов. А о том как подключить это дело к базе, предлагаю обсудить в комментариях.

Итак, что мы хотели бы получить от нашей функции:

echo _strtr('Мобильный телефон Samsung обменяю на новый iphone',
  array(
     'мобильный телефон|Сотовый телефон|мобилка|Мобильник|Фага|Телефон|Сотовый',
     'samsung|Самсунг|LG|Nokia',
     'новый|новенький|обновленный|хороший',
     'обменяю|отдам|разменяю|поменяю|дам в размен',
     'Iphone|Айфон|Айфон|IP|Nokia 3210'
   )
);

Т.е. проходим по «базе» синонимов, если находим совпадение с одним из них, выбираем любой из оставшихся. Базу ключей отдельно содержать не надо.

Реализация

mb_internal_encoding("UTF-8");
function synonimize($str, $repl_array){
  $keys = array_map(function($key){
    return '#'.$key.'#ui';
  }, $repl_array); 
  
  foreach ($keys as $i=>$key) {
    $str = preg_replace_callback($key, function ($match) use ($repl_array, $i) {
      $syns = explode('|', $repl_array[$i]);
      array_splice($syns, array_search(mb_strtolower($match[0]), array_map('mb_strtolower', $syns)), 1);
      return $syns[rand(0, count($syns) - 1)];
    }, $str);
  }
  
  return $str;
}
echo synonimize('Мобильный телефон Samsung обменяю на новый iphone',
  array(
     'Сотовый телефон|мобилка|Мобильник|Фага|Телефон|Сотовый|мобильный телефон',
     'samsung|Самсунг|LG|Nokia',
     'новый|новенький|обновленный|хороший',
     'обменяю|отдам|разменяю|поменяю|дам в размен',
     'Iphone|Айфон|IP|Nokia 3210'
   )
);

Я изменил название функции, так как к strtr оно уже не имеет никакого отношения.

В самом верху добавлена функция mb_internal_encoding, которая устанавливает кодировку по умолчанию UTF-8. Если у вас другая кодировка, то ее надо убрать. И в модификаторах, в регулярном выражении убрать u

Функция, как и раньше, генерирует массив регулярных выражений. Затем перебирает каждое из них (не лучший подход, ниже объясню почему). При нахождении совпадений, вызывается анонимная callback функция. В ней, исходный набор синонимов разбивается на массив и при помощи array_search и array_splice из него удаляется найденное выражение ($match[0]).

После чего, используя функцию rand, из оставшихся синонимов выдергиваем случайный, и затем заменяем на него, найденное выражение($match[0]), вернув его в конце анонимной функции.

Теперь немного про preg_replace_callback. Вообще, она умеет получать на вход массив из регулярок. Тогда необходимость ручного перебора бы отпала. Это существенно ускорило бы работу скрипта. Но я не придумал, как в callback функции узнать какая из регулярок сработала. Можно конечно прогнать исходный массив синонимов циклом и найти в нем нужный набор синонимов, используя $match[0]. Это было бы быстрее, чем перебирать весь массив в начале. Пища для размышления, если у вас несколько млн синонимов.

Пока же главное, что оно работает. Я проверил, и получил несколько прекрасных выражений

Сотовый телефон Nokia поменяю на хороший Nokia 3210
мобилка Nokia дам в размен на новенький Айфон
Фага Nokia отдам на новенький Айфон

Всем добра! Пишите вопросы в комментариях. На наиболее интересные вопросы буду отвечать или даже дополнять статью.

<?php /** * Class Synonymizer */ class Synonymizer { /** * Начало строки с вариациями * * @var string */ private $startSymbol; /** * Окончание строки с вариациями * * @var string */ private $endSymbol; /** * Разделитель * * @var string */ private $delimert; /** * Текст для обработки * * @var string */ private $text; /** * Сгенерированный текст * * @var string */ private $outputText; /** * Synonimizer constructor. * @param string $start * @param string $end * @param string $delimert */ public function __construct($start = ‘[‘, $end = ‘]’, $delimert = ‘|’) { // Символы, которые лучше экранировать $this->setStartSymbol($start); $this->setEndSymbol($end); $this->delimert = $delimert; } /** * Устанавливает значение начального символа в регулярном выражении * * @param $char */ public function setStartSymbol($char) { $this->startSymbol = $char; } /** * Возвращает символ, по которому начинается поиск для регулярного выражения * Если необходимо экранировать символы — экранирует их * * @return string */ private function getStartSymbolForRegexp() { return ( in_array($this->startSymbol, $this->getScreeningSymbols()) ) ? «\$this->startSymbol» : $this->startSymbol; } /** * Устанавливает значение начального символа в регулярном выражении * * @param $char */ public function setEndSymbol($char) { $this->endSymbol = $char; } /** * Возвращает символ, по которому начинается поиск для регулярного выражения * Если необходимо экранировать символы — экранирует их * * @return string */ private function getEndSymbolForRegexp() { return ( in_array($this->endSymbol, $this->getScreeningSymbols()) ) ? «\$this->endSymbol» : $this->endSymbol; } /** * Устанавливает значение текста, который будет обрабатываться скриптом * * @param $text */ public function setText($text) { $this->text = $text; } /** * Получает результирующий (сгенерированный текст) * * @return string */ public function getText() { return $this->synonimize(); } /** * Логика для генерации текста * * @return string */ private function synonimize() { // Выберем все вхождения вариантов по регулярному выражению // Все результаты будут в массиве $matches[0] (мы исспользуем 1 шаблон для поиска) preg_match_all($this->makeRegExp(), $this->text, $matches); // Подготовим текст для замены, отформатировав строку до того формата, который необходим функции vsprintf $this->outputText = preg_replace($this->makeRegExp(), ‘%s’, $this->text); // Генерируем текст для вывода $vspritnfParams = $this->getArrayWithValues($matches[0]); return vsprintf($this->outputText, $vspritnfParams); } /** * Возвращает набор сгенерированных значений * * @param $matches * @return array * @throws Exception */ private function getArrayWithValues($matches) { $data = array(); if ( is_array($matches) ) { foreach ($matches as $match) { $variantsString = str_replace( [$this->startSymbol, $this->endSymbol], [», »], $match); $variantsArray = explode($this->delimert, $variantsString); $data[] = $variantsArray[array_rand($variantsArray)]; } } else { throw new Exception(‘getRandomArray() require an array as it parametr’); } return $data; } /** * Вернет шаблон регулярного выражения, которое хратит в себе варианты с словами * * @return string */ private function makeRegExp() { $startSympolForRegexp = $this->getStartSymbolForRegexp(); $endSympolForRegexp = $this->getEndSymbolForRegexp(); $regexp = $startSympolForRegexp[^]]*«.$endSympolForRegexp; return «/$regexp/«; } /** * Символы, которые необходимо экранировать внутри регулярного выражения * * @return array */ private function getScreeningSymbols() { return array(‘[‘, ‘]’, ‘{‘, ‘}’); } }

Для начала нам неабходимо подобрать формат, в котором будут записаны слова.
Давайте остановимся на таком,

слово => синоним | синоним | синоним | …
слово1 => синоним | синоним
слово2 => @слово

так как для работы с ним уже был написан код для другого проекта.

@собачка указывает что синоним является алиасом для другого синонима.

Поиски в сети не предоставили мне удобоваримых форматов словарей, поэтому пришлось писать конвертор найденного в наш формат.
Сниппет словаря, который нужено сконвертить в наш формат

белок# || вытаращить арабские белки
Белый#, белоснежный, светлый, седой. Белее снега. См. чистый || профессор белой и черной магии, сделать белее снега, сказка о белом бычке, сказка про белого бычка
бельмес# || ни бельмеса, ни бельмеса не смыслить
бельмо# || вылупить бельма, выпучить бельма, выпялить бельма, вытаращить бельма, налить бельма
беляк (-чка)# [опрятный человек, щеголек (Даль, белый)] см. франт
бенефис# || устроить бенефис
Бередить#, вередить, растравлять, растрагивать, задевать. Ср. <Возбуждать>.
Бережливость#, бережь, домовитость, расчетливость, экономия. «Бережь лучше прибытка» (посл.).
Бережливый#, бережный, домовитый, расчетливый, экономный, хозяйственный, умеренный. Жить с расчетом (бережливо). Ср. <Скупой>. Прот. <Расточительный>. См. скупой
бережь# [береженье и бережливость; охрана, сохранение; осторожность (Даль)] см. бережливость, заботливость

Пишем код конвертора

Файл dicconv.php — Конвертор словаря

<?php
setlocale (LC_ALL, 'ru_RU.CP-1251');
#исходный словарь
$buffer = file_get_contents("original.txt");
#step1
$buffer = preg_replace("/[.*] см./mU", "", $buffer);
$buffer = preg_replace("/(.*)/U", "", $buffer);
$buffer = preg_replace("/<.*>/U", "", $buffer);
$buffer = preg_replace("/^(.*)..*$/mU", "$1", $buffer);
$buffer = str_replace('||', '', $buffer);
$buffer = str_replace(array('#,', '# ,'), '#', $buffer);
$buffer = str_replace('#', ' =>', $buffer);
$buffer = str_replace(',', ' |', $buffer);
$buffer = strtolower($buffer);

$abuffer = explode("n", $buffer);
$kbuffer = array();

foreach ($abuffer as $v) {
$_v = explode('=>', $v);
$_k = trim($_v[0]);
$_ak = explode('|', $_k);
$_k = preg_replace("#^([w[:space:]]+).*$#", "$1", $_k);

$_v = trim($_v[1]);
if (!empty($_k) && !empty($_v)) {
  $kbuffer[$_k] = $_v;

  if (count($_ak) > 1) {
    array_shift($_ak);
    foreach ($_ak as $_akv) { if (($__akv = trim($_akv)) && !empty($__akv)) $kbuffer[$__akv] = "@{$_k}"; }
  }

}
}

uksort($kbuffer, "kcmp");
#implode to text
$buffer = '';
unset($abuffer);

foreach ($kbuffer as $k => $v) {
$buffer .= sprintf("%s => %sn", $k, $v);
}

$buffer = preg_replace("#[40]{2,}#", ' ', $buffer);

#done - кладем словарь в файл dic.txt
file_put_contents('dic.txt', $buffer);

/// callback
function kcmp($a, $b) { return strlen($a) > strlen($b); }

Имеем — словарь original.txt. Запускаем скрипт php ./dicconvert.php, на выходе получаем файл dic.txt,
ключи (синонимы) упорядочены по длине фразы — так чтобы короткие слова заменялись сразу, длинные — в конце.

белок => вытаращить арабские белки
волос => шерсть | щетина | пух | пушок | ворса; вихор | грива | хохол | челка | чуб | шевелюра | коса | борода | усы | бакенбарды | бакены | баки | висок | кудер | косма | прядь
антик => редкость | чудак
впрок => заготовлять впрок | не впрок
багаж => вещи | кладь | поклажа | клажа | ноша | груз | фрахт
вкось => и вкривь | и вкось
беляк => франт

Этот файл сьест в последствии наш обработчик словаря.

Класс synonimizer.class.php — Парсер словаря

Собственно библиотека для работы со словарем.
Класс может использоваться автономно, пример:

$syn = new synonimizer();
$result_text = $syn->syn("любой текст");

В конструкторе читаем файл dic.txt и разбирает его в массив внутреннего формата.
Для наблюдения за процессом предусмотрена константа DEBUG, установка которой выдаст при работе
в консоль отладочную информацию о выполненых заменах.

<?php

class synonimizer {

const DEBUG = 0;

const ROW_DELIM   = '=>';   
const VALUE_DELIM = '|';   

private $_dic_file = 'dic.txt';

/**
* @private array 'syn' = array(value, value, value)
*/
private $parsed = array();


function __construct() {
     
     $_parsed = file(dirname(__FILE__) . '/' . $this->_dic_file);
     
    foreach ($_parsed as $k => $v) {
      $v = trim($v);  
      if (empty($v) || 0 === strpos($v, '#')) {
      ; // nop
      }
      else {
        $v = explode(self::ROW_DELIM, $v);
        $_key = trim($v[0]);
        $_data = array();
        if (strpos($v[1], self::VALUE_DELIM) !== false) {
            $data = explode(self::VALUE_DELIM, $v[1]);
            foreach ($data as $dk => $dv) {
                $_data[$dk] = trim($dv);
            }
        }
        else {
            $_data = array(trim($v[1])); 
            if (strpos($_data[0], '@') === 0) {
                // this is alias
                $_data = $this->parsed[substr($_data[0], 1)];
            }
        }
      
        // save
        $this->parsed[$_key] = $_data;
      }
    }
}

/**
* Callback 
*/
static $_c_matches = false; 
static function syn_callback($matches) {
     $out = $matches[2];
     if (self::DEBUG) echo " -- " . $out . "n";
     if (!empty(self::$_c_matches)) {
         $i = count(self::$_c_matches) - 1;
         $i = ($i > 0) ? mt_rand(0, $i) : 0;
         $out = self::$_c_matches[$i];
     }
     if (self::DEBUG)  echo " ++ " . $out . "nn";
     return $matches[1] . $out . $matches[3];
}

private static $index = 0;

public function syn($text) {

   $text = ' ' . str_replace(array('rn', 'n'), "rn", $text) . ' ';
   self::$index++; 
   echo ">>> " . self::$index . "n";
   
   if (self::DEBUG) {
       echo str_repeat("-", 80) . "n" . wordwrap($text, 80) . "n" .  str_repeat("-", 80) . "nn";
    }

    foreach ($this->parsed as $key => $matches) {
        self::$_c_matches = &$matches;
        $text = preg_replace_callback(
        '#([^wd-])(' . preg_quote($key) . ')([^wd-])#i'// ([^wd-]) //([s.,])
        , 'synonimizer::syn_callback'
        , $text
        );
      
    }

    $text = str_replace("rn", 'rn', substr($text, 1, -1));
    if (self::DEBUG) echo "--> " . wordwrap($text, 80) . " nn";
    return $text;
} 
}

Предполагается, что у нас имеется достаточно большой дамп таблицы в файле source.1251.sql.
В нем нам нужно отсинонимизировать поля title, text, description.

UPDATE `texts` SET `id` = 1,`title` = 'Под Тверью открылся рок-фестиваль "Эммаус-2006"', 
`description` = 'За два дня здесь ожидается nвыступление более 20 начинающих групп. Как говорят зрители nфестиваля: "В его афише хорошие  музыканты, это привлекает, как nи само место проведения, все здорово!".'
WHERE  `ss_news_export_news`.`id` = 1;

UPDATE `texts` SET `id` = 2,`title` = 'Samsung представил новый игровой телефон',
`description` = 'Компания Samsung Electronics представила новый игровой сотовый телефон. Мобильник SCH-B450 будет отличаться стильным дизайном и широким набором мультимедийных функций. Видеоадаптер мобильника позволит запускать на SCH-B450 игры с трехмерной графикой, а сп',`category_id` = 2,
WHERE  `ss_news_export_news`.`id` = 2;

...

Файл synmake.php — Синонимайзер

На входе — дамп, на выходе — файл result.sql.
Разницу между файлами можно оценить под виндой например с помощью winmerge.

<?php
$source = "source.1251.sql";
require "synonimizer.class.php";
ob_implicit_flush(true);

$syn = new synonimizer();
$buffer = file_get_contents($source);

echo "working...n";

  function kupper($matches) {
    return '. ' . ucfirst($matches[1]);
  }

// функция обратного вызова
  function kreplace($matches) 
  {
    GLOBAL $syn;
    $text = $syn->syn($matches[2]);
    $text = preg_replace_callback('#. ([а-я]+)#', "kupper", $text);
    $text = ucfirst($text);
    $text = $matches[1] . $text . $matches[3];
    return $text;
  }

$buffer = preg_replace_callback(
              '#(`(?:title|text|description)` = ')(.*)(')#U',
              "kreplace",
              $buffer);


file_put_contents('result.sql', $buffer);

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

Стартовый файл start.bat
Для windows, юниксойды добавляют в начало #!/usr/local/bin/php и запускают

@echo off chcp 1251

php synmake.php

Ну вот и все .. Синонимайзер текста готов :)! Как видите ничего сложного здесь нет.

Веб-сервис по подбору синонимов?

Нужен веб-сервис с API или php-класс/скрипт, способный по запросу выдавать набор синонимов для заданного слова/словосочетания. Русский язык. Вероятно, сервис должен понимать слова в различных формах и падежах. Будет круто, если различит ошибки и опечатки.

Гуглопоиск дал лишь кучу говносеошных сервисов (т.н. синонимайзеров) для искажения информации, поэтому рискую спросить здесь.


  • Вопрос задан

    более трёх лет назад

  • 10842 просмотра

Пригласить эксперта

Есть словарь синонимов ASIS Виталия Тришина.
Я пользуюсь им в виде десктоп-приложения, которое можно скачать на сайте автора:
www.trishin.ru/left/dictionary/

А вот кто-то сделал его онлайн-вариант:
slova.zkir.ru/

База синонимов хорошая. Но падежи/склонения/опечатки он распознавать не будет.

Если падежи напрягают, можно использовать MYSTEM от Яндекса.
Mystem сделан и для винды и для иксов.
В нем уже есть внутри словарная база.
Эта программа по крайней мере поможет найти начальную форму слова, и определить падеж/склонение и т.д.

По мне лучший сервис для подбора синонимов — isynonym.ru


  • Показать ещё
    Загружается…

05 мая 2023, в 02:11

1000 руб./за проект

04 мая 2023, в 23:12

500 руб./за проект

04 мая 2023, в 22:31

10000 руб./за проект

Минуточку внимания

Скачать архив файла можно вот здесь http://dadon.ru/zip/syn.rar
Ссылка на блог приветствуйется.

Привожу кусок PHP программы «Подбор синонимов», которая позволяет менять одни слова на другие (делать подбор синонимов). Я написал код за 30 минут. Думаю и вам не составит труда понять что к чему, поэтому комментарии к программе подбора синонимов опускаю.

Структура файла для синонимов vocabulary.txt следующая:
Исходное Слово | Слово Замены

Скрипт создает три файла: один с заменой синонимов, другие с выделением жирным шрифтом заменяемых слов.

Чтобы поисковик не замечал подлога после работы программы по подбору синонимов нужно заменять каждое пятое слово. Ведь google слабо понимает конструкции, состоящие из пяти и большего количества слов.

Кусок программы на PHP «подбор синонимов» (к сожалению, вордпресс не корректно отображает ПХП код)



$file = $_GET[f];
$file="1.htm";

$filePoinr_w1=fopen("1".$file,"w");
$filePoinr_w2=fopen("2".$file,"w");
$filePoinr_w3=fopen("3".$file,"w");

$array_voc=file("vocabulary.txt");
$ss='';

for ($n=0; $n 

$s=$array_voc[$n];
$p=strpos($s, "|");

$s1=substr($s,0,$p);
$s2=substr($s,$p+1,1000);

$replace1[$s1] = $s2;
$replace2[$s1] = "*b*".$s1."";
$replace3[$s1] = "*b*".$s2.";

}

echo " 

n”;

$array1=file($file);
for ($n=0; $n $str=$array1[$n];

$new_str = strtr($str, $replace1);
fwrite($filePoinr_w1,$new_str."n");

$new_str = strtr($str, $replace2);
fwrite($filePoinr_w2,$new_str."n");

$new_str = strtr($str, $replace3);
fwrite($filePoinr_w3,$new_str."n");

}

fclose($filePoinr_w1);
fclose($filePoinr_w2);
fclose($filePoinr_w3); 

 

Написать программу для подбора синонимов не сложно. Сложно сделать текст осмысленным после синонимной обработки.

Понравилась статья? Поделить с друзьями:
  • Синонимы мятая бумага
  • Синонимы мягкий уступчивый
  • Синонимы мягкий материал
  • Синонимы мчаться лететь
  • Синонимы мучение украденный баловство