Подсвечиваем манипуляции и пропаганду на любом сайте

Подсвечиваем манипуляции и пропаганду на любом сайте

Береги свой ум.

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

Следующий шаг — научиться подсвечивать манипуляции на любом сайте. Мы возьмём за основу уже написанный код и научимся делать его инъекцию в любую страницу. 

Что делаем

Помните наш новогодний спецпроект про падающие снежинки? Мы там написали специальный код, который запускает снежинки на любом сайте. Логика там была такая:

  1. Мы подготовили скрипт, который запускал снежинки на локальной странице.
  2. Добавили в этот скрипт вставку CSS-стилей в документ, чтобы скрипт сам разместил стили в разделе <head>.
  3. Написали специальный код, который можно выполнить в консоли браузера на любом сайте и получить такие же снежинки. 

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

👉 Если бы мы тогда не сделали проект со снежинками, то мы бы сейчас долго разбирались с инжектом JS-кода на произвольную страницу. Но так как у нас уже есть готовый код из того проекта, это сильно облегчает нам задачу.

Собираем скрипт

Чтобы всё заработало, нам нужно вынести скрипт из HTML-страницы в отдельный файл. Возьмём скрипт из прошлого проекта и сохраним его как noodle.js.

// массив с регулярными выражениями
var r = [];
// заполняем элементы массива нашими выражениями
r[0] = /\B[бБ]ольш\W{2,4} +деньг\W{1,3}\B/;
r[1] = /\Bработа\W{1,4}где хо\W{2,7}\B/;
r[2] = /\B[пП]рямо сейчас\B/;
r[3] = /\B[рР]абота\W{0,3} ["«]на дядю["»]\B/;
r[4] = /\Bгорба\W{2,6}с[яь]\B/;
r[5] = /\B[вВ] любое время\B/;
r[6] = /\Bвсего +\d{1,3} +(минут|час)\D{0,3} в (день|неделю)\B/;
r[7] = /\B[шШ]кол\W\B/;
r[8] = /\B[пП]ассивн\W{1,3}\B/;
r[9] = /\B[нН]а +Бали\B/;
r[10] = /\BТаиланд\W{0,2}\B/;
r[11] = /\B[сС]истем\W{1,3} выгодно\B/;
r[12] = /\B[рР]абот\W{1,3} в декрете\B/;

// когда документ загрузился — начинаем работу
$(document).ready(function () {

  // функция, которая оборачивает найденное регулярное выражение в тег с подсветкой
  // в качестве аргументов мы передаём регулярное выражение и текст, который нужно обработать
  function highlightText(regular, originalHtml) {
      // переменная, в которую мы поместим итоговое значение
      var newHtml;
      // оборачиваем найденное значение в тег <span>
      // решение подсмотрели здесь: https://www.telerik.com/support/kb/aspnet-ajax/details/highlight-text-inside-html-elements-and-templates
      newHtml = originalHtml.replace(new RegExp(regular, "gi"), function replace(match) {
          return '<span class="highlight">' + match + '</span>';
      })
      // возвращаем результат обработки
      return newHtml;
  }

  // функция, которая подсвечивает текст в содержимом какого-то элемента на странице
  function lst(txt) {

    // перебираем по очереди все абзацы, которые есть на странице, используя возможности jQuery
    $(txt).each(function () {

      // по очереди проходим по массиву с регулярными выражениями
      for ( var z = 0; z < r.length; z++) {
        // на каждом шаге цикла берём содержимое очередного абзаца 
        var par = this.innerHTML;
        // и прогоняем его через подсветку каждого найденного регулярного выражения
        this.innerHTML = highlightText(r[z],par);
      }
    });
  };

  // подсвечиваем все слова внутри абзацев на странице
  lst('p');
});

Сейчас этот скрипт находит все абзацы, помеченные тегом <p>, и подсвечивает найденный в них текст. Но текст на странице может быть не только внутри абзацев — его можно завернуть в блоки тегом <div> или просто написать без тегов. 

Чтобы скрипт искал совпадения по тексту на всей странице, а не только в абзацах, заменим в вызове функции lst() параметр p на body. Теперь мы сможем обрабатывать содержимое всей страницы, включая разделы меню и дополнительные блоки с другими материалами.

Вставляем стили

В нашем распоряжении есть только этот скрипт, который должен сам подготовить страницу к подсветке найденных слов. Для этого мы идём в проект со снежинками, смотрим, как добавлялись стили в том коде и делаем то же самое в этом:

var style = document.createElement('style'); 
style.innerHTML = ` 
.highlight {
      background: #4CFF00;
    }
`; 
document.head.appendChild(style);

Этот код при запуске скрипта добавит на страницу стиль подсветки с тем же названием, которое используется в нашем скрипте.

