Синхронный парсер интернет страниц
Прежде, чем делать попытки написать асинхронный парсер, напишем в качестве старта обычный (синхронный) парсер.
Мы будем собирать все заголовки с сайта https://www.python.org.
Начнём мы с главной страницы, а затем будем проходить по каждой внутренней ссылке на странице. С каждой из них мы будем собирать заголовок (то, что идёт в метатеге title).
Приведу код парсера, и объясню, как он работает.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | from urllib.parse import urljoin, urlparse, urldefrag import requests from bs4 import BeautifulSoup def fetch_and_validate_page_contents(url): while True: try: response = requests.get( url, timeout=15, stream=True ) if "text/html" not in response.headers.get("Content-Type"): return None if urlparse(response.url).hostname != HOSTNAME: return None return response.text except requests.RequestException: continue def parse_page(url): text = fetch_and_validate_page_contents(url) if text is None: return None, set() soup = BeautifulSoup(text, "lxml") title = soup.find("title") if title is not None: title = title.text all_internal_links = set() for a in soup.find_all("a"): link = a.get("href") if link is None: continue if link.startswith("#"): continue absolute_link = urljoin(WEBSITE, link) if urlparse(absolute_link).hostname == HOSTNAME: all_internal_links.add( urldefrag(absolute_link)[0] ) return title, all_internal_links def main(): visited_urls = {} urls_to_visit = {WEBSITE} while urls_to_visit: current_url_to_parse = urls_to_visit.pop() title, internal_links = parse_page(current_url_to_parse) visited_urls[current_url_to_parse] = title # if len(visited_urls) > 100: # break for url in internal_links: if url not in visited_urls: urls_to_visit.add(url) return visited_urls if __name__ == "__main__": WEBSITE = "https://www.python.org" HOSTNAME = urlparse(WEBSITE).hostname titles = main() print(titles) |
Пройдёмся по каждой из функций.
fetch_and_validate_page_contents
В бесконечном цикле получает заголовки ответа страницы с заданным адресом.
- Если это не html-контент (например, URL картинки, или исполняемого файла), то возвращаем None
- Если это перенаправление на внешний сайт (например, некоторые страницы https://www.python.org перенаправляют на https://peps.python.org), то также возвращаем None
- Если произошла ошибка запроса (таймаут сервера, или проблемы с интернет-соединением), то пробуем ещё раз. По-хорошему, для таких случаев необходимо настроить логирование, однако сейчас мы не будем углубляться в данную тему
parse_page
Для заданного адреса возвращает кортеж (заголовок, множество внутренних ссылок)
- Если страница невалидна, возвращаем заголовок None и пустое множество ссылок (считаем, что их там нет)
- Далее, с помощью BeautifulSoup обрабатываем HTML и ищем заголовок
- Находим все гиперссылки на странице, фильтруем их, игнорируя якорные ссылки (начинающиеся с #, эти ссылки по факту ведут на ту же страницу) и ссылки с другими доменами. Для каждой ссылки мы убираем фрагмент (то, что после #, например https://www.python.org/community/diversity/#diversity-appendix и https://www.python.org/community/diversity/ суть одна страница)
- Возвращаем заголовок страницы и множество внутренних ссылок
main
Главная функция, она, собственно, и собирает все заголовки страниц воедино.
- Словарь visited_urls служит для хранения уже посещенных URL и их заголовков
- Множество urls_to_visit необходимо для сбора ссылок, на которые ещё нужно перейти
- Пока есть URL для посещения, извлекаем один из них, и добавляем заголовок, полученный из Интернет-страницы в visited_urls
- Добавляем все найденные внутренние ссылки в urls_to_visit, если они еще не были посещены
- Возвращаем словарь всех посещенных URL и их заголовков
Главный блок
В главном блоке программы мы устанавливаем начальный URL (на самом деле, мы можем парсить не только python.org) и доменное имя (HOSTNAME).
Затем получаем у функции main() словарь заголовков и печатаем его.
Обратите внимание на строки 53-54. Поскольку сайт достаточно объёмный, его парсинг будет происходить очень долго. Можно раскомментировать данные строки, чтобы получить хоть какой-то результат за разумное время (будут посещены только первые 100 страниц).
Можно ли ускорить парсинг сайта? Можно. Одним из способов является асинхронный парсер, который мы напишем в следующих статьях.