Un enfoque de ciencia de datos para optimizar la estructura de enlaces internos

  • HatumSEO
  • SEO
  • Un enfoque de ciencia de datos para optimizar la estructura de enlaces internos

Conozca el enfoque basado en datos para mejorar los enlaces internos de un sitio web con el fin de lograr un SEO técnico más efectivo.

Optimizar los enlaces internos es importante si le importa que las páginas de su sitio tengan suficiente autoridad para clasificar sus palabras clave objetivo.. Por enlace interno lo que queremos decir es que las páginas de su sitio web reciben enlaces de otras páginas.

Esto es importante porque es la base por la cual Google y otras búsquedas calculan la importancia de la página en relación con otras páginas de su sitio web.

También afecta la probabilidad de que un usuario descubra contenido en su sitio.. El descubrimiento de contenido es la base del algoritmo Google PageRank.

Hoy, estamos explorando un enfoque basado en datos para mejorar los enlaces internos de un sitio web con el fin de lograr un SEO técnico más efectivo.. Eso es para garantizar que la distribución de la autoridad de dominio interna se optimice de acuerdo con la estructura del sitio.

Mejora de las estructuras de enlaces internos con ciencia de datos

Nuestro enfoque basado en datos se centrará en un solo aspecto de la optimización de la arquitectura de enlaces internos, que es modelar la distribución de enlaces internos por profundidad del sitio y luego apuntar a las páginas que carecen de enlaces para su profundidad de sitio particular.

Comenzamos importando las bibliotecas y los datos, limpiando los nombres de las columnas antes de obtener una vista previa:

import pandas as pd
import numpy as np
site_name = 'ON24'
site_filename = 'on24'
website = 'www.on24.com'

# import Crawl Data
crawl_data = pd.read_csv('data/'+ site_filename + '_crawl.csv')
crawl_data.columns = crawl_data.columns.str.replace(' ','_')
crawl_data.columns = crawl_data.columns.str.replace('.','')
crawl_data.columns = crawl_data.columns.str.replace('(','')
crawl_data.columns = crawl_data.columns.str.replace(')','')
crawl_data.columns = map(str.lower, crawl_data.columns)
print(crawl_data.shape)
print(crawl_data.dtypes)
Crawl_data

(8611, 104)

url                          object
base_url                     object
crawl_depth                  object
crawl_status                 object
host                         object
                             ...   
redirect_type                object
redirect_url                 object
redirect_url_status          object
redirect_url_status_code     object
unnamed:_103                float64
Length: 104, dtype: object
Sitebulb data

Lo anterior muestra una vista previa de los datos importados desde la aplicación de rastreo de escritorio Sitebulb. Hay más de 8000 filas y no todas serán exclusivas del dominio, ya que también incluirá URL de recursos y URL de enlaces salientes externos.

También tenemos más de 100 columnas que son superfluas para los requisitos, por lo que se requerirá alguna selección de columnas.

Sin embargo, antes de entrar en eso, queremos ver rápidamente cuántos niveles de sitio hay:

crawl_depth
0             1
1            70
10            5
11            1
12            1
13            2
14            1
2           303
3           378
4           347
5           253
6           194
7            96
8            33
9            19
Not Set    2351
dtype: int64

De lo anterior, podemos ver que hay 14 niveles de sitio y la mayoría de estos no se encuentran en la arquitectura del sitio, sino en el mapa del sitio XML.

Puede notar que Pandas (el paquete de Python para el manejo de datos) ordena los niveles del sitio por dígito.

Esto se debe a que, en esta etapa, los niveles del sitio son cadenas de caracteres en lugar de numéricos.. Esto se ajustará en el código posterior, ya que afectará la visualización de datos («a saber»).

Ahora, filtraremos filas y seleccionaremos columnas.