Добавляем новые регулярные выражения

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

// массив с регулярными выражениями про заработок
var r = [];
// заполняем элементы массива нашими выражениями
r[0] = /\B[бБ]ольш\W{2,4} +деньг\W{1,3}\B/;
r[1] = /\Bработа\W{1,4}где хо\W{2,7}\B/;
r[2] = /\B[пП]рямо сейчас\B/;
r[3] = /\B[рР]абота\W{0,3} ["«]на дядю["»]\B/;
r[4] = /\Bгорба\W{2,6}с[яь]\B/;
r[5] = /\B[вВ] любое время\B/;
r[6] = /\Bвсего +\d{1,3} +(минут|час)\D{0,3} в (день|неделю)\B/;
r[7] = /\B[шШ]кол\W\B/;
r[8] = /\B[пП]ассивн\W{1,3}\B/;
r[9] = /\B[нН]а +Бали\B/;
r[10] = /\BТаиланд\W{0,2}\B/;
r[11] = /\B[сС]истем\W{1,3} выгодно\B/;
r[12] = /\B[рР]абот\W{1,3} в декрете\B/;
r[13] = /\Bвсему +науч\W{1,3}\B/;
r[14] = /\Bуста\W{1,2} +\W+ +в +офисе\B/;
r[15] = /\Bофисн\W{2,3} +планктон\W{0,2}\B/;
r[16] = /\Bмой +доход +составил\B/;
r[17] = /\Bменя +часто +спрашивают\B/;
r[18] = /\Bмногие +спрашивают\B/;
r[19] = /\Bработать +н[ие] +дня\B/;
r[20] = /\Bденежн\W{2,3} +поток\W?\B/;
r[21] = /\Bденежн\W{2,3} +энерги\W\B/;
r[22] = /\Bне +соль[ею]\W{3,5}\B/;
r[23] = /\Bна +нов\W{2,3} +уров\W{2,3}\B/;
r[24] = /\Bдоход +не +ограничен\B/;
r[25] = /\Bнеограниченн\W{2,3} +доход\B/;
r[26] = /\Bидти +к +своей +цели\B/;
r[27] = /\Bвс[её], +о +ч[её]м +(ты|вы) +мечтал\W\B/;
r[28] = /\Bсброс\W{1,4} +оковы\B/;
r[29] = /\Bофисн\W{2,4} +рабств\W\B/;

