Jump to content
  • 0

разбор url с помощью регулярных выражений


Jenek
 Share

Question

Написал скрипт для отслеживания исходящих ссылок через Google Analytics, но в регулярных выражениях я не мастер (мягко говоря), кто-нибудь может посмотреть скрипт, особенно длинный регексп в функции isLinkExternal.

И еще, один человек сказал, что вешать обработчик на document не оптимально, куда его тогда повесить, неужели развесить по отдельным ссылкам будет оптимальней, что-то сомневаюсь?

При последней проверке еще одна проблема обнаружилась если клик на изображении заключенном в ссылку то пользователь переходит по ссылке и это не учитывается.

function isLinkExternal(link)
{
if(link.substring(0, 1) == '/' || link == '') return false;

var host = location.host.replace(/^www./, '');

var a = /^([a-z]+)://([^?#/:]+)/.exec(link); //место сомнений
var scheme = a[1];
var linkHost = a[2].replace(/^www./, '');

if(host != linkHost && (scheme=='http' || scheme=='https'))
return true;

return false;
}

$(document).ready(function () {

$(document).bind('click', function(e) {
var target = (window.event) ? e.srcElement : e.target;
if(target.nodeName == 'A')
if(isLinkExternal(target.href))
{
var link = target.href;
link = '/outgoing/' + link.replace(/:///, '/');
urchinTracker(link);
}
});
});

Link to comment
Share on other sites

25 answers to this question

Recommended Posts

  • 0
И еще, один человек сказал, что вешать обработчик на document не оптимально

Спросите человека как тогда оптимально (-:

var a = /^([a-z]+)://([^?#/:]+)/.exec(link); //место сомнений

^ - совпадение с начала строки

([a-z]+) - символы от a по z. минимум один символ, максимум - не ограничен плюс "захватывающие скобки"

: - просто двоеточие

все выше перечисленное это часть урла, отвечающая за протокол http://, ftp://

// - эквивалентно //

([^?#/:]+) - любые символы кроме ? # / : один или более раз плюс "захватывающие скобки"

Метод exec выполняет сопоставление строки с образцом, заданным регвыр. Если сопоставление с образцом закончилось неудачей, то возвращается значение null. В противном случае результатом является массив подстрок, соответствующих заданному образцу. В процессе сопоставления производится обновление всех свойств объекта регвыр (и тем самым всех свойств объекта RegExp).

В двух словах, мы отделяем протокол от остальной части урла и помещаем их в соответствующие переменные

 var scheme = a[1];
var linkHost = a[2].replace(/^www./, '');

Т.е. для http://htmlbook.ru/

a[0] == 'http://htmlbook.ru/'
a[1] == 'http'
a[2] == 'htmlbook.ru'

—--

При последней проверке еще одна проблема обнаружилась если клик на изображении заключенном в ссылку то пользователь переходит по ссылке и это не учитывается.

Просто в таком случае здесь

 $(document).bind('click', function(e) {
var target = (window.event) ? e.srcElement : e.target;
if(target.nodeName == 'A')
if(isLinkExternal(target.href))
{
var link = target.href;
link = '/outgoing/' + link.replace(/:///, '/');
urchinTracker(link);
}
});

target наверное равен не ссылке, а картинке. Т.е. нам нужно добавить дополнительное условие. Что-то типа:

if ((target.nodeName == 'IMG') && (typeof target.parentNode != null) && (target.parentNode.nodeName == 'A')) {
target = target.parentNode;
}

>Beware of bugs in the above code; I have only proved it correct, not tried it. Donald Knuth

ОФФТОП

Написал скрипт для отслеживания исходящих ссылок через Google Analytics

смахивает на скрытый СЕО (-:

Link to comment
Share on other sites

  • 0
смахивает на скрытый СЕО (-:

То есть спам? Вообще-то я чтобы понятно было зачем этот скрипт и что к чему, вдруг кто-нибудь предложит более оптимальное решение проблемы в целом, часто в вопросах не хватает данных и приходится жаловаться на отсутствие плагина телепатии. Да и сервер лег еще раньше чем я тут ссылку поставил, и далеко не от наплыва посетителей, а от дохлого винта :D Чтобы не было никаких подозрений убрал ссылку, а чтобы их ни у кого не возникало стоит сделать все ссылки с форума nofollow.

Спасибо, конечно, за подробное объяснение, но не зная этого, я вряд ли написал бы этот скрипт, мне интересно будут ли эти выражения достаточно корректно работать для всех url (предыдущая версия косячила немного, от нее здесь ничего не осталось, но я теперь весь в сомнениях), кстати наверное нужно добавить проверку на null результата exec.

Link to comment
Share on other sites

  • 0
Спасибо, конечно, за подробное объяснение, но не зная этого, я вряд ли написал бы этот скрипт,

Дык, выражение "место сомнений" у меня сразу сассоциировалось с "не понимаю что там происходит" (-:

мне интересно будут ли эти выражения достаточно корректно работать для всех url (предыдущая версия косячила немного, от нее здесь ничего не осталось, но я теперь весь в сомнениях)

здесь

 if(host != linkHost && (scheme=='http' || scheme=='https'))
return true;

scheme может быть равна ftp, что тоже является внешней ссылкой...

...имхо надо проверять так

scheme != ""

кстати наверное нужно добавить проверку на null результата exec.

думаю не помешает

на счет картинок я уже написал выше

можно сделать регвыр так

/^(http|https|ftp)://([w.-]+)/i

Link to comment
Share on other sites

  • 0

Предложенный вами регвыр мне больше нравиться, код я переписал

function isLinkExternal(link)
{
if(link.substring(0, 1) == '/' || link == '')
return false;

var a = /^(http|https|ftp)://([w.-]+)/i.exec(link);
if(!a)
return false;

return location.host.replace(/^www./, '') != a[2].replace(/^www./, '');
}


$(document).ready(function () {

$(document).bind('click', function(e) {
var target = (window.event) ? e.srcElement : e.target;

if ((target.nodeName == 'IMG')
&& (typeof target.parentNode != null)
&& (target.parentNode.nodeName == 'A'))
target = target.parentNode;

if(target.nodeName == 'A')
if(isLinkExternal(target.href))
{
var link = target.href;
link = '/outgoing/' + link.replace(/:///, '/');
urchinTracker(link);
}
});
});

Но c ftp есть одна проблема, адреса такого вида: ftp://user:password@somehost.name:123/directory/ (я такие не часто видел правда, но в спецификации (rfc1738) они есть) из них вместо хоста выхватывается user, получается, что потом мы проверяем не совпадает ли имя пользователя с текущим хостом, а это немного не то, что нужно. И хотя ссылки нормально отслеживаются пока имя пользователя не совпадет с текущим хостом, хотелось бы исправить эту проблемку (соображения есть если сработает отпишусь).

Link to comment
Share on other sites

  • 0

Гена на :D

var parseUrl = function(sourceUri) {
var uriParts = new RegExp("^(?:([^:/?#.]+):)?(?://)?(([^:/?#]*)(?::(d*))?)?((/(?:[^?#](?![^?#/]*.[^?#/.]+(?:[?#]|$)))*/?)?([^?#/]*))?(?:?([^#]*))?(?:#(.*))?").exec(sourceUri);
var uri = {
url: sourceUri,
protocol: RegExp.$1,
auth: RegExp.$2,
domain : RegExp.$3,
port: RegExp.$4,
path: RegExp.$5,
dir: RegExp.$6,
file: RegExp.$7,
query: RegExp.$8,
hash: RegExp.$9
}

if(uri.dir.length > 0) uri.dir = uri.dir.replace(//?$/, "/");

return uri;
}

Link to comment
Share on other sites

  • 0

:D иногда стоит заняться чем нибудь другим, а потом вернуться к проблеме и оказывается, что это совсем не проблема. Можно не парсить урл полностью, а выхватить часть с авторизацией, хостом и портом, а потом отрезать от нее все лишнее .replace(/.*@/,'').replace(/:.*/,'')

Кстати заметил, что чем дальше тем меньше становиться функция, интересно до одной строки добьем :)

function isLinkExternal(link)
{
if(link.substring(0, 1) == '/' || link == '') return false;

var a = /^(http|https|ftp)://([^?#/]+)/i.exec(link);
if(!a) return false;

return location.host.replace(/^www./, '') != a[2].replace(/.*@/,'').replace(/:.*/,'').replace(/^www./, '');
}

Link to comment
Share on other sites

  • 0

Так не получится, к сожалению.

Вот невыдуманный пример который пройдет как внутренняя: хттп://del.icio.us/post?url=http://designformasters.info/posts/google-analytics-advanced-use/&title=%D0%9F%D1%80%D0%BE%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D1%82%D0%BE%D0%B5+%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5+Google+Analytics+-+Design+For+Masters

А я сразу обрадовался :D

Link to comment
Share on other sites

  • 0
А я сразу обрадовался :D

Поэкспериментируйте. Можно ведь "оторвать" кусок href до интересующего Вас символа. В данном случае будет так:

function isLinkExternal(link) {
return !link.replace(/?.*$/, '').match(window.location.hostname);
}

Я не знаком с упомянутой Вами ранее спецификацией rfc1738, поэтому решайте сами, какой символ в href нужно выбрать для конца строки, в которой будет осуществляться поиск...

Link to comment
Share on other sites

  • 0
...неужели развесить по отдельным ссылкам будет оптимальней, что-то сомневаюсь?

Я тоже сомневаюсь, т.к. в том случае, когда ссылки будут добавляться динамически, они останутся не "обработанными".

...если клик на изображении заключенном в ссылку то пользователь переходит по ссылке и это не учитывается.

В тэге допустимы многие другие тэги, не только (, например). Поэтому условие:

 if ((target.nodeName == 'IMG')
&& (typeof target.parentNode != null)
&& (target.parentNode.nodeName == 'A'))

также не решает проблему вложенных тэгов. Может подойти такой вариант:

 while (target) {
if (target.href) {
break;
};
target = target.parentNode;
};
if (!target || !isLinkExternal(target.href)) {
return false;
};

Link to comment
Share on other sites

  • 0

rfc1738 - это описание формата URL: http://www.faqs.org/rfcs/rfc1738.html

Часть с вложеными в <а> тегами взял ту которую вы предложили, работает отлично, а вот протестить ссылку в одну строку так и не смог (вернее смог, но не очень надежно получилось, завтра еще попробую, нужно разобраться все таки в этих регулярных выражениях).

Пока получается такой код:

function isLinkExternal(link)
{
if(link.substring(0, 1) == '/' || link == '')
return false;

var a = /^(http|https|ftp)://([^?#/]+)/i.exec(link);
if(!a)
return false;

return location.host.replace(/^www./, '')
!= a[2].replace(/.*@/,'')
.replace(/:.*/,'')
.replace(/^www./, '');
}



$(document).ready(function () {

$(document).bind('click', function(e) {
var target = (window.event) ? e.srcElement : e.target;

while (target)
{
if (target.href) break;
target = target.parentNode;
}

if (!target || !isLinkExternal(target.href))
return true;

var link = target.href;
link = '/outgoing/' + link.replace(/:///, '/');
//alert(link); return false; //тестирование
urchinTracker(link);
});

});

Link to comment
Share on other sites

  • 0
rfc1738 - это описание формата URL: http://www.faqs.org/rfcs/rfc1738.html

Я представляю, что это такое, но я бы не хотел браться за выбор символа - ограничителя строки. Предложил ?, но возможно это не то, что нужно...

...не очень надежно получилось, завтра еще попробую, нужно разобраться все таки в этих регулярных выражениях.

Тут регвыры не нужны. Как я уже написал, Вам нужно выбрать лишь символ-ограничитель для передачи его аргументом в метод replace...

Link to comment
Share on other sites

  • 0

Не выйдет представим, что ссылка указана так:

href="www.blogstorm.co.uk/"

схемы нет, работать она не будет, а определяться как внешняя и засчитываться в статистику будет. Я именно о такой ненадежности говорю.

Link to comment
Share on other sites

  • 0
...работать она не будет, а определяться как внешняя и засчитываться в статистику будет.

Хорошо. Значит достаточно проверить наличие протокола: /^(https?|ftp)/i. Так ведь? Зачем нужны несколько реплейсов - ведь нужно лишь совпадение?

Link to comment
Share on other sites

  • 0

Совпасть может по разному, например, при переходе с narod.ru на vasya.narod.ru совпадет

return !'xttp://vasya.narod.ru'.replace(/?.*$/, '').match('narod.ru');

а ведь не должно

Но можно попробовать так:

function isLinkExternal(link)
{
if(link.substring(0, 1) == '/' || link == '')
return false;

var r = new RegExp('^(http|https)://(www.)?' + location.host.replace(/^www./, ''));
return !r.test(link);
}

Хоть и не в одну строку но сойдет.

ftp я выкинул совсем, потому что даже если он и на том же хосте, что и сайт переходы на него можно считать исходящими.

Для удобства работы со статистикой ссылки нужно переформатировать

пока я остановился на формате /outgoing/схема/ссылка, но не уверен, что это лучший выбор может быть у кого-нибудь есть предложения по формату ссылок.

Link to comment
Share on other sites

  • 0
Совпасть может по разному, например, при переходе с narod.ru на vasya.narod.ru совпадет

return !'xttp://vasya.narod.ru'.replace(/?.*$/, '').match('narod.ru');

а ведь не должно...

Минуточку, разве совпадет?

alert('http://vasya.narod.ru'.match('http://narod.ru'));

Метод match ведь получает не просто 'narod.ru', а window.location.hostname. Разве в hostname не будет протокола?

Link to comment
Share on other sites

  • 0

Блин подправил немного скрипт при запуске FF вылетел, видимо где-то ошибся, придется писать ответ снова.

В hostname протокола нет, но он есть в location.protocol, можно сделать так:

return !link.replace(/?.*$/, '').match(location.protocol + '//' + location.hostname);

но что тогда делать с www которое то есть, то нет, и нужно делать проверку без его учета.

Link to comment
Share on other sites

  • 0

Jenek

Может тогда вынуть из href ту часть, которая должна быть именем хоста?

function isLinkExternal(link) {
link = link.match(/^https?://(?:www.)?([^:/]+)/);
return !(link || [])[0].match(window.location.hostname);
};

Опять не то написал, исправляюсь. Надо не match использовать, просто сравнить строки (надеюсь, что так):

	link = link.match(/^https?://(?:www.)?([^:/]+)/);
if (!link) {
return false;
};
return !(link[1] == window.location.hostname.replace(/^www/, ''));

Исправил еще раз (как плохо, когда не на чем проверить :D )...

Link to comment
Share on other sites

  • 0

Получилось два работающих варианта, можно выбирать какой нравиться :D

function isLinkExternal(link)
{
var r = new RegExp('^https?://(?:www.)?' + location.host.replace(/^www./, ''));
return !r.test(link);
}


function isLinkExternal(link) {
link = link.match(/^https?://(?:www.)?([^:?#/]+)/);
return !((link || [])[1] == location.hostname.replace(/^www./, ''));
}

индекс поменял с 0 на 1 потому что в 0 подстрока которая была распарсена, а переменные с 1

Link to comment
Share on other sites

  • 0

Уважаемые знатоки :)

Подскажите, пожалуйста, менее продвинутому коллеге, как модифицировать последний вариант, чтобы соседние поддомены (3-го уровня) воспринимались как внутренние.

Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
 Share

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. See more about our Guidelines and Privacy Policy