Búsqueda de sitios web

javascript - Extraer el nombre del host de una cadena


Me gustaría coincidir solo con la raíz de una URL y no con toda la URL en una cadena de texto. Dado:

http://www.youtube.com/watch?v=ClkQA2Lb_iE
http://youtu.be/ClkQA2Lb_iE
http://www.example.com/12xy45
http://example.com/random

Quiero obtener las 2 últimas instancias que resuelvan al dominio www.example.com o example.com.

Escuché que las expresiones regulares son lentas y esta sería mi segunda expresión regular en la página, así que si hay alguna manera de hacerlo sin expresiones regulares, házmelo saber.

Estoy buscando una versión en JS/jQuery de esta solución.

Respuesta #1

Un truco sencillo sin usar expresiones regulares:

var tmp        = document.createElement ('a');
;   tmp.href   = "http://www.example.com/12xy45";

// tmp.hostname will now contain 'www.example.com'
// tmp.host will now contain hostname and port 'www.example.com:80'

Envuelve lo anterior en una función como la siguiente y tendrás una forma excelente de extraer la parte del dominio de un URI.

function url_domain(data) {
  var    a      = document.createElement('a');
         a.href = data;
  return a.hostname;
}

Respuesta #2

Te doy 3 posibles soluciones:

  1. Usando un paquete npm psl que extrae cualquier cosa que le lances.
  2. Usando mi implementación personalizada extractRootDomain que funciona en la mayoría de los casos.
  3. URL(url).hostname funciona, pero no para todos los casos extremos. Haz clic en "Ejecutar fragmento" para ver cómo se ejecuta contra ellos.

1. Usando el paquete npm psl (Lista de Sufijos Públicos)

La "Lista de sufijos públicos" es una lista de todos los sufijos y reglas de dominio válidos, no solo dominios de nivel superior con código de país, sino también caracteres Unicode que se considerarían el dominio raíz (es decir, www.食狮.公司.cn, b.c.kobe.jp, etc.). Lea más sobre esto aquí.

Intenta :

npm install --save psl

Luego, con mi implementación de "extractHostname", ejecuta:

let psl = require('psl');
let url = 'http://www.youtube.com/watch?v=ClkQA2Lb_iE';
psl.get(extractHostname(url)); // returns youtube.com

2. Mi implementación personalizada de extractRootDomain

A continuación está mi implementación y también funciona con una variedad de posibles entradas de URL.

function extractHostname(url) {
  var hostname;
  //find & remove protocol (http, ftp, etc.) and get hostname

  if (url.indexOf("//") > -1) {
    hostname = url.split('/')[2];
  } else {
    hostname = url.split('/')[0];
  }

  //find & remove port number
  hostname = hostname.split(':')[0];
  //find & remove "?"
  hostname = hostname.split('?')[0];

  validateDomain(hostname);
  return hostname;
}

// Warning: you can use this function to extract the "root" domain, but it will not be as accurate as using the psl package.

function extractRootDomain(url) {
  var domain = extractHostname(url),
  splitArr = domain.split('.'),
  arrLen = splitArr.length;

  //extracting the root domain here
  //if there is a subdomain
  if (arrLen > 2) {
    domain = splitArr[arrLen - 2] + '.' + splitArr[arrLen - 1];
    //check to see if it's using a Country Code Top Level Domain (ccTLD) (i.e. ".me.uk")
    if (splitArr[arrLen - 2].length == 2 && splitArr[arrLen - 1].length == 2) {
      //this is using a ccTLD
      domain = splitArr[arrLen - 3] + '.' + domain;
    }
  }
  validateDomain(domain);
  return domain;
}

const urlHostname = url => {
  try {
    return new URL(url).hostname;
  }
  catch(e) { return e; }
};

const validateDomain = s => {
  try {
    new URL("https://" + s);
    return true;
  }
  catch(e) {
    console.error(e);
    return false;
  }
};

const urls = [
    "http://www.blog.classroom.me.uk/index.php",
    "http://www.youtube.com/watch?v=ClkQA2Lb_iE",
    "https://www.youtube.com/watch?v=ClkQA2Lb_iE",
    "www.youtube.com/watch?v=ClkQA2Lb_iE",
    "ftps://ftp.websitename.com/dir/file.txt",
    "websitename.com:1234/dir/file.txt",
    "ftps://websitename.com:1234/dir/file.txt",
    "example.com?param=value",
    "https://facebook.github.io/jest/",
    "//youtube.com/watch?v=ClkQA2Lb_iE",
    "www.食狮.公司.cn",
    "b.c.kobe.jp",
    "a.d.kyoto.or.jp",
    "http://localhost:4200/watch?v=ClkQA2Lb_iE",
    "a.d.kyoto.or.j|p",
];

const test = (method, arr) => console.log(
`=== Testing "${method.name}" ===\n${arr.map(url => method(url)).join("\n")}\n`);

test(extractHostname, urls);
test(extractRootDomain, urls);
test(urlHostname, urls);

Independientemente de tener el protocolo o incluso el número de puerto, puedes extraer el dominio. Esta es una solución muy simplificada, sin expresiones regulares, así que creo que esto funcionará dado el conjunto de datos que se nos proporcionó en la pregunta.

Esto no proporciona ningún tipo de validación de nombre de dominio, así que si deseas añadir una, puedes hacerlo tú mismo mediante

3. URL(url).hostname

URL(url).hostname es una solución válida, pero no funciona bien con algunos casos extremos que he abordado. Como puedes ver en mi última prueba, no le gustan algunas de las URLs. Sin embargo, definitivamente puedes usar una combinación de mis soluciones para que todo funcione.

