使用JavaScript检测文本中的URL


151

有没有人建议检测一组字符串中的URL?

arrayOfStrings.forEach(function(string){
  // detect URLs in strings and do something swell,
  // like creating elements with links.
});

更新:我最终使用此正则表达式进行链接检测…显然几年后。

kLINK_DETECTION_REGEX = /(([a-z]+:\/\/)?(([a-z0-9\-]+\.)+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(\?[a-z0-9+_\-\.%=&]*)?)?(#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(\s+|$)/gi

完整的帮助程序(带有可选的车把支持)在#1654670处


11
尝试列出有限的TLD可能不是一个好主意,因为它们会不断创建新的TLD。
Maxy-B

同意。有时我们需要的是带有TLD的可更新代码。实际上可以通过构建脚本将TLD附加到正则表达式中,也可以通过动态代码更新TLD。生活中的某些事物意味着像TLD和时区那样要标准化。有限控制对于验证真实世界地址用例的现有“ TLD”可验证URL可能是不错的选择。
Edward Chan JW

Answers:


217

首先,您需要一个与网址匹配的好的正则表达式。这很难做到。看到这里这里这里

...几乎所有内容都是有效的网址。有一些标点符号规则将其拆分。没有标点符号,您仍然有一个有效的URL。

仔细检查RFC,看看是否可以构造“无效” URL。规则非常灵活。

例如:::::,一个有效的URL。路径是":::::"。漂亮的文件名,但是有效的文件名。

此外,/////也是有效的网址。netloc(“主机名”)为""。路径是"///"。再次,愚蠢。也有效。此URL规范化为"///" 等效的URL 。

类似的东西"bad://///worse/////" 是完全有效的。哑巴但有效。

无论如何,这个答案并不意味着为您提供最佳的正则表达式,而是证明如何使用JavaScript进行文本内的字符串包装。

好的,所以让我们使用这个: /(https?:\/\/[^\s]+)/g

同样,这是一个不好的正则表达式。它将有许多误报。但是,对于此示例来说已经足够了。

function urlify(text) {
  var urlRegex = /(https?:\/\/[^\s]+)/g;
  return text.replace(urlRegex, function(url) {
    return '<a href="' + url + '">' + url + '</a>';
  })
  // or alternatively
  // return text.replace(urlRegex, '<a href="$1">$1</a>')
}

var text = 'Find me at http://www.example.com and also at http://stackoverflow.com';
var html = urlify(text);

console.log(html)

// html now looks like:
// "Find me at <a href="http://www.example.com">http://www.example.com</a> and also at <a href="http://stackoverflow.com">http://stackoverflow.com</a>"

因此总而言之,请尝试:

$$('#pad dl dd').each(function(element) {
    element.innerHTML = urlify(element.innerHTML);
});

4
“许多误报”的一些示例将大大改善此答案。否则,未来的Google员工将只剩下一些(也许有效?)FUD。
cmcculloh 2014年

我不知道您可以将功能作为第二个参数传递给.replace:|
Aamir Afridi 2015年

4
很好,但是它会text="Find me at http://www.example.com, and also at http://stackoverflow.com."在两个404的后面加上标点符号来做“错误”的事情。一些用户已经意识到这一点,并且会在标点符号之前在URL后面添加一个空格,以避免损坏,但是我使用的大多数链接器(Gmail,etherpad,phabricator)都将单独的尾随标点符号与URL分开。
skierpage

如果文本已经包含锚定网址,则可以使用功能removeAnchors(text){var div = $('<div> </ div>')。html(text); div.find('a')。contents()。unwrap(); 返回div.text(); }首先删除锚点,然后再返回text.replace
Muneeb Mirza,

如果文本已经包含锚定的URL,则您正在使用jquery删除锚定,但是我正在使用Angular。如何在Angular中删除锚点?
Sachin Jagtap '19

132

我最终将其用作正则表达式:

var urlRegex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;

URL中不包括结尾标点符号。Crescent的功能就像一个魅力:)一样:

function linkify(text) {
    var urlRegex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
    return text.replace(urlRegex, function(url) {
        return '<a href="' + url + '">' + url + '</a>';
    });
}

4
最后,在最明显的情况下真正有效的正则表达式!这个值得一书签。我测试了Google搜索中的数千个示例,直到找到了。
伊斯梅尔2015年

6
简单又漂亮!但是urlRegex应该在外部 linkify将其定义为编译成本很高。
BM

1
这无法检测到完整的URL:disney.wikia.com/wiki/Pua_(Moana)
Jry9972

1
()在每个字符列表中添加了它,现在可以使用了。
Guillaume F.

3
它无法检测到仅以www开头的网址。例如:www.facebook.com
CraZyDroiD '18 -10-11

51

我在这个问题上搜索了很长时间,然后发现我有一个Android方法android.text.util.Linkify,它利用一些非常强大的正则表达式来完成此任务。幸运的是,Android是开源的。

他们使用几种不同的模式来匹配不同类型的网址。您可以在以下位置找到所有这些文件:http : //grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.0_r1/android/text/util/Regex.java#Regex。 0WEB_URL_PATTERN

如果您只关心与WEB_URL_PATTERN匹配的URL,即符合RFC 1738规范的URL,则可以使用以下命令:

/((?:(http|https|Http|Https|rtsp|Rtsp):\/\/(?:(?:[a-zA-Z0-9\$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,64}(?:\:(?:[a-zA-Z0-9\$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,25})?\@)?)?((?:(?:[a-zA-Z0-9][a-zA-Z0-9\-]{0,64}\.)+(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnrwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eouw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw]))|(?:(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])))(?:\:\d{1,5})?)(\/(?:(?:[a-zA-Z0-9\;\/\?\:\@\&\=\#\~\-\.\+\!\*\'\(\)\,\_])|(?:\%[a-fA-F0-9]{2}))*)?(?:\b|$)/gi;

这是源的全文:

"((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+ "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}\\.)+"   // named host
+ "(?:"   // plus top level domain
+ "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ "|(?:biz|b[abdefghijmnorstvwyz])"
+ "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+ "|d[ejkmoz]"
+ "|(?:edu|e[cegrstu])"
+ "|f[ijkmor]"
+ "|(?:gov|g[abdefghilmnpqrstuwy])"
+ "|h[kmnrtu]"
+ "|(?:info|int|i[delmnoqrst])"
+ "|(?:jobs|j[emop])"
+ "|k[eghimnrwyz]"
+ "|l[abcikrstuvy]"
+ "|(?:mil|mobi|museum|m[acdghklmnopqrstuvwxyz])"
+ "|(?:name|net|n[acefgilopruz])"
+ "|(?:org|om)"
+ "|(?:pro|p[aefghklmnrstwy])"
+ "|qa"
+ "|r[eouw]"
+ "|s[abcdeghijklmnortuvyz]"
+ "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+ "|u[agkmsyz]"
+ "|v[aceginu]"
+ "|w[fs]"
+ "|y[etu]"
+ "|z[amw]))"
+ "|(?:(?:25[0-5]|2[0-4]" // or ip address
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
+ "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ "|[1-9][0-9]|[0-9])))"
+ "(?:\\:\\d{1,5})?)" // plus option port number
+ "(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~"  // plus option query params
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ "(?:\\b|$)";

