Руководство о том, как анализировать целевые страницы, сканировать лексические элементы и внедрять их в свои инициативы в области маркетинга и электронной коммерции.

Предисловие

В своей предыдущей статье я рассказал о возможностях автоматизации копирования объявлений Google Ads Responsive Search Ads с использованием Python 3 и Pandas. На этот раз я представлю руководство о том, как извлечь специфическую для веб-страницы лексическую информацию с помощью обработки естественного языка (NLP) для дополнительных соображений по копированию. Программа НЛП, представленная в этой статье, позволит вам:

  1. Разобрать веб-страницу на наличие текста и лексических элементов
  2. Определить тематику веб-страницы по ее лексическим элементам
  3. Представьте наиболее подходящие лексические элементы для создания копий

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

Примечание: лексическая информация, которую мы будем извлекать, также может быть использована для A/B-тестирования в среде электронной коммерции — процесс, лежащий в основе этого, будет описан в следующей публикации. А пока добавьте меня в LinkedIn, если хотите следить за всеми моими новыми проектами.

Зависимости проекта

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

$ pip install requests
import requests

Модуль requests позволяет отправлять HTTP-запросы с помощью Python. HTTP-запрос возвращает Объект ответа со всеми данными ответа (содержимое, кодировка, статус и т. д.).

$ pip install justext
import justext

jusText — это инструмент для удаления стандартного содержимого, такого как навигационные ссылки, верхние и нижние колонтитулы, со страниц HTML. Он предназначен для сохранения в основном текста, содержащего полные предложения, и поэтому хорошо подходит для создания лингвистических ресурсов, таких как веб-корпусы.

pip install -U pip setuptools wheel
pip install -U spacy
python -m spacy download en_core_web_trf
import spacy
from spacy.matcher import Matcher

spaCy — это бесплатная библиотека с открытым исходным кодом для расширенной обработки естественного языка (NLP) в Python. spaCy разработан специально для производственного использования и помогает создавать приложения, которые обрабатывают и понимают большие объемы текста. Его можно использовать для создания систем извлечения информации или понимания естественного языка, а также для предварительной обработки текста для глубокого обучения. Модуль Matcher позволяет находить слова и фразы, используя правила, описывающие атрибуты их токенов.

Примечание. Мы будем использовать конвейер en_core_web_trf для получения более точных результатов (в отличие от более распространенного и ориентированного на производительность варианта en_core_web_sm).

from collections import Counter

Counter — это подкласс dict для подсчета хешируемых объектов. Это коллекция, в которой элементы хранятся как ключи словаря, а их счетчики хранятся как значения словаря. Подсчеты могут быть любыми целыми значениями, включая нулевые или отрицательные подсчеты. Класс Counter подобен сумкам или мультимножествам в других языках.

Извлечение текста

Эта программа предназначена для извлечения лексических элементов из любого заданного URL-адреса, где доступ к запрошенному ресурсу не запрещен. Если вы пытаетесь запросить ресурсы веб-страницы, которые заблокированы в целях безопасности, вы, скорее всего, столкнетесь с ошибкой 403. Если вы хотите развернуть эту программу на уровне предприятия, вам необходимо проконсультироваться со своим ИТ-отделом, чтобы определить, возможно ли исключение безопасности для ваших вызовов API.

В нашем примере мы будем анализировать целевую страницу продукта из канадского ассортимента бытовой техники Whirlpool.

Это довольно низкоуровневая страница в иерархии веб-страниц Whirlpool, так почему мы анализируем именно ее? На практике гораздо сложнее правильно определить тематику низкоуровневого лендинга, чем высокоуровневого — обычно это связано с отсутствием текстового контента на низкоуровневых страницах. Если мы сможем получить точные результаты на детальном уровне, программа легко масштабируется до более общих целевых страниц.

Мы начинаем с отправки HTTP-запроса на нашу целевую целевую страницу с помощью модуля requests.

#extract text
url = "https://www.whirlpool.ca/en_ca/laundry/washers/he-top-load-washer/p.5.5-cu.-ft.-i.e.c.-smart-top-load-washer.wtw6120hw.html?originVariantsOrder=WH,C6"
response = requests.get(url)

Библиотека jusText необходима для извлечения текстовых элементов веб-страницы. jusText удалит лишний «беспорядок» (шаблоны, шаблоны) вокруг основного текстового содержимого веб-страницы и вернет текстовые элементы.

paragraphs = justext.justext(response.content, justext.get_stoplist("English"))

Каждый абзац, проанализированный jusText, будет добавлен в список — в данном случае это пустой список с именем for_processing. После этого список объединяется в одну строку для анализа spaCy.

