Вадим Стеркин

  • Главная
  • Windows
  • SSD
  • Программы
  • Разное
  • Об авторе
Вы тут: Главная → Windows → PowerShell: как извлечь список уникальных ссылок с веб-страницы

PowerShell: как извлечь список уникальных ссылок с веб-страницы

Рубрики: Windows, Вопрос - Ответ Обновлено: 09.12.2020 комментариев 12

Возникла задача получить со страницы Confluence все ссылки, в URL которых была последовательность символов XYZ. Вручную – не вариант, потому что ссылок много.

Я в блоге показывал парсинг веб-страниц с командлетом PowerShell Invoke-WebRequest. Но тут пришлось бы возиться с авторизацией и двухфакторной аутентификацией, поэтому я решил сохранить HTML-страницу локально.

Базовый подход к обработке локальных веб-страниц легко нагугливается вплоть до примеров кода – используется COM-объект и метод IHTMLDocument2.write. После удаления из HTML-кода страницы этой строки:

<meta http-equiv="X-UA-Compatible" content="IE=EDGE,chrome=IE7">

сработал такой парсинг:

$source = Get-Content -Path "C:\temp\page.html" -Raw
$page = New-Object -ComObject "HTMLFile"
$page.IHTMLDocument2_write($source)

На этом специфика локальной веб-страницы закончилась. Далее я получил список всех URLs с ключевым словом:

$urls = $page.links | Where-Object {$_.href -like '*XYZ*'}
$urls | Format-Table href

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

Во-первых, в некоторых случаях ссылки вели на якорь (конкретное место на странице) и имели вид /page#anchor. Лишнее отсекается оператором -split по тому же принципу, что и в рассказе про youtube-dl. Разница лишь в разделителе (в данном случае — #) и необходимости перебрать все ссылки командлетом ForEach-Object.

$urls | ForEach-Object {$_.href.split('#')[0]}

Во-вторых, оказалось, что в списке есть дубликаты, т.е. одинаковые ссылки присутствовали на странице несколько раз. Работа с дубликатами – вполне типовая задача, и в одной из грядущих статей я к ней вернусь. Это решается группировкой с помощью командлета Group-Object, который до сих пор не фигурировал в блоге.

Для наглядности я отсортировал группы по количеству ссылок. Видно, что две ссылки встречаются на странице по четыре раза, а остальные – по одному разу.

Парсинг

Чтобы получить только уникальные ссылки, надо из каждой группы взять по одной ссылке. В данном случае я беру свойство Name, значением которого выступает уникальный URL.

$urls | ForEach-Object {$_.href.split('#')[0]} | 
Group-Object | ForEach-Object {$_.Name} | Sort-Object

В итоге получается аккуратный список уникальных ссылок. При необходимости их все можно оптом открыть в браузере, передав список дальше по конвейеру командлету ForEach-Object. В таком случае последний этап конвейера будет примерно таким:

ForEach-Object {Start-Process "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" $_}

Метки: PowerShell, скрипты Информация в статье применима к Windows 7 и новее

Об авторе

Вадим - владелец этого блога, и почти все записи здесь вышли из-под его пера. Подробности о блоге и авторе здесь.

Вас также может заинтересовать:

  • PowerShell: как заменить дубликаты файлов на жесткие ссылки для экономии места
  • PowerShell: как в Windows 10 быстро выявить дубликаты драйверов и удалить их
  • Как скачать видео с YouTube с помощью youtube-dl и PowerShell
  • PowerShell: парсинг веб-страниц и загрузка файлов с Invoke-WebRequest
← PowerShell: как в Windows 10 быстро выявить дубликаты драйверов и удалить их
Как определить, является файл компонентом Windows или просто выпущен Microsoft →
Telegram logo

Я в Telegram

Подпишитесь на канал и читайте интересные записи чаще! Есть вопросы? Задайте их в чате.

комментариев 12