如果您真的想花哨的话,也可以测试电子邮件地址。电子邮件地址的正则表达式为:

/[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}\\@[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25})+/gi

PS:以上正则表达式支持的顶级域是截至2007年6月的最新域。有关最新列表,您需要检查https://data.iana.org/TLD/tlds-alpha-by-domain.txt


3
由于您具有不区分大小写的正则表达式,因此无需指定a-zA-Zhttp|https|Http|Https|rtsp|Rtsp
Ry-

4
很好,但是我不确定我是否会使用它。对于大多数用例,我宁愿接受一些误报,而不是使用依赖于TLD硬编码列表的方法。如果在代码中列出了TLD,则可以保证有一天会过时,如果可以避免的话,我宁愿不对代码进行将来的强制性维护。
Mark Amery

3
这项工作有101%的时间有效,但不幸的是,它还会找到没有空格的URL。如果我在hello@mydomain.com上进行匹配,则会捕获“ mydomain.com”。有没有一种方法可以改进它,使其仅在其前面有空格的情况下才能捕获?
Deminetix 2015年

还要注意,这非常适合捕获用户输入的网址
Deminetix 2015年

需要注意的是grepcode.com不再了,这里就是我在Android源代码到正确的位置的链接。我认为Android使用的regex可能自2013年以来已更新(原始帖子),但自2015年以来似乎未进行过更新,因此可能缺少一些较新的TLD。
詹姆斯