for_processing = []
    for paragraph in paragraphs:
        if not paragraph.is_boilerplate:
            for_processing.append(paragraph.text)
for_processing = ''.join(for_processing)

Наконец, просмотрите извлеченное содержимое в терминале, распечатав список.

#print(for_processing)

Извлечь все теги части речи (POS)

Лексические элементы также могут называться тегами части речи (POS). Чтобы проанализировать извлеченный текст для тегов POS, мы начинаем с загрузки конвейера en_core_web_trf с помощью spaCy.

nlp = spacy.load('en_core_web_trf')

Мы также инициализируем doc, контейнер для доступа к лингвистическим аннотациям. В нашем сценарии doc будет присвоено значение for_processing.

doc = nlp(for_processing)

Каждый POS в пределах for_processing необходимо будет разбить на отдельные слова, знаки препинания и т. д., известные как «токены». Этот процесс называется токенизацией, и spaCy выполнит это, применяя правила, специфичные для языка, с которым мы работаем. Мы загрузили конвейер en_core_web_trf для нашего проекта, что означает, что правила, применимые к токенизации, будут специфичны для английского языка.

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

for token in doc:
print(token.text, token.pos_)
OUTPUT
Wash PROPN
technology NOUN
with ADP
Active PROPN
Bloom PROPN
™ PUNCT
wash NOUN
action NOUN
senses VERB
the DET

Каждому токену присваивается POS-тег в spaCy из следующего списка:

Просмотр всех тегов POS сам по себе не совсем полезен, но мы выполнили первую задачу нашей программы.

  1. Разобрать веб-страницу на наличие текста и лексических элементов (Готово!)

Давайте посмотрим, как мы можем применить наши POS-теги на практическом примере.

Определите тему и сущность веб-страницы

Если бы нам нужно было создавать маркетинговые материалы, специфичные для веб-страницы, мы должны были бы:

  • Иметь возможность анализировать нашу веб-страницу на предмет ее основной темы и сущности.
  • Извлеките описательные токены (прилагательные), непосредственно связанные с нашей основной темой.
  • Используйте наиболее подходящие прилагательные для использования в наших маркетинговых активациях.

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

Мы начинаем с создания списка всех токенизированных элементов нашей веб-страницы, которые можно считать «словами», т.е. токены, не являющиеся стоп-словами или знаками препинания.

nlp = spacy.load("en_core_web_trf")
matcher = Matcher(nlp.vocab)
doc = nlp(for_processing)
# all tokens that aren't stop words or punctuations
words = [token.text
    for token in doc
        if not token.is_stop and not token.is_punct]

Давайте отобразим наши топ-5 наиболее часто наблюдаемых токенов «слов».

# display the five most common tokens
word_freq = Counter(words)
common_words = word_freq.most_common(5)
print(common_words)
OUTPUT
[('\n', 43), ('washer', 15), ('5', 12), ('load', 10), ('water', 9)]

Похоже, на нашей странице много новых строк или разрывов строк. Также есть значение «шайб» (это хороший знак), а также несколько других цифр и существительных.

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

# noun tokens that aren't stop words or punctuations
nouns = [token.text
    for token in doc
    if (not token.is_stop and
        not token.is_punct and
        token.pos_ == "NOUN")]

Теперь мы отображаем 5 наиболее часто встречающихся существительных. Мы также присвоим нашему наиболее употребительному существительному значение most_common_noun для дальнейшего использования.

# display the five most common noun tokens
noun_freq = Counter(nouns)
common_nouns = noun_freq.most_common(5)
most_common_noun = str(common_nouns[0][0])
print(common_nouns)
print("The most common noun is:", most_common_noun, "or", most_common_noun.capitalize())
OUTPUT
[('washer', 15), ('load', 10), ('water', 9), ('account', 9), ('information', 7)]
The most common noun is: washer or Washer

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

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

Что, если мы проверим наш извлеченный текст на имена собственные? Это существительные, которые обозначают конкретное существо или вещь, обычно используя имя. Давайте попробуем использовать этот подход, чтобы попытаться установить марку нашей идентифицированной стиральной машины.

# noun tokens that arent stop words or punctuations
names = [token.text
    for token in doc
    if (not token.is_stop and
        not token.is_punct and
        token.pos_ == "PROPN")]
# five most common proper noun tokens
name_freq = Counter(names)
common_names = name_freq.most_common(5)
print(common_names)
OUTPUT
[('Privacy', 5), ('Notice', 4), ('Load', 4), ('Wash', 4), ('Terms', 3)]