// массив с регулярными выражениями c пропагандой
var pr = [];
// заполняем элементы массива нашими выражениями
pr.push(/\Bвраг\W{0,3}\B/);
pr.push(/\Bвойн\W{1,3}\B/);
pr.push(/\Bбор\W\W?с[ья]\B/);
pr.push(/\Bмраз\W{0,3}\B/);
pr.push(/\Bгад\W{0,3}\B/);
pr.push(/\Bурод\W{0,3}\B/);
pr.push(/\Bпредател\W{0,3}\B/);
pr.push(/\Bиуд\W{0,3}\B/);
pr.push(/\Bпаразит\W{0,3}\B/);
pr.push(/\Bагент\W{0,3}\B/);
pr.push(/\Bметодичк\W{0,3}\B/);
pr.push(/\Bпо +указке\B/);
pr.push(/\Bвероломн\W{0,3}\B/);
pr.push(/\Bопуска\W{1,3}с[яь]\B/);
pr.push(/\Bпровальн\W{0,3}\B/);
pr.push(/\Bунизительн\W{0,3}\B/);
pr.push(/\Bчудовищн\W{0,3}\B/);
pr.push(/\Bварварск\W{0,3}\B/);
pr.push(/\Bзверск\W{0,3}\B/);
pr.push(/\B(об)?нищ\W{2,6}\B/);
pr.push(/\B(на|с) +колен\W{0,3}\B/);
pr.push(/\Bдави\W{2,4}\B/);
pr.push(/\Bподавля\W{1,4}\B/);
pr.push(/\Bвырва\W{1,4}\B/);
pr.push(/\Bуничтожи\W{1,4}\B/);
pr.push(/\Bрастопта\W{1,4}\B/);
pr.push(/\Bпроглоти\W{1,4}\B/);
pr.push(/\Bподстави\W{1,4}\B/);
pr.push(/\Bдоби\W{1,4}с[ья]\B/);
pr.push(/\Bдоби\W{1,4}\B/);
pr.push(/\Bнеудивительно\B/);
pr.push(/\Bв +то +время +как\B/);
pr.push(/\Bразумеетс[ья]\B/);
pr.push(/\B(мы|каждый) +(из +нас)? +долж\W{2,4}\B/);
pr.push(/\B(мы|каждый) +(из +нас)? +обяза\W{1,3}\B/);
pr.push(/\Bостава\W{1,4}с[ья] +в +стороне\B/);
pr.push(/\Bвсе +только +начинаетс[ья]\B/);
pr.push(/\Bсу\W{1,4} +котор\W{2,4}\B/);
pr.push(/\Bединственн\W{2,4}\B/);
pr.push(/\Bв +упор +не +виде\W{1,4}\B/);
pr.push(/\Bзакрыва\W{1,4} +глаза\B/);
pr.push(/\Bжестк\W{1,4}\B/);
pr.push(/\Bбескомпромиссн\W{1,4}\B/);
pr.push(/\Bбесцеремонн\W{1,4}\B/);
pr.push(/\Bмощн\W{1,4}\B/);
pr.push(/\Bрезонанс\W{0,4}\B/);
pr.push(/\Bэпопе\W{1,4}\B/);
pr.push(/\Bгеро\W{1,4}\B/);
pr.push(/\Bгероическ\W{1,4}\B/);
pr.push(/\B(с|на) +колен\W{0,4}\B/);
pr.push(/\Bподвиг\W{0,4}\B/);
pr.push(/\Bдолг\W{0,4}\B/);
pr.push(/\Bсвященн\W{1,4}\B/);
pr.push(/\Bдля +кажд\W{1,4}\B/);
pr.push(/\Bбезучастн\W{1,4}\B/);
pr.push(/\Bскорее +всего\B/);
pr.push(/\Bвероятнее +всего\B/);
pr.push(/\Bперспектив\W{0,3} +развития\B/);
pr.push(/\Bмы +как +страна\B/);
pr.push(/\Bнарод\W{0,2}\B/);
pr.push(/\Bобществ\W{1,4}\B/);
pr.push(/\Bдеградац\W{2,4}\B/);
pr.push(/\Bразвал\W{0,4}\B/);
pr.push(/\Bбез +лишнего +шума\B/);
pr.push(/\Bтак +называем\W{0,4}\B/);
pr.push(/\Bдно \B/);
pr.push(/\Bпросто\B/);
pr.push(/\Bпросто-напросто\B/);
pr.push(/\Bсотни +тысяч\B/);
pr.push(/\Bмиллионы\B/);
pr.push(/\Bдесятки +миллионов\B/);
pr.push(/\Bбудущ\W{2,4}\B/);
pr.push(/\Bпобед\W{1,4}\B/);
pr.push(/\Bсветл\W{2,4}\B/);
pr.push(/\Bскорее +всего\B/);
pr.push(/\Bвсе +стороны +(\W{5,15} )?жизни\B/);
pr.push(/\Bвсеобщ\W{2,4}\B/);
pr.push(/\Bвсе\B/);
pr.push(/\Bкаждый\B/);
pr.push(/\Bразвити\W{1,4} +страны\B/);
pr.push(/\Bнедаром\B/);
pr.push(/\Bмноголетн\W{2,4}\B/);
pr.push(/\Bмы +как +страна\B/);
pr.push(/\Bотсутствие +какого-либо\B/);
pr.push(/\Bкаки\W{1,3}-либо +перспектив\W{0,4}\B/);
pr.push(/\Bдостойн\W{2,4} +рол\W{1,3}\B/);
pr.push(/\Bдостоинств\W{1,4}\B/);
pr.push(/\Bгордос\W{1,4}\B/);
pr.push(/\Bсвобод\W{0,4}\B/);
pr.push(/\Bтотальн\W{1,4}\B/);
pr.push(/\Bмассовое +(\W{5,15} )?сознание\B/);
pr.push(/\Bпо-видимому\B/);
pr.push(/\Bнет +даже +подобия\B/);
pr.push(/\Bочевидно\B/);

Мы добавили второй массив с новыми словами, которые отвечают за пропаганду. Чтобы этот массив тоже участвовал в работе, добавим в скрипт после первого цикла с проверкой такой код со вторым циклом:

// по очереди проходим по второму массиву с регулярными выражениями
for ( var z = 0; z < pr.length; z++) {
  // на каждом шаге цикла берём содержимое очередного абзаца 
  var par = this.innerHTML;
  // и прогоняем его через подсветку каждого найденного регулярного выражения
  this.innerHTML = highlightText(pr[z],par);
}

Подготавливаем инжект-код

Снова залезем в проект со снежинками и посмотрим, как это сделано там:

var js = document.createElement('script'); js.src = "https://thecode.local/wp-content/uploads/2019/12/snowfall2020.js"; document.body.appendChild(js);

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

var js = document.createElement('script'); js.src = "https://mihailmaximov.ru/projects/lapsha/noodle.js"; document.body.appendChild(js);

Тестирование

На некоторых сайтах при запуске из консоли мы видим такое:

Тестирование

