Быстрый анализ сайтов конкурентов через сайтмапы. Часть 3 - более глубокий анализ

Или как что-то скорее не получилось, но выглядит все равно забавно

Posted by snakers41 on July 5, 2017

Статьи цикла

Быстрый анализ сайтов конкурентов через сайтмапы. Часть 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 технологии и применить такой подход к разным задачам =)

Если вам была интересна эта статья - также заходите на наш канал в телеграме.