Статьи цикла
Быстрый анализ сайтов конкурентов через сайтмапы. Часть 1 - парсинг
Быстрый анализ сайтов конкурентов через сайтмапы. Часть 2 - простой анализ
Быстрый анализ сайтов конкурентов через сайтмапы. Часть 3 - более глубокий анализ
В предыдущей статье мы остановились на том, что сделали простой анализ с помощью ручной разметки. А вдруг данные скрывают какую-то важную внутреннюю структуру, о которой мы даже не подозреваем и это откроет нам глаза на то, как компании на рынке строят свое SEO? Единственный способ узнать - сделать свой анализ.
Внимание - некоторые несущественные шаги пропущены для краткости изложения.
На всякий случай, исходники вы все еще можете скачать тут:
1. Подготовка данных
Давайте для начала подтянем наш ручной "маппинг" к основному датафрейму.
mapping_1_df = pd.read_excel('mapping1.xlsx')
mapping_2_df = pd.read_excel('mapping2.xlsx')
result_df = pd.read_csv('all_sitemaps.csv')
Теперь наши таблицы надо соединить:
result_df = pd.merge(
result_df,
mapping_2_df,
how='left',
left_on=['first_nav', 'second_nav'],
right_on=['first_split', 'second_split'])
result_df
result_df = pd.merge(
result_df,
mapping_1_df,
how='left',
left_on=['first_split_type', 'second_split_type'],
right_on=['first_split_type', 'second_split_type'])
result_df
Добавим теперь колонку, которая бы показывала язык и посмотрим, где язык не проставился:
result_df['language'] = result_df[result_df['first_split_type']=='language-alias'].first_nav
result_df[result_df['first_split_type']!='language-alias']
Удалим лишние колонки:
del result_df['second_split_type']
del result_df['first_split_type']
del result_df['second_split']
del result_df['first_split']
Теперь нужно векторизовать (в данном случае просто разбить на слова через пробел) URL-ы для анализа:
def split_url(string,sep1,sep2):
try:
split_list = string.split(sep1)
result_list = []
for idx, split_part in enumerate(split_list):
if (idx>0):
try:
split_part_list = split_part.split(sep2)
result_list.append(split_part_list)
except:
result_list.append(split_part)
return ' '.join([item for sublist in result_list for item in sublist])
except:
return []
result_df['word_vectors'] = result_df['loc'].apply(lambda x: split_url(x,'/','-'))
Сохраним получившийся результат на диск и откроем его.
result_df = result_df[['word_vectors','group', 'broker', 'language']]
result_df.to_csv('all_sitemaps_words_2.csv')
result_df = pd.read_csv('all_sitemaps_words_2.csv')
2. Простой анализ
Теперь можно посмотреть какие языки самые популярные на рынке:
table = pd.pivot_table(
result_df,
index=["language"],
columns=["group"],
values=["Unnamed: 0"],
aggfunc={"Unnamed: 0":len},fill_value=0
)
table
Это что-то нам в принципе уже дает с точки зрения стратегии локализации сайта.

3. Пример визуализации
Для начала давайте посмотрим, какие слова самые популярные для одного языка - английского. Для этого склеим все английские урлы в одну большую переменную.
texts = result_df[(result_df['group']=='instruments')&(result_df['language']=='en-ch')].word_vectors.fillna(value='').values.tolist()
После подготовки датасета нужно подготовить вектора. Для этого нам подойдет встроенная функция sklearn. Из-за большого объема данных, анализ будем делать на подвыборке.
cv = CountVectorizer(max_features=1000)
cv_fit=cv.fit_transform(random.sample(texts, 380000))
word_array = cv_fit.toarray()
word_array.shape
(380000, 1000)
word_names = cv.get_feature_names()
word_popularity = word_array.sum(axis=0)
word_popularity = np.vstack((word_popularity,word_names))
word_popularity
array([['297', '1134', '162', ..., '135', '189', '161'],
['10', '100', '1000', ..., 'zdp', 'zealand', 'zinc']],
dtype='<U21')
Теперь мы получили вектора слов и частотность их употребления. Но на практике значения самых популярных и большей части слов будут отличаться на порядки. Чтобы сделать визуализацию, нужно немного сгладить разницу.
word_popularity_df = pd.DataFrame(word_popularity.T)
word_popularity_df.columns = ['word_popularity','word']
word_popularity_df[['word_popularity']] = word_popularity_df[['word_popularity']].apply(pd.to_numeric)
word_popularity_df['word_popularity'] = word_popularity_df['word_popularity'].apply(lambda x: np.sqrt(x))
word_popularity_df[['word_popularity']] = word_popularity_df[['word_popularity']].apply(np.ceil)
word_popularity_df.sort_values(by='word_popularity', ascending=False)
В результате получается такая табличка (внизу только ее верхняя часть). Видно, что часть слов мусорная, но что-то скорее всего точно можно будет извлечь.
Используя библиотеку Wordmaps не без танцев с бубнами, построим карту самых используемых слов
core_text = ''
for word in list(word_popularity_df['word'].values):
for x in range(0, word_popularity_df[word_popularity_df['word']== word].word_popularity.values[0].astype(np.int64)):
core_text = core_text + ' ' + word
title = 'English sitemaps wordcloud'
plt.figure(figsize=(30,15))
wc = wordcloud.WordCloud(background_color='white', width=1200, height=600, max_font_size=100, max_words=400).generate(core_text)
wc.recolor(random_state=0)
plt.imshow(wc)
plt.title(title, fontsize=30)
plt.axis("off")
Без разбивки на категории получается не очень информативно (не забывайте, что это анализ URL-ов, а не контента страниц).