↓
  1. Lecron

    08.12.2020 в 20:54

    Правильнее вначале извлекать все ссылки, а потом фильтровать на xyz. Проще повторно использовать код.

    Ваша оценка: Thumb up Thumb down 0
    • Vadim Sterkin

      08.12.2020 в 21:10

      Не понял. Я вроде именно это и делаю в строке 4 с конвейером. Да, я присваиваю переменную $urls уже отфильтрованному списку, но мне прочее и не нужно.

      Ваша оценка: Thumb up Thumb down 0
      • Lecron

        09.12.2020 в 09:05

        Это сейчас. Пройдет время и когда понадобится фильтровать по другому критерию, найти фильтр в конце скрипта, будет гораздо легче чем в середине. Без необходимости восстанавливать в голове всю логику скрипта.

        $urls = $page.links | ForEach-Object {$_.href.split('#')[0]} | Group-Object | ForEach-Object {$_.Name}
        $filtered = $urls | Where-Object {$_.href -like '*XYZ*'} | Sort-Object
        

        Просто правило хорошего кода, а не влияния на функциональность. Становится важным, потому что после публикации, ваш пример начнет расползаться по клиентским машинам. В идеале, вообще подумать о создании функции Get-Links.

        Ваша оценка: Thumb up Thumb down 0
  2. Vadim Sterkin

    09.12.2020 в 10:46

    Lecron: Просто правило хорошего кода, а не влияния на функциональность.

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

    Равно как зачем мне применять оба приема ко всему массиву ссылок, если меня интересуют конкретные с XYZ? Правило хорошего кода в данном случае получается неэффективным с точки зрения скорости. Ладно если объектов пара сотен, как в моем случае, а если пара сотен тысяч?

    Я думаю, что с учетом индвидуальности каждого случая описанный порядок действий (и последовательности команд) вполне логичный*. Мне нужны ссылки с XYZ — я с этого начинаю, а дальше уже смотрю, что получилось. Может, этого достаточно, и тогда вообще не о чем писать в блоге :)

    *Возможно, логичнее было бы сначала выбрать уникальные ссылки, а потом уже отсекать якоря. Но так уж вышло, что сначала я заметил якоря, а потом уже дубликаты.

    Ваша оценка: Thumb up Thumb down 0
    • Lecron

      09.12.2020 в 11:53

      Vadim Sterkin: Но в данном случае я демонстрирую пример приемов split и group нежели пример скрипта.

      Одно другому не помеха. В человеке все должно быть прекрасно :) Впрочем, мой изначальный комментарий, скорее предназначался читателям статьи, чем Вам.

      Vadim Sterkin: Правило хорошего кода в данном случае получается неэффективным с точки зрения скорости. Ладно если объектов пара сотен, как в моем случае, а если пара сотен тысяч?

      Преждевременная оптимизация — зло!
      Примеры должны быть релевантными (репрезентативными? всегда путаюсь в этих сложных словах))). Такую задачу, буде она возникнет, вряд ли будут решать шелл-скриптом, ибо она явно будет шире. И даже для скрипта, который полностью вписан в .NET это разница в миллисекунды. На фоне парсинга html для извлечения ссылок, вообще 0.

      Ваша оценка: Thumb up Thumb down 0
    • Lecron

      09.12.2020 в 12:01

      Кстати, может действительно не стоит экономить строки и написать более самодокументируемый код.

      $urls = $page.links | ForEach-Object {$_.href.split('#')[0]} 
      $unique = $urls | Group-Object | ForEach-Object {$_.Name}
      $filtered = $unique | Where-Object {$_.href -like '*XYZ*'} | Sort-Object
      Ваша оценка: Thumb up Thumb down +1
      • Vadim Sterkin

        09.12.2020 в 12:22

        Да, это самый наглядный вариант. Но боюсь, что тогда в комментарии придет Вадимс Поданс и поинтересуется, не проходит ли тут чемпионат мира по созданию переменных :)

        Lecron: Такую задачу, буде она возникнет, вряд ли будут решать шелл-скриптом, ибо она явно будет шире. И даже для скрипта, который полностью вписан в .NET это разница в миллисекунды.

        Василий Гусев в чате написал, что парсит регулярными выражениями, потому что не любит комы :)

        Ваша оценка: Thumb up Thumb down 0
        • Lecron

          09.12.2020 в 13:49

          Vadim Sterkin: Василий Гусев в чате написал, что парсит регулярными выражениями, потому что не любит комы :)

          Каждому свое.
          Не знаю, что было бы источником данных, скорее всего BeautifulSoup, но на питоне обсуждаемый код выглядел бы где-то так:

          list(set(url.href.split('#')[0] for url in links if 'xyz' in url.href)).sort()

          И никаких чемпионатов по переменным :)

          Ваша оценка: Thumb up Thumb down +1
  3. Vadim Sterkin

    10.12.2020 в 13:11

    Lecron: но на питоне обсуждаемый код выглядел бы где-то так:

    Ну на пошике обсуждаемый код тоже может выглядеть как-то так↓ Но это не для публикации :)

    $page.links | ? href -like '*XYZ*' | %{$_.href.split('#')[0]} | group | % name | sort
    Ваша оценка: Thumb up Thumb down +1
    • Lecron

      10.12.2020 в 15:42

      Почему не для публикации? Вполне можно в конце статьи писать такой краткий вариант. Вреда не вижу, а пользу вполне.

      Ваша оценка: Thumb up Thumb down 0
  4. Andrew D

    16.12.2020 в 10:25

    Тут мысль одна возникла. Я как-то решал задачу через excel, в нем генерировал список ссылок, экспортировал в txt и скармливал Reget Deluxe. Но толи там HTTPS сертификаты протухли, толи оно запрос неверно формирует, в прошлый раз что-то пошло не так.
    Можно ли сотворить на PS? Думаю да.
    Суть: есть набор ссылок вида https://example.com/path/nnnn где nnnn — число.
    Задача: перейти по всем ссылкам по очереди, там где лежит бинарник (прямо или с редиректом — не важно, суть в том что браузер при переходе по верному номеру спрашивает куда класть, а по неверному — страничка о том что не найдено) — взять как есть вместе с именем и сохранить в папку, добавив его nnnn к имени.

    $a = Invoke-WebRequest -Uri $url

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

    Ваша оценка: Thumb up Thumb down 0
    • Vadim Sterkin

      17.12.2020 в 17:32

      Andrew D: суть в том что браузер при переходе по верному номеру спрашивает куда класть

      Так вы настройте браузер, чтобы не спрашивал — будет класть в заданную папку. А так, загрузку файлов я показывал уже — тут.

      Ваша оценка: Thumb up Thumb down 0