19

根据新月新答案

如果您要检测带有http://不带http://的链接和www。您可以使用以下内容

function urlify(text) {
    var urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
    //var urlRegex = /(https?:\/\/[^\s]+)/g;
    return text.replace(urlRegex, function(url,b,c) {
        var url2 = (c == 'www.') ?  'http://' +url : url;
        return '<a href="' +url2+ '" target="_blank">' + url + '</a>';
    }) 
}

这是一个很好的解决方案,但是我还想检查文本是否不应该包含href。我尝试过此正则表达式= /((?!href)((https?:\/\/)|(www\.)|(mailto:))[^\s]+)/gi,但它不起作用。您能为我提供帮助吗,或者上述正则表达式为何不起作用?
萨钦·贾格塔普

我喜欢您还向返回的输出中添加了target =“ _ blank”。这个版本是我想要的。没有什么可以超过大多数链接的顶部(否则我会使用Linkifyjs)。
迈克尔·库伯勒


6

还可以进一步改进功能以渲染图像:

function renderHTML(text) { 
    var rawText = strip(text)
    var urlRegex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;   

    return rawText.replace(urlRegex, function(url) {   

    if ( ( url.indexOf(".jpg") > 0 ) || ( url.indexOf(".png") > 0 ) || ( url.indexOf(".gif") > 0 ) ) {
            return '<img src="' + url + '">' + '<br/>'
        } else {
            return '<a href="' + url + '">' + url + '</a>' + '<br/>'
        }
    }) 
} 

或链接到完整尺寸图像的缩略图:

return '<a href="' + url + '"><img style="width: 100px; border: 0px; -moz-border-radius: 5px; border-radius: 5px;" src="' + url + '">' + '</a>' + '<br/>'

这是strip()函数,该函数通过删除任何现有的html来预处理文本字符串以确保一致性。

function strip(html) 
    {  
        var tmp = document.createElement("DIV"); 
        tmp.innerHTML = html; 
        var urlRegex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;   
        return tmp.innerText.replace(urlRegex, function(url) {     
        return '\n' + url 
    })
} 

2
let str = 'https://example.com is a great site'
str.replace(/(https?:\/\/[^\s]+)/g,"<a href='$1' target='_blank' >$1</a>")

短代码大工作!...

结果:-

 <a href="https://example.com" target="_blank" > https://example.com </a>

1

现有npm软件包:url-regex,只需使用yarn add url-regex或进行安装,npm install url-regex并按以下方式使用:

const urlRegex = require('url-regex');

const replaced = 'Find me at http://www.example.com and also at http://stackoverflow.com or at google.com'
  .replace(urlRegex({strict: false}), function(url) {
     return '<a href="' + url + '">' + url + '</a>';
  });

0

tmp.innerText未定义。您应该使用tmp.innerHTML