# Filter for redirected and live links
redir_live_urls = crawl_data[['url', 'crawl_depth', 'http_status_code', 'indexable_status', 'no_internal_links_to_url', 'host', 'title']]
redir_live_urls = redir_live_urls.loc[redir_live_urls.http_status_code.str.startswith(('2'), na=False)]
redir_live_urls['crawl_depth'] = redir_live_urls['crawl_depth'].astype('category')
redir_live_urls['crawl_depth'] = redir_live_urls['crawl_depth'].cat.reorder_categories(['0', '1', '2', '3', '4',
                                                                                 '5', '6', '7', '8', '9',
                                                                                        '10', '11', '12', '13', '14',
                                                                                        'Not Set',
                                                                                       ])
redir_live_urls = redir_live_urls.loc[redir_live_urls.host == website]
del redir_live_urls['host']
print(redir_live_urls.shape)
Redir_live_urls

(4055, 6)
Sitebulb data

Al filtrar filas para URL indexables y seleccionar las columnas relevantes, ahora tenemos un marco de datos más optimizado (piense en la versión de Pandas de una pestaña de hoja de cálculo).

Explorando la distribución de enlaces internos

Ahora estamos listos para ver los datos y tener una idea de cómo se distribuyen los enlaces internos en general y por profundidad del sitio.

from plotnine import *
import matplotlib.pyplot as plt
pd.set_option('display.max_colwidth', None)
%matplotlib inline

# Distribution of internal links to URL by site level
ove_intlink_dist_plt = (ggplot(redir_live_urls, aes(x = 'no_internal_links_to_url')) + 
                    geom_histogram(fill = 'blue', alpha = 0.6, bins = 7) +
                    labs(y = '# Internal Links to URL') + 
                    theme_classic() +            
                    theme(legend_position = 'none')
                   )

ove_intlink_dist_plt
Internal Links to URL vs No Internal Links to URL

De lo anterior podemos ver abrumadoramente que la mayoría de las páginas no tienen enlaces, por lo que mejorar los enlaces internos sería una oportunidad importante para mejorar el SEO aquí.

Veamos algunas estadísticas a nivel del sitio.

crawl_depth
0             1
1            70
10            5
11            1
12            1
13            2
14            1
2           303
3           378
4           347
5           253
6           194
7            96
8            33
9            19
Not Set    2351
dtype: int64

La tabla anterior muestra la distribución aproximada de los enlaces internos por nivel de sitio, incluido el promedio (media) y la mediana (50% del cuantil).

Esto es junto con la variación dentro del nivel del sitio (desviación estándar estándar), que nos dice qué tan cerca del promedio están las páginas dentro del nivel del sitio;

De lo anterior podemos suponer que el promedio por nivel de sitio, con la excepción de la página de inicio (profundidad de rastreo 0) y las páginas de primer nivel (profundidad de rastreo 1), oscila entre 0 y 4 por URL.

Para un enfoque más visual:

# Distribution of internal links to URL by site level
intlink_dist_plt = (ggplot(redir_live_urls, aes(x = 'crawl_depth', y = 'no_internal_links_to_url')) + 
                    geom_boxplot(fill = 'blue', alpha = 0.8) +
                    labs(y = '# Internal Links to URL', x = 'Site Level') + 
                    theme_classic() +            
                    theme(legend_position = 'none')
                   )

intlink_dist_plt.save(filename = 'images/1_intlink_dist_plt.png', height=5, width=5, units = 'in', dpi=1000)
intlink_dist_plt
Internal Links to URL vs Site Level Links

El gráfico anterior confirma nuestros comentarios anteriores de que la página de inicio y las páginas vinculadas directamente desde ella reciben la mayor parte de los enlaces.

Con las escalas como están, no tenemos mucha vista sobre la distribución de los niveles inferiores.. Enmendaremos esto tomando un logaritmo del eje y:

# Distribution of internal links to URL by site level
from mizani.formatters import comma_format