*¡Gracias @Timmerz, @renoirb, @rineez, @BigDong, @ra00l, @ILikeBeansTacos, @CharlesRobertson por sus sugerencias! @ross-allen, ¡gracias por reportar el error!

Respuesta #3

No hay necesidad de analizar la cadena, solo pasa tu URL como un argumento al constructor URL:

const url = 'http://www.youtube.com/watch?v=ClkQA2Lb_iE';
const { hostname } = new URL(url);

console.assert(hostname === 'www.youtube.com');

Respuesta #4

Prueba esto:

var matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
var domain = matches && matches[1];  // domain will be null if no match is found

Si quieres excluir el puerto de tu resultado, usa esta expresión en su lugar:

/^https?\:\/\/([^\/:?#]+)(?:[\/:?#]|$)/i

Editar: Para evitar que se coincida con dominios específicos, use una mirada hacia adelante negativa. (?!youtube.com)

/^https?\:\/\/(?!(?:www\.)?(?:youtube\.com|youtu\.be))([^\/:?#]+)(?:[\/:?#]|$)/i

Respuesta #5

Hay dos buenas soluciones para esto, dependiendo de si necesitas optimizar el rendimiento o no (¡y sin dependencias externas!):

1. Usa URL.hostname para la legibilidad

La solución más limpia y fácil es usar URL.hostname.

const getHostname = (url) => {
  // use URL constructor and return hostname
  return new URL(url).hostname;
}

// tests
console.log(getHostname("https://stackoverflow.com/questions/8498592/extract-hostname-name-from-string/"));
console.log(getHostname("https://developer.mozilla.org/en-US/docs/Web/API/URL/hostname"));

URL.hostname es parte de la API de URL, soportada por todos los navegadores principales excepto IE (caniuse). Usa un polyfill de URL si necesitas soportar navegadores antiguos.

Bono: ¡usar el constructor de URL también te dará acceso a otras propiedades y métodos de URL!


2. Usa RegEx para rendimiento

URL.hostname debería ser tu elección para la mayoría de los casos de uso. Sin embargo, todavía es mucho más lento que esta expresión regular (pruébalo tú mismo en jsPerf):

const getHostnameFromRegex = (url) => {
  // run against regex
  const matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
  // extract hostname (will be null if no match is found)
  return matches && matches[1];
}

// tests
console.log(getHostnameFromRegex("https://stackoverflow.com/questions/8498592/extract-hostname-name-from-string/"));
console.log(getHostnameFromRegex("https://developer.mozilla.org/en-US/docs/Web/API/URL/hostname"));

Resumen

Probablemente deberías usar URL.hostname. Si necesitas procesar una cantidad increíblemente grande de URLs (donde el rendimiento sería un factor), considera usar RegEx.

Respuesta #6

Analizar una URL puede ser complicado porque puedes tener números de puerto y caracteres especiales. Por lo tanto, recomiendo usar algo como parseUri para que lo haga por ti. Dudo que el rendimiento vaya a ser un problema a menos que estés analizando cientos de URLs.

Respuesta #7

Si terminas en esta página y estás buscando el mejor REGEX de URLS, prueba este:

^(?:https?:)?(?:\/\/)?([^\/\?]+)

https://regex101.com/r/pX5dL9/1

Puedes usarlo como se muestra a continuación y también de manera insensible a mayúsculas y minúsculas para coincidir con HTTPS y HTTP también.

const match = str.match(/^(?:https?:)?(?:\/\/)?([^\/\?]+)/i);
const hostname = match && match[1];

Funciona para URLs sin http://, con http, con https, solo con/y no captura la ruta ni la ruta de consulta.

Buena suerte

Respuesta #8

Intenté usar las soluciones dadas, la elegida fue exagerada para mi propósito y "Crear un elemento" me causa problemas.

Aún no está listo para el Puerto en la URL. Espero que alguien lo encuentre útil

function parseURL(url){
    parsed_url = {}

    if ( url == null || url.length == 0 )
        return parsed_url;

    protocol_i = url.indexOf('://');
    parsed_url.protocol = url.substr(0,protocol_i);

    remaining_url = url.substr(protocol_i + 3, url.length);
    domain_i = remaining_url.indexOf('/');
    domain_i = domain_i == -1 ? remaining_url.length - 1 : domain_i;
    parsed_url.domain = remaining_url.substr(0, domain_i);
    parsed_url.path = domain_i == -1 || domain_i + 1 == remaining_url.length ? null : remaining_url.substr(domain_i + 1, remaining_url.length);

    domain_parts = parsed_url.domain.split('.');
    switch ( domain_parts.length ){
        case 2:
          parsed_url.subdomain = null;
          parsed_url.host = domain_parts[0];
          parsed_url.tld = domain_parts[1];
          break;
        case 3:
          parsed_url.subdomain = domain_parts[0];
          parsed_url.host = domain_parts[1];
          parsed_url.tld = domain_parts[2];
          break;
        case 4:
          parsed_url.subdomain = domain_parts[0];
          parsed_url.host = domain_parts[1];
          parsed_url.tld = domain_parts[2] + '.' + domain_parts[3];
          break;
    }

    parsed_url.parent_domain = parsed_url.host + '.' + parsed_url.tld;

    return parsed_url;
}

Ejecutando esto:

parseURL('https://www.facebook.com/100003379429021_356001651189146');

Resultado :

Object {
    domain : "www.facebook.com",
    host : "facebook",
    path : "100003379429021_356001651189146",
    protocol : "https",
    subdomain : "www",
    tld : "com"
}

Artículos relacionados