4. Более глубокая структура
Попробуем применить PCA, а вдруг там внутри есть какие-то кластеры, о которых у нас нет никакого понимания!
# let's try to do the PCA / t-SNE and cluster analysis on a subsample
result_df_subset = result_df.sample(frac=0.1, replace=True)
texts = result_df_subset.word_vectors.fillna(value='').values.tolist()
cv = CountVectorizer(max_features=500)
cv_fit=cv.fit_transform(random.sample(texts, 1404739))
word_array = cv_fit.toarray()
word_array.shape
У нас получается такой массив - (1404739, 500)
from sklearn.decomposition import PCA
word_names = cv.get_feature_names()
pca = PCA(n_components=2).fit_transform(word_array)
pca
array([[-0.68948127, -0.13604158],
[-0.82461816, -0.20401469],
[-0.68948127, -0.13604158],
...,
[ 0.9155401 , -0.4791268 ],
[ 0.70337389, -0.25374255],
[ 0.71072892, -0.5133545 ]])
Вставим первые 2 компоненты в нашу таблицу.
result_df_subset['pc1'] = pca[:,0].tolist()
result_df_subset['pc2'] = pca[:,1].tolist()
Теперь посмотрим есть ли кластеры в нашей выборке и чему они соответствуют.
import seaborn as sns
sns.lmplot('pc1', 'pc2', data=result_df_subset, hue='language', fit_reg=False, size=10)
plt.show()
Из визуализаций ниже получается, что кластеры есть, но они не соответствуют ни языку, ни компании, ни группам из нашей ручной разметки. Хм...интересно.


5. Что остается в итоге?
Разобьем все на кластеры и попробуем визуализировать слова - может будут видны отличия. Инициализируем алгоритм K-means значениями примерно в центре предполагаемых кластеров.
init_array = np.array([[-0.25,0], [-0.25,-1], [0.7,-0.25], [1.25, -1.25], [0.25,0.7], [0.25,-1.75]])
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=6, random_state=0, init=init_array).fit(result_df_subset[['pc1', 'pc2']])
kmeans.labels_
Получается примерно так

Чтобы сразу построить сразу много облаков слов, нужно поставить этот процесс на поток. Нам поможет несложная функция:
def draw_word_cloud(result_df_subset,class_no):
texts = result_df_subset[(result_df_subset['class']==class_no)].word_vectors.fillna(value='').values.tolist()
cv = CountVectorizer(max_features=1000)
cv_fit=cv.fit_transform(texts)
word_array = cv_fit.toarray()
word_popularity = word_array.sum(axis=0)
word_names = cv.get_feature_names()
word_popularity = np.vstack((word_popularity,word_names))
word_popularity_df = pd.DataFrame(word_popularity.T)
word_popularity_df.columns = ['word_popularity','word']
word_popularity_df[['word_popularity']] = word_popularity_df[['word_popularity']].apply(pd.to_numeric)
word_popularity_df['word_popularity'] = word_popularity_df['word_popularity'].apply(lambda x: np.sqrt(x))
word_popularity_df[['word_popularity']] = word_popularity_df[['word_popularity']].apply(np.ceil)
core_text = ''
for word in list(word_popularity_df['word'].values):
for x in range(0, word_popularity_df[word_popularity_df['word']== word].word_popularity.values[0].astype(np.int64)):
core_text = core_text + ' ' + word
title = class_no
plt.figure(figsize=(30,15))
wc = wordcloud.WordCloud(background_color='white', width=1200, height=600, max_font_size=100, max_words=400).generate(core_text)
wc.recolor(random_state=0)
plt.imshow(wc)
plt.title(title, fontsize=30)
plt.axis("off")
6. Небольшое разочарование
Если построить облака слов для каждого из классов выше, то визуально между ними ну вообще нет никакой разницы. Приведу примеры для кластеров 0, 2 и 5 как самых отдаленных.
Читатель, а может ты увидишь разницу, которая мне не совсем очевидна?


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

Новости
How-to

8. Выводы
В процессе мы узнали много нового:
- Как делать простейший парсинг сайтов, как работать с текстами, лямбда функциями и векторами;
- Как делать простейший анализ на коленке;
- Как визуализировать наши находки;
- Мы увидели структуру сайтов на данном рынке, увидели самые частотные слова из URL-ов, увидели внутреннюю структуру (которую не до конца поняли), увидели на каких языках, рынках и сайтах больше всего контента в этой отрасли;
- Если сосредоточиться на бизнес-части, то из такого анализа можно написать стратегию своего отдельно взятого ресурса...
А самое главное то, что это все легко повторить используя open-source технологии и применить такой подход к разным задачам =)
Если вам была интересна эта статья - также заходите на наш канал в телеграме.