Парсим URLs

30 июля 2008

Недавно, мне нужно было написать небольшое приложение, которое бы парсило URLs. В связи с чем хочу поделиться небольшой информацией по написанию своего паука (crawl, spider). Для начала давайте разберемся, из каких частей состоит URL. Обобщенно его структуру можно представить следующим образом:


<схема>://<логин>:<пароль>@<хост>:<порт>/?<запрос>#<фрагмент>

  1. <схема> — схема обращения к ресурсу, как правило это сетевой протокол.
  2. <логин>:<пароль> — тут все понятно: реквизиты для авторизации.
  3. <хост> — доменное имя хоста в системе DNS (или IP).
  4. <порт> — порт.
  5. <URL-путь> — информация, идентифицирующая местоположение ресурса.
  6. <запрос> — параметры запроса (имя=значение).
  7. <фрагмент> — фрагмент (например якорь на странице).

Один из самых оптимальных вариантов выдергивания URLs из страницы – это поиск их с помощью регулярных выражений (regular expressions – выражения, которые задают шаблон поиска подстроки в некотором тексте). Регулярное выражение для URL будет выглядеть следующим образом:

private String m_hlre = "(?:[hH][rR][eE][fF]\\s*=\\s*[\'\\\"])(?:[\\s]*)(.*?)(?:[\'\\\"])";

Конечно, есть вероятность, что это выражение не покрывает все варианты, если у кого-нибудь есть лучшее предложение пишите. Ниже представлен кусочек кода, который забирает страницу:

private void request(Uri url) {
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
    //если нужен прокси
    request.Proxy = new WebProxy(m_hostProxy, m_port);
    request.Proxy.Credentials = new NetworkCredential(m_loginProxy, m_passwordProxy, m_domainProxy);
    WebResponse response = request.GetResponse();
    Stream stream = response.GetResponseStream();
    StreamReader reader = new StreamReader(stream, true);
    String htmlText = reader.ReadToEnd();
    parse(url, htmlText);
}

Парсим страницу:

private void parse(Uri discoveryUrl, String data) {
    // парсим
    MatchCollection mc = Regex.Matches(data, m_hlre, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
    // если совпадения есть
    if (mc.Count != 0) {
        foreach (Match match in mc) {
            Uri uri;
            // пытаемся создать объект Uri, Groups[1] содежрит URLs
            if (Uri.TryCreate(match.Groups[1].Value, UriKind.RelativeOrAbsolute, out uri)) {
                // если адрес не абсолютный то создаем новый Uri
                // из базового адреса + относительный
                // можем его использовать в качестве источника
                // для получения следующей страницы для парсинга
                if (!uri.IsAbsoluteUri) {
                    uri = new Uri(discoveryUrl, uri);
                }
                if (uri.Scheme == Uri.UriSchemeHttp ||
                uri.Scheme == Uri.UriSchemeHttps ||
                uri.Scheme == "feed" ||
                uri.Scheme == "webcal" ||
                uri.Scheme == "news") {
                    // проверяем принадлежит ли URL домену
                    // если задача забрать только адреса
                    // принадлежащие текуещму домену
                    if (uri.DnsSafeHost.Equals(m_urlDomain)) {
                        // сохраняем где-либо новый адреc
                    }
                }
            }
        }
    }
}

Этот код не идеальный и не проверяет некоторые ситуации, в которых полученный URL является не желательным (например этот код может пропарить адрес css или картинки), если доработать напильником то все будет хорошо 🙂 .

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

Regex urlRegex = new Regex(@"^((?<scheme>[A-Za-z0-9_+.]{1,8}):)?(//)?" +
                           @"((?<userinfo>[!-~]+)@)?" +
                           @"(?<host>[^/?#:]*)(:(?<port>[0-9]*))?" +
                           @"(/(?<path>[^?#]*))?" +
                           @"(\?(?<query>[^#]*))?" +
                           @"(#(?<fragment>.*))?",
                           RegexOptions.ExplicitCapture);

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


Где «имя группы» — <scheme>, <userinfo>…<fragment> (см. регулярное выражение).
Вот и все, теперь вы можете написать своего паука, который будет парсить страницы.

5 комментариев на “Парсим URLs”

  1. Когда смотришь на исполненные оригинальные идеи, посещает мысль, а ведь это ж так просто, ну почему я это не смог придумать…

  2. Спасибо за пост. Разжевано специально для меня 🙂

  3. osoq:

    those are awsome, thanks for sahring!

  4. Трудновато было разбираться, но думаю очень пригодится этот материал.

Оставить комментарий