К сожалению, эти результаты выглядят не совсем правильно. Из-за отсутствия текстового содержания, в котором упоминается наша торговая марка, наша программа не может определить, что эта стиральная машина на самом деле является стиральной машиной Whirlpool. Есть еще один способ решить нашу проблему — с помощью функции распознавания именованных сущностей spaCy. Согласно документации spaCy:

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

Категории распознавания именованных сущностей (NER) могут подпадать под следующие ярлыки:

Поскольку мы хотим идентифицировать только организацию/бренд, мы укажем это с помощью if (ent.label == ‘ORG') в нашем прогоне NER ниже.

# attempt named entity recognition
entities = [ent.text
  for ent in doc.ents
  if (ent.label_ == 'ORG')]

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

entities = list(map(lambda x: x.replace('.com','').replace('.ca',''), entities))

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

# display the five most common entities
ent_freq = Counter(entities)
common_entities = ent_freq.most_common(5)
print(common_entities)
OUTPUT
[('Whirlpool', 3), ('AccountWhirlpool', 1), ('Whirlpool Canada', 1), ('emailWhirlpool Canada', 1), ('Home Depot', 1)]

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

most_common_entity = str(common_entities[0][0])

Подводя итог, на данный момент мы определили:

  • Тема нашей веб-страницы, определенная spaCy как «стиральная машина».
  • Бренд/организация нашей стиральной машины, идентифицированная spaCy как «Whirlpool».

Соответствующие описания и анализ настроений

Мы уже можем описать нашу стиральную машину как стиральную машину Whirlpool, но что, если бы мы нашли дополнительные описательные слова в форме прилагательных, а не сущностей?

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

К счастью, модуль spaCy matcher позволяет нам находить экземпляры прилагательных на нашей веб-странице, которые находятся рядом с нашей ранее определенной темой веб-страницы. Мы выражаем эту смежную связь с помощью шаблона — «найди мне все экземпляры тегов POS, которые являются прилагательными, за которыми сразу следует наиболее часто идентифицируемое существительное, «стиральная машина» / «стиральная машина».

nlp = spacy.load("en_core_web_trf")
matcher = Matcher(nlp.vocab)
doc = nlp(for_processing)
pattern = [
  [{"POS": "ADJ"}, {"TEXT": most_common_noun}],
  [{"POS": "ADJ"}, {"TEXT": most_common_noun.capitalize()}]
]
matcher.add("ADJ_NOUN_PATTERN", pattern)
matches = matcher(doc)

В нашем более раннем алгоритме обнаружения существительных мы видели, что на нашей веб-странице было найдено 15 экземпляров слова «шайба» — давайте отобразим результаты нашего сопоставления и посмотрим, сколько экземпляров с нашим наложенным шаблоном возвращается.

print("Total matches found: ", len(matches))
OUTPUT
Total matches found:  3

В текстовом контенте есть три случая, когда наш шаблон совпадает. Давайте просмотрим пары существительное-прилагательное и назначим их переменной relevant_adjectives.

relevant_adjectives = []
for match_id, start, end in matches:
  relevant_adjectives.append(doc[start].text)
for match_id, start, end in matches:
    print("Match Found: ", doc[start:end].text)
print("The relevant adjectives are:", relevant_adjectives)
OUTPUT
Match Found:  smart washer
Match Found:  Best washer
Match Found:  smelly washer
The relevant adjectives are: ['smart', 'Best', 'smelly']

spaCy смог распознать умный, лучший и вонючий прилагательные, связанные с нашим most_common_noun. Это начало… однако «вонючий» не совсем похоже на прилагательное, которым вы хотели бы описать эту стиральную машину, особенно в маркетинговых материалах.

Мы можем провести анализ настроений связанных прилагательных, чтобы определить, имеют ли они «положительную» или «отрицательную» коннотацию. Для этого мы будем импортировать библиотеку TextBlob.

TextBlob — библиотека Python (2 и 3) для обработки текстовых данных. Он предоставляет согласованный API для погружения в общие задачи обработки естественного языка (NLP), такие как тегирование частей речи, извлечение именной фразы, анализ настроений и многое другое.

$ pip install -U textblob
$ python -m textblob.download_corpora

Процедура проста — для каждого прилагательного в нашем ранее определенном relevant_adjectives мы будем использовать TextBlob для анализа тональности прилагательного, а затем отобразим его полярность и субъективность.

Примечание. Полярность представлена ​​в виде диапазона с плавающей запятой, где показатель полярности 1,0 указывает на абсолютное положительное значение, а полярность -1,0 указывает на абсолютное отрицательное значение.

Примечание. Субъективность представлена ​​в виде диапазона с плавающей запятой, где показатель субъективности 1,0 указывает на то, что значение открыто для бесконечной интерпретации, а полярность -1,0 указывает на беспристрастное и объективное значение.

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

#conduct sentiment analysis (1.0 polarity = positive, -1.0 polarity = negative)
from textblob import TextBlob
positive_adjectives = []
for adjective in relevant_adjectives:
  print(adjective, TextBlob(adjective).sentiment)
  if TextBlob(adjective).sentiment.polarity > 0:
    positive_adjectives.append(adjective)
print("The positive adjectives are:", positive_adjectives)
OUTPUT
smart Sentiment(polarity=0.2, subjectivity=0.6)
Best Sentiment(polarity=1.0, subjectivity=0.3)
smelly Sentiment(polarity=0.0, subjectivity=0.0)
The positive adjectives are: ['smart', 'Best']

Собираем все вместе — автоматизируем копирование

В своей предыдущей статье я представил пошаговое руководство по созданию инструмента автоматизации копирования рекламы на Python с прямым выводом в формате .CSV, отформатированном для совместимости с платформами Google Ads и Microsoft Ads.

Мы будем использовать лексические элементы, которые мы извлекли с нашей веб-страницы, для создания рекламного текста без необходимости вручную определять продукт, марку продукта и соответствующие прилагательные продукта. Я не буду воспроизводить весь код, который я написал в своей предыдущей статье — если вы хотите создать свою собственную программу, вы можете найти полный код в моем репозитории gitHub.

Я изменил исходный код программы Python Responsive Search Ads, чтобы он соответствовал нашему текущему примеру электронной коммерции Whirlpool. Напомним, что мы принимаем пользовательские данные для adjectives, topic_phrases, branded_phrases и final_url. Теперь все они могут быть заменены с помощью наших токенизированных текстовых извлечений.

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

adjectives = ['Quality', 'High Quality', 'Excellent', 'Great Quality']
adjectives.extend(positive_adjectives)
adjectives = [adjective.title() for adjective in adjectives]
adjectives = [adjective + ' ' for adjective in adjectives]

Переменная topic_phrases больше не требует ввода пользователем, так как она была идентифицирована нашими POS-тегами как most.common_noun. Еще раз, мы отформатируем эту переменную, чтобы она соответствовала форматированию регистра заголовка нашей копии.

В большинстве маркетинговых активаций мы относимся к ассортименту товаров во множественном числе. Здесь мы предоставляем условный аргумент, который ставит во множественное число соответствующий most.common_noun, если он не заканчивается символом "s".

topic_phrases = most_common_noun.capitalize()
suffix = "s"
if topic_phrases.endswith(suffix) == False:
  topic_phrases = str(topic_phrases + suffix)

Переменная branded_phrases больше не принимает пользовательский ввод, поскольку мы смогли определить марку стиральной машины с помощью нашего модуля NER. Мы добавляем пробел в конце строки этой переменной, чтобы обеспечить согласованность форматирования и удобочитаемость.

branded_phrases = most_common_entity + ' '

Наконец, переменная final_url не требует ввода пользователем, так как это URL-адрес, с которого мы начали синтаксический анализ нашей веб-страницы — значение хранится в нашей первой созданной переменной url.

final_url = url

Мы все сделали — давайте запустим и посмотрим результаты! (Примечание. Я транспонировал выходные данные .CSV, чтобы их было легче читать)

Давайте попробуем это на другой целевой странице для 4-дверного холодильника шириной 36 дюймов и посмотрим, что получится.

Нам удалось еще раз определить правильные лексические элементы.

OUTPUT
#webpage subject identification
The most common noun is: fridge or Fridge
#webpage entity identification
The most common entity is: Whirlpool
#adjectives matched to our most common noun
The relevant adjectives are: ['new', 'current', 'Great', 'old', 'Great']
#relevant adjective sentiment analysis 
new Sentiment(polarity=0.1, subjectivity=0.4)
current Sentiment(polarity=0.0, subjectivity=0.4)
Great Sentiment(polarity=0.8, subjectivity=0.75)
old Sentiment(polarity=0.1, subjectivity=0.2)
The positive adjectives are: ['new', 'Great', 'old']
#'old' in this case is associated to be very slightly positive.
For more strict parsing of polarity, increase the threshold of acceptance (if TextBlob(adjective).sentiment.polarity > 0:)

Мы подошли к концу нашего путешествия по НЛП — спасибо за чтение! Я надеюсь, что вы нашли это руководство полезным для автоматизации некоторых ваших маркетинговых тактик. Как всегда, полный исходный код доступен в моем репозитории gitHub — https://github.com/rfinatan/Natural-Language-Processing-for-Marketing-and-E-Commerce-Using-spaCy.

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