Расширения Chrome с обработкой естественного языка

Как создать расширение Chrome, которое управляет DOM на основе именованных сущностей.

Создание Chrome-расширения Know Your VC было скорее спровоцированной идеей, если уж на то пошло.

Прежде чем я углублюсь в нюансы того, как мы создали этот продукт, вам следует заглянуть на нашу страницу knowyourvc.com, а также ознакомиться с нашим расширением Chrome здесь! Этот проект также является первым проектом с открытым исходным кодом, который я выпустил - посмотрите его на Github здесь. Я расскажу о коде в репозитории, если вы хотите продолжить!

В целом, расширение представляет собой простое расширение сценариев содержимого, которое прослушивает событие document.ready и рекурсивно анализирует DOM с помощью анализатора именованных сущностей. Для каждого имени, которое он находит в этом рекурсивном вызове, мы делаем простой запрос к нашему Know Your VC API, который выполняет простой запрос имени. Если имя возвращает что-то отличное от null, наш парсер DOM добавит элементы div вокруг найденного им узла имени и добавит событие наведения для всплывающего окна.

Звучит довольно просто, не так ли? Это!

Наше расширение начинается с создания нового Hilitor. Это очень маленькая библиотека подсветки для javascript, которую я сам настроил для этого расширения. в файле content.js мы вызываем hilitewords и передаем DOM-узел, который хотим переместить. В идеале это был бы корневой узел модели DOM.

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

if(node.hasChildNodes()) {      
  for(var i=0; i < node.childNodes.length; i++)
      this.hiliteWords(node.childNodes[i]);    
}

Затем мы проверяем, является ли узел текстовым, проверяя атрибут nodeType каждого узла. если nodeType == 3, это означает, что мы должны анализировать узел.

Далее мы используем (вероятно, единственную) облегченную библиотеку обработки естественного языка, называемую компромисс. Он включает в себя распознавание именованных сущностей, на которые мы будем во многом полагаться при синтаксическом анализе нашей DOM.

Мы передаем строку каждого текстового узла DOM, и библиотека NLP возвращает нам очень удобный объект, полный объектов, которые он распознал, включая некоторую информацию, зависящую от типа объекта.

doc = nlp(node.nodeValue);
var data = doc.people().data();
if (data.length > 0) {
    var self = this;
    data.map(function(entity) {
        if (entity.hasOwnProperty('firstName')) {
        // do your DOM parsing here

Замечание об этой библиотеке - вы ожидаете, что это будет очень дорого с вашими стандартными библиотеками НЛП на основе правил. Но пакет был разработан с учетом Javascript и веб-интерфейсов, поэтому он чрезвычайно легкий и работает невероятно быстро.

Мы проверяем, есть ли у объекта, который распознала библиотека, firstName, чтобы не перегружать API бесполезными результатами.

Хотя это и не идеально, но дальше мы отправляем запрос API для каждого найденного имени с полем firstName. Но прежде чем мы это сделаем, нам нужно создать экземпляры оберток для нашего нового выделенного слова. На самом деле это намного сложнее, чем кажется. Достаточно просто прикрепить тег <em> до и после слова, если мы просто пытаемся выделить все появляющиеся имена, но мы также добавляем карточку с информацией об инвесторе, которая появляется при наведении курсора. Вот тут-то все становится немного сложнее.

var wrapper = document.createElement('div');              
var popup = document.createElement('div');              
var stars = document.createElement('div');

Мы начинаем вручную создавать элементы DOM, которые скоро присоединим. Нам нужно проанализировать эти элементы DOM вместе со всеми необходимыми CSS и атрибутами, пока мы получаем информацию из нашего API.

Далее мы выполняем вызов API! Мы создали очень простой экземпляр EC2 на AWS, чтобы поддерживать это - реализация в нашем API будет обсуждена позже.

Но дело с контент-скриптами - это дополнительная безопасность для запросов без SSL. Это проблема, потому что Chrome рассматривает этот запрос между разными источниками как нечто, чего не должно происходить от одного из его расширений. Так что мы можем сделать?

Здесь начинается немного сложностей - мы экстраполируем запрос в наши фоновые скрипты.

chrome.runtime.sendMessage({                
    method: 'GET',                
    action: 'xhttp',                
    url: url,              
}, function (res) {                
    res = JSON.parse(res);
    // inject info into DOM

Мы отправляем сообщение нашим фоновым скриптам, и у нас также есть слушатель:

chrome.runtime.onMessage
    .addListener(function (request, sender, callback) {
        if (request.action == "xhttp") {
            var xhttp = new XMLHttpRequest();
            // Rest of the request

Наши фоновые сценарии делают наши запросы за нас и просто передают необходимую полезную нагрузку обратно в функцию обратного вызова, которую мы передаем.

Теперь мы проверяем, существует ли инвестор, проверяя объект JSON на наличие investorId и просматривая:

if (!res.investorId) {                
    return;                
}                 
...            
if (res.review) {
    // append review text

Код, выходящий за рамки этого, требует значительного рефакторинга! Но в основном мы просто добавляем прослушиватели событий onClick и hover, чтобы скрыть / показать созданную нами карточку обзора.

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

Двигаясь вперед, мы обязательно должны рассмотреть возможность обучения нашему собственному классификатору. Для этого подойдет модель от последовательности к последовательности, которая принимает на входе строку и возвращает только имя найденного инвестора. Проблема заключается в построении обучающих данных из нашей базы данных инвесторов, а также в размере модели. Прогнозирование модели от последовательности к последовательности - нелегкая операция - что могло бы быть лучше, если бы мы сделали наш собственный распознаватель именованных объектов, который больше всего подходит для наших собственных инвесторов!