Это значит, что на сайте не подключён jQuery и скрипт не знает, что делать с переменной $. Чтобы это исправить, сделаем инжект не из одной строчки кода, а из двух:

  1. Первая строчка добавит на страницу поддержку jQuery.
  2. Вторая добавит наш скрипт и подсветит нужные слова.

var js0 = document.createElement('script'); js0.src = "https://thecode.media/wp-content/uploads/2019/06/jquery.js"; document.body.appendChild(js0);
var js = document.createElement('script'); js.src = "https://mihailmaximov.ru/projects/lapsha/noodle.js"; document.body.appendChild(js);

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

Также есть маленький процент сайтов, где наш код не сработает и выдаст что-то такое:

Также есть маленький процент сайтов, где наш код не сработает и выдаст что-то такое

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

Примеры работы

Для начала применим инъекцию скрипта на каком-нибудь явном испытательном стенде. Сделаем страницу с адово пропагандистским текстом и вставим в консоль наше заклинание:

var js0 = document.createElement('script'); js0.src = "https://thecode.media/wp-content/uploads/2019/06/jquery.js"; document.body.appendChild(js0);
var js = document.createElement('script'); js.src = "https://mihailmaximov.ru/projects/lapsha/noodle.js"; document.body.appendChild(js);

Результат:

Подсвечиваем манипуляции и пропаганду на любом сайте

Теперь в боевых условиях: возьмём эмоциональную, но не слишком заряженную статью доктора экономических наук Евгения Гонтмахера и вставим в консоли наше заклинание. Результат: 

Подсвечиваем манипуляции и пропаганду на любом сайте

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

Также мы обнаружили, что скрипт срабатывает с некоторой задержкой: она связана с тем, что сначала страница ждёт загрузки всех элементов, в том числе рекламы, скрытых пикселей и прочей служебной мишуры, которая в обычной жизни не мешала бы прочитать статью. Поэтому после инъекции в некоторых случаях нужно подождать. Или же мы перекроим инъекцию так, чтобы она срабатывала мгновенно. 

Ещё всплыло, что наша разметка может по-варварски разорвать существующую вёрстку страницы: сломать меню, помешать работе всплывающих окон и других вещей, запрограммированных на JS. Ну что ж… Можно было бы вернуть в код реагирование только на тексты внутри тега <p>, но это противоречит нашей задаче. 

Что значит этот результат

Наш скрипт подсвечивает слова, на которые стоит обратить внимание при чтении любого эмоционального текста. Например: 

  • Многолетняя стагнация, застой и деградация — по чьим оценкам? На основании каких данных? Это личные ощущения автора или статистически значимые данные? 
  • Эпопея — по чьим оценкам? 
  • Скорее всего — важно понимать, что это предположение.
  • Всеобщее ощущение, общественное внимание — манипуляция через обобщение. Очень разных людей описывают каким-то одним словом, как будто присваивая им всем некое общее свойство. 

Эти слова не значат сами по себе, что перед вами манипуляция. Но если текст после инъекции зажёгся как новогодняя ёлка, стоит прочитать его особенно внимательно: обобщения разобщить, натяжки стянуть, передёргивания задёрнуть, факты отделить от эмоций. 

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

Текст и код:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Мария Дронова

Соцсети:

Олег Вешкурцев

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства и компенсация, если вы пойдёте работать в «Яндекс».
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
Подключаем нейросеть к веб-камере

Готовьтесь — искусственный интеллект уберёт вас из кадра.

Делаем свой планировщик задач

Bootstrap + TodoList = Trello

Как сделать собственную программу-тудушку за 10 минут
Как сделать собственную программу-тудушку за 10 минут

Пошаговое руководство.

Проверяем текст в Главреде
Проверяем текст в Главреде

Добавляем новые возможности через API.

Кнопка в виде ЛОДКИ
Кнопка в виде ЛОДКИ

Просто дурь на CSS. Разок можно.

Как убрать что угодно на любом сайте
Как убрать что угодно на любом сайте

Самый популярный приём разработчиков.

Что означает ошибка IndentationError: expected an indented block
Что означает ошибка IndentationError: expected an indented block

Самая популярная ошибка у начинающих программистов на Python.

Простой калькулятор на JavaScript
Простой калькулятор на JavaScript

Проще уже некуда

Шифр Вернама на JavaScript
Шифр Вернама на JavaScript

Невзламываемый шифр за 4 строчки кода.

Делаем сами: адаптивный сайт

С котиками!

Uncaught TypeError: Cannot read property — что это означает
Uncaught TypeError: Cannot read property — что это означает

Нельзя прочитать то, чего нет.

Запускаем телеграм-бота на сервере

Тогда он будет работать круглые сутки, а вы — отдыхать.

Домашний кинотеатр на Raspberry Pi

Превращаем любой телевизор в умный гаджет.

easy