intlink_dist_plt = (ggplot(redir_live_urls, aes(x = 'crawl_depth', y = 'no_internal_links_to_url')) + 
                    geom_boxplot(fill = 'blue', alpha = 0.8) +
                    labs(y = '# Internal Links to URL', x = 'Site Level') + 
                    scale_y_log10(labels = comma_format()) + 
                    theme_classic() +            
                    theme(legend_position = 'none')
                   )

intlink_dist_plt.save(filename = 'images/1_log_intlink_dist_plt.png', height=5, width=5, units = 'in', dpi=1000)
intlink_dist_plt
Internal Links to URL vs Site Level Links

Lo anterior muestra la misma distribución de los enlaces con la vista logarítmica, lo que nos ayuda a confirmar los promedios de distribución para los niveles inferiores.. Esto es mucho más fácil de visualizar.

Dada la disparidad entre los primeros dos niveles del sitio y el resto del sitio, esto es indicativo de una distribución sesgada.

Como resultado, tomaré un logaritmo de los enlaces internos, lo que ayudará a normalizar la distribución.

Ahora tenemos el número normalizado de enlaces, que vamos a visualizar:

# Distribution of internal links to URL by site level
intlink_dist_plt = (ggplot(redir_live_urls, aes(x = 'crawl_depth', y = 'log_intlinks')) + 
                    geom_boxplot(fill = 'blue', alpha = 0.8) +
                    labs(y = '# Log Internal Links to URL', x = 'Site Level') + 
                    #scale_y_log10(labels = comma_format()) + 
                    theme_classic() +            
                    theme(legend_position = 'none')
                   )

intlink_dist_plt
Log Internal Links to URL vs Site Level Links

De lo anterior, la distribución parece mucho menos sesgada, ya que las cajas (rango intercuartílico) tienen un cambio de paso más gradual de nivel de sitio a nivel de sitio.

Esto nos prepara muy bien para analizar los datos antes de diagnosticar qué URL no están optimizadas desde el punto de vista de los enlaces internos.

Cuantificación de los problemas

El siguiente código calculará el cuantil 35 inferior (término de ciencia de datos para percentil) para cada profundidad del sitio.

# internal links in under/over indexing at site level
# count of URLs under indexed for internal link counts

quantiled_intlinks = redir_live_urls.groupby('crawl_depth').agg({'log_intlinks': 
                                                                 [quantile_lower]}).reset_index()
quantiled_intlinks = quantiled_intlinks.rename(columns = {'crawl_depth_': 'crawl_depth', 
                                                          'log_intlinks_quantile_lower': 'sd_intlink_lowqua'})
quantiled_intlinks
Crawl Depth and Internal Links

Lo anterior muestra los cálculos.. Los números no tienen sentido para un profesional de SEO en esta etapa, ya que son arbitrarios y tienen el propósito de proporcionar un límite para las URL con enlaces inferiores en cada nivel del sitio.

Ahora que tenemos la tabla, los fusionaremos con el conjunto de datos principal para determinar si la URL fila por fila está enlazada o no.

# join quantiles to main df and then count
redir_live_urls_underidx = redir_live_urls.merge(quantiled_intlinks, on = 'crawl_depth', how = 'left')

redir_live_urls_underidx['sd_int_uidx'] = redir_live_urls_underidx.apply(sd_intlinkscount_underover, axis=1)
redir_live_urls_underidx['sd_int_uidx'] = np.where(redir_live_urls_underidx['crawl_depth'] == 'Not Set', 1,
                                                   redir_live_urls_underidx['sd_int_uidx'])

redir_live_urls_underidx

Ahora tenemos un marco de datos con cada URL marcada como un enlace debajo de la columna «sd_int_uidx» como 1.

Esto nos coloca en posición de sumar la cantidad de páginas del sitio con enlaces inferiores por profundidad del sitio:

