Извлечение всех ссылок web-сайта с помощью Python

Одна из задач, которая стояла в рамках проекта, нацеленного на исследование мер поисковой оптимизации (SEO, search engine optimization) информационных ресурсов дочерних структур организации, предполагала поиск всех ссылок и выявление среди них так называемых «мертвых (битых) ссылок», отсылающих на несуществующий сайт, страницу, файл, что в свою очередь понижает рейтинг информационного ресурса.
В этом посте я хочу поделиться одним из способов извлечения всех ссылок сайта (внутренних и внешних), который поможет при решении подобных задач.
Посмотрим, как можно создать инструмент извлечения ссылок в Python, используя пакет requests и библиотеку BeautifulSoup. Итак,
pip install requests bs4
Импортируем необходимые модули:
import requests from urllib.parse import urlparse, urljoin from bs4 import BeautifulSoup
Затем определим две переменные: одну для всех внутренних ссылок (это URL, которые ссылаются на другие страницы того же сайта), другую для внешних ссылок вэб-сайта (это ссылки на другие сайты).
# Инициализировать набор ссылок (уникальные ссылки) int_url = set() ext_url = set()
Далее создадим функцию для проверки URL – адресов. Это обеспечит правильную схему в ссылке — протокол, например, http или https и имя домена в URL.
# Проверяем URL def valid_url(url): parsed = urlparse(url) return bool(parsed.netloc) and bool(parsed.scheme)
На следующем шаге создадим функцию, возвращающую все действительные URL-адреса одной конкретной веб-страницы:
# Возвращаем все URL-адреса def website_links(url): urls = set() # извлекаем доменное имя из URL domain_name = urlparse(url).netloc # скачиваем HTML-контент вэб-страницы soup = BeautifulSoup(requests.get(url).content, "html.parser")
Теперь получим все HTML теги, содержащие все ссылки вэб-страницы.
for a_tag in soup.findAll("a"): href = a_tag.attrs.get("href") if href == "" or href is None: # href пустой тег continue
В итоге получаем атрибут href и проверяем его. Так как не все ссылки абсолютные, возникает необходимость выполнить соединение относительных URL-адресов и имени домена. К примеру, когда найден href — «/search» и URL — «google.com» , то в результате получим «google.com/search».
# присоединить URL, если он относительный (не абсолютная ссылка) href = urljoin(url, href)
В следующем шаге удаляем параметры HTTP GET из URL-адресов:
parsed_href = urlparse(href) # удалить параметры URL GET, фрагменты URL и т. д. href = parsed_href.scheme + "://" + parsed_href.netloc + parsed_href.path
Если URL-адрес недействителен/URL уже находится в int_url , следует перейти к следующей ссылке.
Если URL является внешней ссылкой, вывести его и добавить в глобальный набор ext_url и перейдти к следующей ссылке.
И наконец, после всех проверок получаем URL, являющийся внутренней ссылкой; выводим ее и добавляем в наборы urls и int_url
if not valid_url(href): # недействительный URL continue if href in int_url: # уже в наборе continue if domain_name not in href: # внешняя ссылка if href not in ext_url: print(f"[!] External link: ") ext_url.add(href) continue print(f"[*] Internal link: ") urls.add(href) int_url.add(href) return urls
Напоминаю, что эта функция захватывает ссылки одной вэб-страницы.
Теперь создадим функцию, которая сканирует весь веб-сайт. Данная функция получает все ссылки на первой странице сайта, затем рекурсивно вызывается для перехода по всем извлеченным ссылкам. Параметр max_urls позволяет избежать зависания программы на больших сайтах при достижении определенного количества проверенных URL-адресов.
# Количество посещенных URL-адресов visited_urls = 0 # Просматриваем веб-страницу и извлекаем все ссылки. def crawl(url, max_urls=50): # max_urls (int): количество макс. URL для сканирования global visited_urls visited_urls += 1 links = website_links(url) for link in links: if visited_urls > max_urls: break crawl(link, max_urls=max_urls)
Итак, проверим на сайте, к которому имеется разрешение, как все это работает:
if __name__ == "__main__": crawl("https://newtechaudit.ru") print("[+] Total External links:", len(ext_url)) print("[+] Total Internal links:", len(int_url)) print("[+] Total:", len(ext_url) + len(int_url))
Вот фрагмент результата работы программы:

