Извлечение всех ссылок на веб-странице — обычная задача для веб-парсеров, полезно создавать продвинутые парсеры, которые сканируют каждую страницу определенного веб-сайта для извлечения данных, его также можно использовать для процесса диагностики SEO или даже на этапе сбора информации для проникновения. тестеры. В этом руководстве я расскажу, как с нуля на Python создать инструмент для извлечения ссылок, используя только запросы и библиотеки BeautifulSoup.
Установим зависимости:
pip3 install requests bs4 colorama
Откроем новый файл Python и импортируем необходимые нам модули:
import requests from urllib.parse import urlparse, urljoin from bs4 import BeautifulSoup import colorama
Мы собираемся использовать 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()
- Внутренние ссылки — это URL-адреса, которые ведут на другие страницы того же веб-сайта.
- Внешние ссылки — это URL-адреса, которые ведут на другие веб-сайты.
Поскольку не все ссылки в тегах привязки (теги) действительны (я экспериментировал с этим), некоторые из них являются ссылками на части веб-сайта, некоторые — javascript, поэтому давайте наишем функцию для проверки URL-адресов:
def is_valid(url):
"""
Проверяет, является ли url допустимым
"""
parsed = urlparse(url)
return bool(parsed.netloc) and bool(parsed.scheme)
Здесь проверяем наличие в URL-адресе правильной схемы (протокола, например http или https) и имени домена.
Теперь создадим функцию для возврата всех действительных 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")
Во-первых, инициализирована переменную urls набора URL-адресов, использован set Python, так-как нам не нужны лишние повторяющиеся ссылки.
Во-вторых, извлекается доменное имя из URL-адреса, которое понадобится для проверки полученных ссылок, на предмет внешних или внутренних.
В третьих, загружен HTML-контент веб-страницы, который обернул объектом soup для облегчения синтаксического анализа HTML.
Получим все 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
Завершим функцию:
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"{GRAY}[!] External link: {href}{RESET}")
external_urls.add(href)
continue
print(f"{GREEN}[*] Internal link: {href}{RESET}")
urls.add(href)
internal_urls.add(href)
return urls
В сухом остатке всё, что здесь сделано и проверяется:
- Если URL-адрес недействителен, перейдём к следующей ссылке.
- Если URL-адрес уже находится в
internal_urls, нам он уже не нужен. - Если URL-адрес является внешней ссылкой, распечатаем её серым цветом и добавив в наш глобальный набор
external_urls, перейдём к следующей ссылке.
Наконец, после всех проверок URL будет внутренней ссылкой, распечатываем её и добавляем в наши наборы urls и internal_urls.
Вышеупомянутая функция будет захватывать только ссылки одной конкретной страницы, что, если мы хотим извлечь все ссылки всего веб-сайта? Сделаем это:
# количество посещенных URL-адресов будет сохранено здесь
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"{YELLOW}[*] Crawling: {url}{RESET}")
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.
Хорошо, давайте проверим и убедимся, что вы используете это на веб-сайте, на который вы авторизованы, в противном случае я не несу ответственности за любой вред, который вы причиняете.
if __name__ == "__main__":
crawl("https://www.thepythoncode.com")
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)
Тестирую на этом сайте. Тем не менее, я настоятельно рекомендую вам не делать этого, это вызовет много запросов, приведет к переполнению веб-сервера и может заблокировать ваш IP-адрес.
Вот часть вывода:
Тут нужна картинка
После завершения сканирования будет выведено общее количество извлеченных и просканированных ссылок:
[+] Total Internal links: 90 [+] Total External links: 137 [+] Total URLs: 227 [+] Total crawled URLs: 30
Классно, правда? Я надеюсь, что этот рецепт будет вам полезен и вдохновит на создание подобных инструментов с использованием Python.
Я собрал все фрагменты и немного отредактировал код для сохранения найденных URL-адресов в файле, а также для получения URL-адреса из аргументов командной строки функции main, посмотрите содержимое файла link_extractor.py.
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
# инициализировать set's для внутренних и внешних ссылок (уникальные ссылки)
internal_urls = set()
external_urls = set()
total_urls_visited = 0
def is_valid(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)
# удалить параметры URL GET, фрагменты 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"{GRAY}[!] Внешняя ссылка: {href}{RESET}")
external_urls.add(href)
continue
print(f"{GREEN}[*] Внутреннея ссылка: {href}{RESET}")
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"{YELLOW}[*] Проверено: {url}{RESET}")
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
"""
url = 'https://alumnus.susu.ru'
max_urls = 500
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
# сохранить внутренние ссылки в файле
with open(f"{domain_name}_internal_links.txt", "w") as f:
for internal_link in internal_urls:
print(internal_link.strip(), file=f)
# сохранить внешние ссылки в файле
with open(f"{domain_name}_external_links.txt", "w") as f:
for external_link in external_urls:
print(external_link.strip(), file=f)
Есть некоторые веб-сайты, которые загружают большую часть своего контента с помощью JavaScript, в результате нам нужно вместо этого использовать библиотеку , которая позволяет нам выполнять Javascript с помощью . Я уже написал для этого сценарий, добавив всего несколько строк (поскольку очень похож на ). Вот посмотрите код записанный в файле link_extractor_js.py:
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"{GRAY}[!] External link: {href}{RESET}")
external_urls.add(href)
continue
print(f"{GREEN}[*] Internal link: {href}{RESET}")
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"{YELLOW}[*] Crawling: {url}{RESET}")
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"{domain_name}_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"{domain_name}_external_links.txt", "w") as f:
for external_link in external_urls:
print(external_link.strip(), file=f)
Многократный запрос одного и того же веб-сайта за короткий период времени может привести к тому, что веб-сайт заблокирует ваш IP-адрес. В этом случае вам необходимо использовать прокси-сервер для таких целей.
Если вы хотите вместо этого получать изображения, проверьте это руководство: Как загрузить все изображения с веб-страницы в Python, или, если вы хотите извлечь таблицы HTML, проверьте это руководство.
По мотивам
Как с помощью в Python извлечь все ссылки на веб‑сайты, опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.
Респект и уважуха