# Summarise int_udx by site level
intlinks_agged = redir_live_urls_underidx.groupby('crawl_depth').agg({'sd_int_uidx': ['sum', 'count']}).reset_index()
intlinks_agged = intlinks_agged.rename(columns = {'crawl_depth_': 'crawl_depth'})
intlinks_agged['sd_uidx_prop'] = intlinks_agged.sd_int_uidx_sum / intlinks_agged.sd_int_uidx_count * 100
print(intlinks_agged)

  crawl_depth  sd_int_uidx_sum  sd_int_uidx_count  sd_uidx_prop
0            0                0                  1      0.000000
1            1               41                 70     58.571429
2            2               66                303     21.782178
3            3              110                378     29.100529
4            4              109                347     31.412104
5            5               68                253     26.877470
6            6               63                194     32.474227
7            7                9                 96      9.375000
8            8                6                 33     18.181818
9            9                6                 19     31.578947
10          10                0                  5      0.000000
11          11                0                  1      0.000000
12          12                0                  1      0.000000
13          13                0                  2      0.000000
14          14                0                  1      0.000000
15     Not Set             2351               2351    100.000000

Ahora vemos que, a pesar de que la página de profundidad 1 del sitio tiene un número de enlaces por URL superior al promedio, todavía hay 41 páginas con enlaces inferiores.

Para ser más visual:

# plot the table
depth_uidx_plt = (ggplot(intlinks_agged, aes(x = 'crawl_depth', y = 'sd_int_uidx_sum')) + 
                    geom_bar(stat = 'identity', fill = 'blue', alpha = 0.8) +
                    labs(y = '# Under Linked URLs', x = 'Site Level') + 
                    scale_y_log10() + 
                    theme_classic() +            
                    theme(legend_position = 'none')
                   )

depth_uidx_plt.save(filename = 'images/1_depth_uidx_plt.png', height=5, width=5, units = 'in', dpi=1000)
depth_uidx_plt
Under Linked URLs vs Site Level

Con la excepción de las URL del mapa del sitio XML, la distribución de las URL con enlaces subyacentes parece normal, como lo indica la forma de campana cercana.. La mayoría de las URL con enlaces inferiores se encuentran en los niveles de sitio 3 y 4.

Exportación de la lista de URL con enlaces inferiores

Ahora que tenemos un control sobre las URL con enlaces inferiores por nivel de sitio, podemos exportar los datos y encontrar soluciones creativas para cerrar las brechas en la profundidad del sitio, como se muestra a continuación.

# data dump of under performing backlinks
underlinked_urls = redir_live_urls_underidx.loc[redir_live_urls_underidx.sd_int_uidx == 1]
underlinked_urls = underlinked_urls.sort_values(['crawl_depth', 'no_internal_links_to_url'])
underlinked_urls.to_csv('exports/underlinked_urls.csv')
underlinked_urls
Sitebulb data

Otras técnicas de ciencia de datos para enlaces internos

Cubrimos brevemente la motivación para mejorar los enlaces internos de un sitio antes de explorar cómo se distribuyen los enlaces internos en el sitio por nivel de sitio.

Luego procedimos a cuantificar el alcance del problema de enlaces subyacentes tanto numérica como visualmente antes de exportar los resultados para recomendaciones.

Naturalmente, el nivel del sitio es solo un aspecto de los enlaces internos que se pueden explorar y analizar estadísticamente.

Otros aspectos que podrían aplicar técnicas de ciencia de datos a los enlaces internos incluyen y obviamente no se limitan a:

  • Autoridad a nivel de página externa.
  • Relevancia del texto ancla.
  • Intención de búsqueda.
  • Buscar recorrido del usuario.

¿Qué aspectos le gustaría ver cubiertos?

Por favor, deje un comentario a continuación.

Más recursos:

Imagen destacada: Shutterstock/Optimarc

Leer el articulo original en Search Engine Journal.

¡Danos un Voto!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

¿Tienes una pregunta?

Luis Narciso
Sobre SEO
(Posicionamiento Web)

Frank Fajardo
Sobre Diseño Web, Anuncios, Diseño y Redes Sociales