Обсуждение завершено.

Subscribers

Популярные записи

  • 10 причин, по которым я не могу работать в Windows XP (362)
  • 10 бесплатных приложений для Windows Phone, которыми я пользуюсь регулярно (110)
  • Windows 8.1 Update 1: что получается, когда Microsoft вас слушает (183)
  • Как в Windows 10 автоматически выполнять задачи на восходе и закате солнца (33)
  • Нюансы отключения службы SysMain в Windows 10 (26)
  • 6 ошибок людей с маленьким системным разделом (184)
  • Можно ли работать в Windows без антивируса (278)
  • Еще →

Свежие комментарии

  • Vadim Sterkin к записи Как выполнять команды и скрипты от имени системы средствами Windows
  • Vadim Sterkin к записи Поддержите меня подпиской или донатом!
  • Валерий Плотников к записи Поддержите меня подпиской или донатом!
  • Артём Ракчеев к записи Нюансы управления звуком в Windows 10
  • Игорь к записи Поддержите меня подпиской или донатом!
  • Vadim Sterkin к записи Поддержите меня подпиской или донатом!
  • Игорь к записи Поддержите меня подпиской или донатом!
  • Vadim Sterkin к записи Поддержите меня подпиской или донатом!

Рекомендую ресурсы

  • Windows 10, etc — канал этого блога в Telegram
  • Инсайдеры Windows 10 — чат блога в Telegram
  • Community — новости предварительных сборок
  • Николай Павлов — тайны планеты Excel
  • Вадимс Поданс — PKI, PowerShell и Тера Патрик
  • Василий Гусев — PowerShell и другие скрипты
  • Kazun — PowerShell для взрослых

Реклама

Измененная тема eleven40 Pro на платформе Genesis · Архивы и метки · Правила (16+) · О рекламе · Обратная связь · Вход

Допускается копирование материалов без изменений, с указанием имени автора и гиперссылки на сайт.