Обратите внимание, что многократный запрос к одному и тому же сайту за короткий промежуток времени может привести к тому, что ваш IP-адрес будет заблокирован. Ссылка на оригинал поста.
Как найти все URL и URI внутри HTML с помощью Python?
Вообще-то URL надо еще и подменить-обернуть (пробую сделать что-то типа веб-прокси на Django) но это уже мелочи. Сначала надо найти URL и URI. Для простоты буду все называть URL.
Я знаю о существовании BeautifulSoup и его возможностях по разбору (и замене) внутри HTML. Но в реальности задача оказывается ему не по зубам.
Для начала URL может встречаться не только в что-то или , но и в , в , в
Кроме того, URL может встречаться в конструкциях типа
. а еще бывает, что BeautifulSoup обнаруживает, что-то, что похоже на URL, но это им не является (например, как в конструкции типа
Все эти варианты найти с помощью BeautifulSoup и дальше разобрать не просто, но всё ещё можно. Но BeautifulSoup не поможет, если некоторые HTM-теги не закрыты. Например, попадется конструкция (без финализирующей /> ) и тогда BeautifulSoup «проглотит» весь HTML до следующего (а его может больше и не быть на странице. к слову если стил написания HTML-кода «не закрывать теги», то BeautifulSoup вообще бессилен).
Если же использовать регулярные выражения, то все становится очень сложно, т.к. URL внутри HTML может быть не оформлен в кавычки (и тогда он воспринимается до ближайшего пробела). Или иметь произвольные пробельные символы (например:
html
Я думаю случаев употребления URL намного больше. Я уж не говорю про случаи когда URL встречается внутри встроенного в JavaScript (а там иногда стараются скрыть URL от паркинга. впрочем раз скрывают, то и пусть так и будет. но «открытые» URL хотелось бы обнаруживать).
И как это все разобрать? Написать универсальное регулярное выражение для всех случаев — не способен. BeautifulSoup, как я объяснил, не всегда помогает. Есть ли какие-нибудь альтернативы по обнаружению URL?
- Вопрос задан более двух лет назад
- 93 просмотра
python. beautifulsoup. Не получается получить все ссылки со страницы
Не могу вытащить все ссылки на карточки ЖК, получается вытащить только 1 ссылку с помощью find на сайте код выглядит так :
ссылка на саму страницу: https://krasnodar.cian.ru/novostroyki Методом find с уточнением
soup.find('div', class_='class="_0fce717cdb--container--1Gxqr _0fce717cdb--container-background_color--transparent--3pvxk _0fce717cdb--container-display--inline-block--3bIEb").find('a').get('href')
работает и находит необходимую ссылку Но методом find_all не получается добиться нужного результата.
Отслеживать
12.5k 7 7 золотых знаков 19 19 серебряных знаков 48 48 бронзовых знаков
задан 6 апр 2021 в 14:26
13 5 5 бронзовых знаков
имена классов — динамические, там нет одинаковых _0fce717cdb—container—1Gxqr.
6 апр 2021 в 14:59
У вас разные имена классов в ссылках, вот и не получается найти.
6 апр 2021 в 15:13
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
import requests from bs4 import BeautifulSoup url = 'https://krasnodar.cian.ru/novostroyki/' r = requests.get(url) soup = BeautifulSoup(r.content, 'html.parser') cards = soup.find_all('div', ) for card in cards: title = card.find('span', ).text link = card.find('a', )['href'] print(f' ')
выведет на печать:
ЖК «Европейский» https://zhk-evropeyskiy-krasnodar.cian.ru/#map ЖК «Самолет» https://krasnodar.cian.ru/zhiloy-kompleks-samolet-krasnodar-353100/hod-stroitelstva/ ЖК «Сказка град» https://zhk-skazka-grad-krasnodar.cian.ru/hod-stroitelstva/ . ЖК «Резиденция» https://krasnodar.cian.ru/zhiloy-kompleks-rezidenciya-kozhzavod-mkr-7135/hod-stroitelstva/ ЖК «Седьмой континент» https://krasnodar.cian.ru/zhiloy-kompleks-sedmoy-kontinent-krasnodar-8390/otzyvy/ ЖК «Южане» https://krasnodar.cian.ru/zhiloy-kompleks-yuzhane-krasnodar-23921/hod-stroitelstva/
Отслеживать
ответ дан 6 апр 2021 в 16:02
12.5k 7 7 золотых знаков 19 19 серебряных знаков 48 48 бронзовых знаков
Благодарю. Способ помог — немного поправил теги. Понял принцип. Буду тщательнее изучать вопрос.
7 апр 2021 в 10:21
import requests from bs4 import BeautifulSoup as Soup from bs4.element import Tag response = requests.get('https://krasnodar.cian.ru/novostroyki') soup = Soup(response.content, 'html.parser') def link_from_header(header: Tag): a = header.find('a') return a.get('href') link_list = [*map(link_from_header, soup.find_all('div', ))] print(*link_list, sep='\n') # https://zhk-evropeyskiy-krasnodar.cian.ru/ # https://krasnodar.cian.ru/zhiloy-kompleks-samolet-krasnodar-353100/ # https://zhk-skazka-grad-krasnodar.cian.ru/ # https://krasnodar.cian.ru/zhiloy-kompleks-park-pobedy-krasnodar-1686651/ # https://zhk-strizhi-krasnodar.cian.ru/ # https://krasnodar.cian.ru/zhiloy-kompleks-abrikosovo-krasnodar-16066/ # https://krasnodar.cian.ru/zhiloy-kompleks-elegant-krasnodar-8304/ # https://krasnodar.cian.ru/zhiloy-kompleks-dostoyanie-krasnodar-1789905/ # https://zhk-sportivnaya-derevnya-krasnodar.cian.ru/ # https://krasnodar.cian.ru/zhiloy-kompleks-otkrytie-krasnodar-50168/ # https://zhk-gubernskiy-krasnodar.cian.ru/ # https://krasnodar.cian.ru/zhiloy-kompleks-rakurs-krasnodar-1659959/ # https://krasnodar.cian.ru/zhiloy-kompleks-dyhanie-krasnodar-39245/ # https://krasnodar.cian.ru/zhiloy-kompleks-development-plaza-krasnodar-144280/ # https://zhk-sportivnyy-park-krasnodar.cian.ru/ # https://zhk-melodiya-krasnodar.cian.ru/ # https://zhk-solnechnyy-gorod-krasnodar.cian.ru/ # https://krasnodar.cian.ru/zhiloy-kompleks-novella-krasnodar-1706586/ # https://krasnodar.cian.ru/zhiloy-kompleks-svoboda-krasnodar-33666/ # https://krasnodar.cian.ru/zhiloy-kompleks-lime-krasnodar-1276272/ # https://krasnodar.cian.ru/zhiloy-kompleks-serdce-shkolnyy-mkr-47259/ # https://krasnodar.cian.ru/zhiloy-kompleks-grani-krasnodar-48852/ # https://krasnodar.cian.ru/zhiloy-kompleks-rezidenciya-kozhzavod-mkr-7135/ # https://krasnodar.cian.ru/zhiloy-kompleks-sedmoy-kontinent-krasnodar-8390/ # https://krasnodar.cian.ru/zhiloy-kompleks-yuzhane-krasnodar-23921/
Отслеживать
ответ дан 6 апр 2021 в 16:58
6,497 2 2 золотых знака 8 8 серебряных знаков 23 23 бронзовых знака
Благодарю за ответ. Не все понял. но постараюсь разобраться
7 апр 2021 в 10:23
@K1bomax, link_list = [*map(link_from_header, soup.find_all(‘div’, <'data-mark': 'GKCardTitle'>))] Если нужен список ссылок с которым нужно дальше работать'data-mark':>
7 апр 2021 в 12:01
Да я понял. но у меня пока что все идет без функций. решил написать одной простыней. После буду переписывать по человечески.
Как в Python извлечь все ссылки на веб‑странице

Получение всех ссылок на веб-странице — обычная задача для веб-парсеров, полезно создавать продвинутые парсеры, которые сканируют каждую страницу определенного веб-сайта для извлечения данных, его также можно использовать для процесса диагностики SEO или даже на этапе сбора информации для проникновения. тестеры. Здесь узнаете, как создать инструмент для извлечения ссылок на Python с нуля, используя только библиотеки requests и BeautifulSoup .
pip3 install requests bs4 colorama
Мы будем использовать requests для удобного выполнения HTTP-запросов, BeautifulSoup для синтаксического анализа HTML и colorama для изменения цвета текста.
Откройте новый файл, назовем его link_extractor.py и следуйте инструкциям, импортируем необходимые нам модули:
import requests from urllib.parse import urlparse, urljoin from bs4 import BeautifulSoup import colorama
Мы собираемся использовать colorama только для раскраски текста при печати, чтобы различать внутренние и внешние ссылки:
# инициализация модуля colorama colorama.init() GREEN = colorama.Fore.GREEN GRAY = colorama.Fore.LIGHTBLACK_EX RESET = colorama.Fore.RESET YELLOW = colorama.Fore.YELLOW
Нам понадобятся две глобальные переменные, одна для всех внутренних ссылок сайта, а другая для всех внешних ссылок:
# инициализация наборов для ссылок (обеспечивается уникальность ссылок) internal_urls = set() external_urls = set()
- internal_urls — URL-адреса, которые ведут на другие страницы того же веб-сайта.
- external_urls — URL-адреса, которые ведут на другие веб-сайты.
Поскольку не все ссылки в тегах привязки (теги) действительны, я это проверял, некоторые из них являются ссылками на части веб-сайта, некоторые — javascript, поэтому напишем функцию для проверки URL-адресов:
def is_valid(url): """ Проверяет, является ли 'url' действительным URL """ parsed = urlparse(url) return bool(parsed.netloc) and bool(parsed.scheme)
Признаком истинности ссылки являются наличие в URL-адресе правильной scheme (протокола, например http или https) и имени домена netloc.
Теперь создадим функцию для возврата всех действительных URL-адресов веб-страницы:
def get_all_website_links(url): """ Возвращает все URL-адреса, найденные на `url`, в котором он принадлежит тому же веб-сайту. """ # все URL-адреса `url` urls = set() # доменное имя URL без протокола domain_name = urlparse(url).netloc soup = BeautifulSoup(requests.get(url).content, "html.parser")
Во-первых, я инициализировал переменную набора URL-адресов, здесь я использовал set Python, потому как нам не нужны повторяющиеся ссылки.
Во-вторых, я извлек доменное имя из URL-адреса, которое нам понадобится, для проверки, является ли полученная ссылка внешней или внутренней.
В третьих, я загрузил HTML-содержимое веб-страницы и обернул его объектом soup для облегчения синтаксического анализа HTML.
Получим все HTML-теги a (теги привязки, содержащие все ссылки веб-страницы):
for a_tag in soup.findAll("a"): href = a_tag.attrs.get("href") if href == "" or href is None: # href пустой тег continue
Поскольку не все ссылки являются абсолютными, нам нужно объединить относительные URL-адреса с их доменными именами (например, когда href равно « /search », а url — « google.com », результатом будет « google.com/search »):
# присоединяемся к URL, если он относительный (не абсолютная ссылка) href = urljoin(url, href)
Теперь нам нужно удалить GET-параметры HTTP из URL-адресов, поскольку это приведет к избыточности в наборе, приведенный ниже код обрабатывает это:
parsed_href = urlparse(href) # удалить GET-параметры URL, фрагменты URL и т. д. href = parsed_href.scheme + "://" + parsed_href.netloc + parsed_href.path
if not is_valid(href): # недействительный URL continue if href in internal_urls: # уже в наборе continue if domain_name not in href: # внешняя ссылка if href not in external_urls: print(f"[!] Внешняя ссылка: ") external_urls.add(href) continue print(f"[*] Внутренняя ссылка: ") urls.add(href) internal_urls.add(href) return urls
Все, что мы здесь сделали, это проверили:
- Если URL-адрес недействителен, перейдите к следующей ссылке.
- Если URL-адрес уже находится в internal_urls , нам это тоже не нужно.
- Если URL-адрес является внешней ссылкой, распечатайте ее серым цветом и добавьте в наш глобальный набор external_urls и перейдите к следующей ссылке.
Наконец, после всех проверок URL будет внутренней ссылкой, мы распечатываем его и добавляем в наши наборы urls и internal_urls .
Вышеупомянутая функция будет захватывать только ссылки одной конкретной страницы, что, если мы хотим извлечь все ссылки всего веб-сайта? Давай сделаем это:
# number of urls visited so far will be stored here total_urls_visited = 0 def crawl(url, max_urls=30): """ Сканирует веб-страницу и извлекает все ссылки. Вы найдете все ссылки в глобальных переменных набора external_urls и internal_urls. параметры: max_urls (int): максимальное количество URL-адресов для сканирования, по умолчанию - 30. """ global total_urls_visited total_urls_visited += 1 print(f"[*] Проверено: ") links = get_all_website_links(url) for link in links: if total_urls_visited > max_urls: break crawl(link, max_urls=max_urls)
Эта функция сканирует веб-сайт, что означает, что она получает все ссылки первой страницы, а затем рекурсивно вызывает себя, чтобы перейти по всем ранее извлеченным ссылкам. Однако это может вызвать некоторые проблемы, программа будет зависать на крупных веб-сайтах (на которых есть много ссылок), таких как google.com, в результате, был добавлен параметр max_urls для выхода, когда мы достигаем определенного количества проверенных URL.
Хорошо, давайте проверим это, используя известный вам url:
if __name__ == "__main__": crawl('https://waksoft.susu.ru') print("[+] Итого внутренних ссылок:", len(internal_urls)) print("[+] Итого внешних ссылок:", len(external_urls)) print("[+] Итого URL:", len(external_urls) + len(internal_urls)) print("[+] Всего просканировано URL:", max_urls)
Тестирую на этом сайте. Однако я настоятельно рекомендую вам не делать этого, так как это вызовет много запросов и приведет к переполнению кэша веб-сервера, что может заблокировать ваш IP-адрес.

Вот часть вывода:
После завершения сканирования будет выведено общее количество извлеченных и просканированных ссылок:
[+] Итого внутренних ссылок: 211 [+] Итого внешних ссылок: 144 [+] Итого URL: 355 [+] Всего проверено URL: 30
Классно, правда? Я надеюсь, что это руководство было для вас полезным и вдохновило вас на создание таких инструментов с использованием Python.
Вот полный код утилиты для автономной работы:
import requests from urllib.parse import urlparse, urljoin from bs4 import BeautifulSoup import colorama # запускаем модуль colorama colorama.init() GREEN = colorama.Fore.GREEN GRAY = colorama.Fore.LIGHTBLACK_EX RESET = colorama.Fore.RESET YELLOW = colorama.Fore.YELLOW # инициализировать набор ссылок (уникальные ссылки) internal_urls = set() external_urls = set() total_urls_visited = 0 def is_valid(url): """ Проверяет, является ли url действительным URL """ parsed = urlparse(url) return bool(parsed.netloc) and bool(parsed.scheme) def get_all_website_links(url): """ Возвращает все URL-адреса, найденные на `url`, в котором он принадлежит тому же веб-сайту. """ # все URL-адреса `url` urls = set() # доменное имя URL без протокола domain_name = urlparse(url).netloc soup = BeautifulSoup(requests.get(url).content, "html.parser") for a_tag in soup.findAll("a"): href = a_tag.attrs.get("href") if href == "" or href is None: # href пустой тег continue # присоединяемся к URL, если он относительный (не абсолютная ссылка) href = urljoin(url, href) parsed_href = urlparse(href) # удалить GET-параметры URL, фрагменты URL и т. д. href = parsed_href.scheme + "://" + parsed_href.netloc + parsed_href.path if not is_valid(href): # недействительный URL continue if href in internal_urls: # уже в наборе continue if domain_name not in href: # внешняя ссылка if href not in external_urls: print(f"[!] Внешняя ссылка: ") external_urls.add(href) continue print(f"[*] Внутренняя ссылка: ") urls.add(href) internal_urls.add(href) return urls def crawl(url, max_urls=30): """ Сканирует веб-страницу и извлекает все ссылки. Вы найдете все ссылки в глобальных переменных набора external_urls и internal_urls. параметры: max_urls (int): максимальное количество URL-адресов для сканирования, по умолчанию - 30. """ global total_urls_visited total_urls_visited += 1 print(f"[*] Проверена ссылка: ") links = get_all_website_links(url) for link in links: if total_urls_visited > max_urls: break crawl(link, max_urls=max_urls) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Инструмент получения ссылок на web-странице") parser.add_argument("url", help="URL, по которому надо получить все ссылки") parser.add_argument("-m", "--max-urls", help="Максимальное количество получаемых ссылок URL, по умолчанию 30.", default=30, type=int) args = parser.parse_args() url = args.url max_urls = args.max_urls crawl(url, max_urls=max_urls) print("[+] Итого внутренних ссылок:", len(internal_urls)) print("[+] Итого внешних ссылок:", len(external_urls)) print("[+] Итого URL:", len(external_urls) + len(internal_urls)) print("[+] Всего проверено URL:", max_urls) domain_name = urlparse(url).netloc # сохраняем внутренние ссылки в файл with open(f"_internal_links.txt", "w") as f: for internal_link in internal_urls: print(internal_link.strip(), file=f) # сохраняем внешние ссылки в файл with open(f"_external_links.txt", "w") as f: for external_link in external_urls: print(external_link.strip(), file=f)
Есть некоторые веб-сайты, которые загружают большую часть своего контента с помощью JavaScript, в результате нам нужно вместо этого использовать библиотеку request_html, которая позволяет нам выполнять Javascript с помощью Chromium. Я уже написал для этого сценарий, добавив всего несколько строк (поскольку request_html очень похож на requests), посмотрите.
from requests_html import HTMLSession from urllib.parse import urlparse, urljoin from bs4 import BeautifulSoup import colorama # init the colorama module colorama.init() GREEN = colorama.Fore.GREEN GRAY = colorama.Fore.LIGHTBLACK_EX RESET = colorama.Fore.RESET YELLOW = colorama.Fore.YELLOW # initialize the set of links (unique links) internal_urls = set() external_urls = set() total_urls_visited = 0 def is_valid(url): """ Checks whether `url` is a valid URL. """ parsed = urlparse(url) return bool(parsed.netloc) and bool(parsed.scheme) def get_all_website_links(url): """ Returns all URLs that is found on `url` in which it belongs to the same website """ # all URLs of `url` urls = set() # domain name of the URL without the protocol domain_name = urlparse(url).netloc # initialize an HTTP session session = HTMLSession() # make HTTP request & retrieve response response = session.get(url) # execute Javascript try: response.html.render() except: pass soup = BeautifulSoup(response.html.html, "html.parser") for a_tag in soup.findAll("a"): href = a_tag.attrs.get("href") if href == "" or href is None: # href empty tag continue # join the URL if it's relative (not absolute link) href = urljoin(url, href) parsed_href = urlparse(href) # remove URL GET parameters, URL fragments, etc. href = parsed_href.scheme + "://" + parsed_href.netloc + parsed_href.path if not is_valid(href): # not a valid URL continue if href in internal_urls: # already in the set continue if domain_name not in href: # external link if href not in external_urls: print(f"[!] External link: ") external_urls.add(href) continue print(f"[*] Internal link: ") urls.add(href) internal_urls.add(href) return urls def crawl(url, max_urls=30): """ Crawls a web page and extracts all links. You'll find all links in `external_urls` and `internal_urls` global set variables. params: max_urls (int): number of max urls to crawl, default is 30. """ global total_urls_visited total_urls_visited += 1 print(f"[*] Crawling: ") links = get_all_website_links(url) for link in links: if total_urls_visited > max_urls: break crawl(link, max_urls=max_urls) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Link Extractor Tool with Python") parser.add_argument("url", help="The URL to extract links from.") parser.add_argument("-m", "--max-urls", help="Number of max URLs to crawl, default is 30.", default=30, type=int) args = parser.parse_args() url = args.url max_urls = args.max_urls crawl(url, max_urls=max_urls) print("[+] Total Internal links:", len(internal_urls)) print("[+] Total External links:", len(external_urls)) print("[+] Total URLs:", len(external_urls) + len(internal_urls)) print("[+] Total crawled URLs:", max_urls) domain_name = urlparse(url).netloc # save the internal links to a file with open(f"_internal_links.txt", "w") as f: for internal_link in internal_urls: print(internal_link.strip(), file=f) # save the external links to a file with open(f"_external_links.txt", "w") as f: for external_link in external_urls: print(external_link.strip(), file=f)
Многократный запрос одного и того же веб-сайта за короткий промежуток времени может привести к тому, что веб-сайт заблокирует ваш IP-адрес. В этом случае вам необходимо использовать прокси-сервер для таких целей.

Как в Python извлечь все ссылки на веб‑странице , опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.
Респект и уважуха