function strip(html) 
    {  
        var tmp = document.createElement("DIV"); 
        tmp.innerHTML = html; 
        var urlRegex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;   
        return tmp.innerHTML .replace(urlRegex, function(url) {     
        return '\n' + url 
    })

0

试试这个:

function isUrl(s) {
    if (!isUrl.rx_url) {
        // taken from https://gist.github.com/dperini/729294
        isUrl.rx_url=/^(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i;
        // valid prefixes
        isUrl.prefixes=['http:\/\/', 'https:\/\/', 'ftp:\/\/', 'www.'];
        // taken from https://w3techs.com/technologies/overview/top_level_domain/all
        isUrl.domains=['com','ru','net','org','de','jp','uk','br','pl','in','it','fr','au','info','nl','ir','cn','es','cz','kr','ua','ca','eu','biz','za','gr','co','ro','se','tw','mx','vn','tr','ch','hu','at','be','dk','tv','me','ar','no','us','sk','xyz','fi','id','cl','by','nz','il','ie','pt','kz','io','my','lt','hk','cc','sg','edu','pk','su','bg','th','top','lv','hr','pe','club','rs','ae','az','si','ph','pro','ng','tk','ee','asia','mobi'];
    }

    if (!isUrl.rx_url.test(s)) return false;
    for (let i=0; i<isUrl.prefixes.length; i++) if (s.startsWith(isUrl.prefixes[i])) return true;
    for (let i=0; i<isUrl.domains.length; i++) if (s.endsWith('.'+isUrl.domains[i]) || s.includes('.'+isUrl.domains[i]+'\/') ||s.includes('.'+isUrl.domains[i]+'?')) return true;
    return false;
}

function isEmail(s) {
    if (!isEmail.rx_email) {
        // taken from http://stackoverflow.com/a/16016476/460084
        var sQtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
        var sDtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
        var sAtom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
        var sQuotedPair = '\\x5c[\\x00-\\x7f]';
        var sDomainLiteral = '\\x5b(' + sDtext + '|' + sQuotedPair + ')*\\x5d';
        var sQuotedString = '\\x22(' + sQtext + '|' + sQuotedPair + ')*\\x22';
        var sDomain_ref = sAtom;
        var sSubDomain = '(' + sDomain_ref + '|' + sDomainLiteral + ')';
        var sWord = '(' + sAtom + '|' + sQuotedString + ')';
        var sDomain = sSubDomain + '(\\x2e' + sSubDomain + ')*';
        var sLocalPart = sWord + '(\\x2e' + sWord + ')*';
        var sAddrSpec = sLocalPart + '\\x40' + sDomain; // complete RFC822 email address spec
        var sValidEmail = '^' + sAddrSpec + '$'; // as whole string

        isEmail.rx_email = new RegExp(sValidEmail);
    }

    return isEmail.rx_email.test(s);
}

也将认识到的URL,例如 google.comhttp://www.google.blahttp://google.blawww.google.bla但不google.bla


0

您可以使用这样的正则表达式提取正常的网址格式。

(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})

如果您需要更复杂的模式,请使用这样的库。

https://www.npmjs.com/package/pattern-dreamer


目的是(?:www\.|(?!www))什么?为什么应该wwwww.com无效?
Toto

你是对的。实际上,我只是把它当作正则表达式使用了。我建议使用上面的链接库。在网址检测中我们应该考虑很多情况,因此正则表达式应该更复杂。
康·安德鲁

0

通用的面向对象解决方案

对于像我这样使用不允许直接操作DOM的angular框架的人,我创建了一个函数,该函数接受字符串并返回url/ plainText对象的数组,该数组可用于创建所需的任何UI表示形式。

网址正则表达式

对于URL匹配,我使用了(稍作修改)h0mayun正则表达式:/(?:(?:https?:\/\/)|(?:www\.))[^\s]+/g

我的功能也下降标点符号从诸如URL的结束.,我相信,更多的时候会比结束一个合法的URL实际标点符号(但它可能是!这是不严谨的科学作为其他的答案很好地解释)。对于我申请了将正则表达式跟随到匹配的URL上/^(.+?)([.,?!'"]*)$/

打字稿代码

    export function urlMatcherInText(inputString: string): UrlMatcherResult[] {
        if (! inputString) return [];

        const results: UrlMatcherResult[] = [];

        function addText(text: string) {
            if (! text) return;

            const result = new UrlMatcherResult();
            result.type = 'text';
            result.value = text;
            results.push(result);
        }

        function addUrl(url: string) {
            if (! url) return;

            const result = new UrlMatcherResult();
            result.type = 'url';
            result.value = url;
            results.push(result);
        }

        const findUrlRegex = /(?:(?:https?:\/\/)|(?:www\.))[^\s]+/g;
        const cleanUrlRegex = /^(.+?)([.,?!'"]*)$/;

        let match: RegExpExecArray;
        let indexOfStartOfString = 0;

        do {
            match = findUrlRegex.exec(inputString);

            if (match) {
                const text = inputString.substr(indexOfStartOfString, match.index - indexOfStartOfString);
                addText(text);

                var dirtyUrl = match[0];
                var urlDirtyMatch = cleanUrlRegex.exec(dirtyUrl);
                addUrl(urlDirtyMatch[1]);
                addText(urlDirtyMatch[2]);

                indexOfStartOfString = match.index + dirtyUrl.length;
            }
        }
        while (match);

        const remainingText = inputString.substr(indexOfStartOfString, inputString.length - indexOfStartOfString);
        addText(remainingText);

        return results;
    }

    export class UrlMatcherResult {
        public type: 'url' | 'text'
        public value: string
    }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.