Веб-скрапинг динамических сайтов с помощью Python

В этом руководстве вы узнаете, как использовать пакет Selenium Python для веб-скрапинга данных из различных HTML-элементов на YouTube и Hacker News.
8 min read
Dynamic website scraping

Большая часть доступных для веб-скрапинга данных находится на таких динамических сайтах, как Amazon и YouTube. Они предлагают интерактивный и отзывчивый пользовательский опыт, основанный на пользовательских интересах. Например, когда вы заходите в свой аккаунт на YouTube, представленный на нем видеоконтент подстраивается под ваш поиск. В результате веб-парсинг динамических сайтов может оказаться более сложной задачей, поскольку данные постоянно изменяются в зависимости от взаимодействия с пользователем.

Для того чтобы выполнить веб-скрапинг данных с динамических сайтов, необходимо использовать передовые методы, которые позволяют имитировать взаимодействие реального пользователя с сайтом, перемещаться, выбирать генерируемый JavaScript контент и обрабатывать асинхронные запросы JavaScript и XML (AJAX).  

В этом руководстве вы узнаете, как выполнять веб-скрапинг данных с динамического сайта с помощью Selenium — библиотеки Python с открытым исходным кодом.

Сбор данных с динамического сайта с помощью Selenium

Перед тем, как приступать к сбору данных с динамического сайта, необходимо разобраться с пакетом Python, который вы будете использовать, под названием Selenium.

Что представляет собой Selenium?

Selenium — пакет Python с открытым исходным кодом, представляющий собой систему автоматизированного тестирования. Она позволяет выполнять различные операции на динамических сайтах. В список доступных для нее задач входит открытие и закрытие диалоговых окон, поиск определенных запросов на YouTube или заполнение форм. И все это в предпочитаемом вами браузере.

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

Теперь, когда вы знаете, как работает Selenium, давайте приступим.

Создайте новый проект Python

Первое, что вам нужно сделать, это создать новый проект Python. Создайте каталог с именем  data_scraping_project , в котором будут храниться все собранные данные и файлы исходного кода. В этом каталоге будет два подкаталога:

  1. scripts будет содержать все сценарии Python, которые извлекают и собирают данные с динамического веб-сайта.
  2. data — место, где будут храниться все данные, извлеченные из динамического сайта.

Установка пакетов Python

После создания каталога data_scraping_project необходимо установить следующие пакеты Python, которые помогут вам искать, собирать и сохранять данные с динамического сайта:

  • Selenium
  • Webdriver Manager будет управлять бинарными драйверами для различных браузеров. Webdriver предоставляет вам набор API, позволяющий запускать различные команды для взаимодействия с сайтами, облегчая разбор, загрузку и изменение содержимого.
  • pandas сохранит извлеченные с динамического сайта данные в простой CSV-файл.

Вы можете установить пакет Selenium Python, выполнив в терминале следующую команду pip:

pip install selenium

Selenium будет использовать бинарный драйвер для управления выбранным вами браузером. Этот пакет Python предоставляет драйверы для следующих браузеров: Chrome, Chromium, Brave, Firefox, IE, Edge и Opera.

Затем выполните в терминале следующую команду pip для установки webdriver-manager:

pip install webdriver-manager

Чтобы установить pandas, выполните следующую команду pip:

pip install pandas

Что вы будете парсить

В этой статье мы извлечем данные из двух разных мест — YouTube-канала Programming with Mosh и Hacker News:

Программирование с YouTube-каналом Mosh

С YouTube-канала Programming with Mosh можно получить следующую информацию:

  • Название видеоролика.
  • Ссылка на видео или его URL.
  • Ссылка на изображение или его URL.
  • Количество просмотров для конкретного видео.
  • Время публикации видео.
  • Комментарии с определенного URL видеоролика на YouTube.

Из Hacker News вы получите следующие данные:

  • Название статьи.
  • Ссылка на статью.
Скриншот Hacker News

Теперь, когда вы знаете, что будете парсить, давайте создадим новый Python-скрипт (data_scraping_project/scripts/youtube_videos_list.py).

Импорт пакетов Python

Во-первых, вам понадобится импортировать пакеты Python, которые вы будете использовать для поиска, сбора и хранения данных в CSV-файл:


# import libraries
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import pandas as pd

Инициализация Webdriver

Для инициализации Webdriver необходимо выбрать браузер, который будет использовать Selenium (в данном случае Chrome), а затем установить соответствующий бинарный драйвер.

В Chrome есть инструменты разработчика для отображения HTML-кода страницы. Они помогут определиться с элементами для сканирования и сбора данных. Чтобы отобразить HTML-код, необходимо щелкнуть правой кнопкой мыши страницу в браузере Chrome и выбрать Inspect Element.

Чтобы установить бинарный драйвер для Chrome, запустите следующий код:

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

Бинарный драйвер для Chrome будет установлен на вашей машине и автоматически инициализирует Webdriver.

Парсите данные с помощью Selenium

Чтобы выполнить веб-скрапинг данных с помощью Selenium, необходимо определить URL YouTube в простой переменной Python (url). Из этой ссылки вы собираете все данные, о которых говорилось ранее, за исключением комментариев с конкретного URL в YouTube:

# Define the URL
url = "https://www.youtube.com/@programmingwithmosh/videos"

# load the web page
driver.get(url)

# set maximum time to load the web page in seconds
driver.implicitly_wait(10)

Selenium автоматически загружает ссылку на YouTube в браузере Chrome. Кроме того, задается временной интервал (10 секунд), чтобы убедиться, что страница полностью загружена (включая все HTML-элементы). Это поможет вам выполнить веб-скрапинг данных, отображаемых с помощью JavaScript.

Парсите данные, используя теги и id

Одним из преимуществ Selenium является то, что он может извлекать данные, используя самые разные представленные на странице элементы, включая id и теги.

Например, для веб-скрапинга данных можно использовать либо id (post-title), либо теги (h1 и p):

<h1 id ="post-title">Introduction to data scrapping using Python</h1>
<p>You can use selenium python package to collect data from any dynamic website</p>

Или, если вы хотите получить данные по ссылке на YouTube, необходимо использовать, представленный на странице идентификатор. Откройте URL YouTube в браузере, затем щелкните правой кнопкой мыши и выберите Inspect, чтобы определить id. Затем с помощью мыши просмотрите страницу и определите id, который содержит список представленных на канале видео:

Используйте Webdriver для веб-скрапинга данных, которые находятся в пределах определенного id. Чтобы найти HTML-элемент по id-атрибуту, вызовите метод find_element() Selenium и передайте By.ID в качестве первого аргумента, а id — в качестве второго аргумента.

Чтобы собрать для каждого ролика его заголовок и ссылку на видео, необходимо использовать id-атрибут video-title-link. Поскольку вы собираетесь собрать несколько HTML-элементов с этим «айдишником», следует использовать метод find_elements():

# collect data that are withing the id of contents
contents = driver.find_element(By.ID, "contents")

#1 Get all the by video tite link using id video-title-link
video_elements = contents.find_elements(By.ID, "video-title-link")

#2 collect title and link for each youtube video
titles = []
links = []

for video in video_elements:

    #3 Extract the video title
    video_title = video.get_attribute("title")

    #4 append the video title
    titles.append(video_title)

    #5 Extract the video link
    video_link = video.get_attribute("href")

#6 append the video link
links.append(video_link)

Этот код выполняет следующие задачи:

  • Он собирает данные, которые находятся внутри id-атрибута объекта contents.
  • Он собирает все HTML-элементы с id-атрибутом video-title-link из объекта WebElement под названием contents.
  • Он создает два списка для добавления заголовков и ссылок.
  • Он извлекает заголовок (title) видео с помощью метода get_attribute() и передает его.
  • Он добавляет название видео в список с заголовками.
  • Он извлекает ссылку на видео с помощью метода get_atribute() и передает href в качестве аргумента.
  • Он добавляет ссылку на видео в соответствующий список для URL.

На этом этапе все названия видео и ссылки будут находиться в двух списках Python: titles and links.

Далее необходимо извлечь ссылку на размещенное на странице изображение до того, как вы нажмете на ссылку видео YouTube для просмотра ролика. Чтобы получить ссылку на изображение, необходимо найти все элементы HTML, вызвав метод  find_elements() и передав  By.TAG_NAME в качестве первого аргумента, а название тега в качестве второго аргумента:

#1 Get all the by Tag
img_elements = contents.find_elements(By.TAG_NAME, "img")

#2 collect img link and link for each youtube video
img_links = []

for img in img_elements:

    #3 Extract the img link
    img_link = img.get_attribute("src")
    if img_link:
        #4 append the img link
        img_links.append(img_link)

Этот код собирает все HTML-элементы с тегом img из объекта WebElement под названием contents. Он также создает список для добавления ссылок на изображения и извлекает его с помощью метода get_attribute(), передавая src в качестве аргумента. Наконец, он добавляет ссылку на изображение в список img_links.

Вы также можете использовать id и название тега, чтобы извлечь максимальное количество данных, связанных с видеороликом на YouTube. Перейдя по URL на YouTube, можно увидеть количество просмотров и время публикации для каждого видео. Чтобы извлечь эту информацию, необходимо собрать все HTML-элементы, имеющие идентификатор metadata-line, а затем собрать данные из HTML-элементов тега span:

#1 find the element with the specific ID you want to scrape
meta_data_elements = contents.find_elements(By.ID, 'metadata-line')

#2 collect data from span tag
meta_data = []

for element in meta_data_elements:
    #3 collect span HTML element
    span_tags = element.find_elements(By.TAG_NAME, 'span')

    #4 collect span data
    span_data = []
    for span in span_tags:
        #5 extract data for each span HMTL element.
        span_data.append(span.text)
    #6 append span data to the list
    meta_data.append(span_data)

# print out the scraped data.
print(meta_data)

Этот блок кода собирает все HTML-элементы, имеющие id-атрибут metadata-line, из объекта WebElemen под названием contents и создает список для добавления данных из тега span, который будет содержать количество просмотров и время публикации.

Он также собирает все HTML-элементы с тегом span из объекта WebElement под названием meta_data_elements и создает список с их данными. Затем он извлекает текстовые данные из HTML-элемента span и добавляет их в список span_data. И наконец, он добавляет данные из списка span_data в meta_data.

Данные, извлеченные из HTML-элемента span, будут выглядеть следующим образом:

Далее необходимо создать два списка Python и сохранить отдельно количество просмотров и время публикации:

#1 Iterate over the list of lists and collect the first and second item of each sublist
views_list = []
published_list = []

for sublist in meta_data:
    #2 append number of views in the views_list
    views_list.append(sublist[0])

    #3 append time published in the published_list
    published_list.append(sublist[1])

Здесь вы создаете два списка Python, которые извлекают данные из meta_data, и добавляете количество просмотров в views_list, а время публикации — в published_list.

На данном этапе вы извлекли название видео, URL страницы с видео, URL изображения, количество просмотров и время публикации ролика. Эти данные можно сохранить в DataFrame с помощью пакета Python под названием pandas . Используйте следующий код для сохранения данных из titles, links, img_links, views_list, и published_list в DataFrame (pandas):

# save in pandas dataFrame
data = pd.DataFrame(

list(zip(titles, links, img_links, views_list, published_list)),

columns=['Title', 'Link', 'Img_Link', 'Views', 'Published']
)

# show the top 10 rows
data.head(10)

# export data into a csv file.
data.to_csv("../data/youtube_data.csv",index=False)

driver.quit()

Вот как должны выглядеть извлеченные данные в DataFrame (pandas):

Эти сохраненные данные экспортируются из pandas в CSV-файл под названием youtube_data.csv с помощью функции to_csv().

Теперь вы можете запустить youtube_videos_list.py и убедиться, что все работает правильно.

Веб-скрапинг данных с помощью CSS селектора

Selenium также может извлекать данные на основе определенных шаблонов в HTML-элементах с помощью CSS-селектора. Селектор используется для выделения определенных элементов по их id, тегу, классу и другим атрибутам.

Например, здесь, в HTML-коде страницы присутствует сразу несколько элементов div, один из которых имеет класс "inline-code":

<html>
<body>
<p>Hello World!</p>
<div>Learn Data Scraping</div>
<div class="inline-code"> data scraping with Python code</div>
<div>Saving</div>
</body>
</html>

Вы можете использовать CSS-селектор для поиска на странице тега div с классом “‘inline-code”`. Этот же подход можно применить и для извлечения комментариев под видеороликом в YouTube.

Теперь давайте воспользуемся CSS-селектором, чтобы собрать комментарии, опубликованные к этому видео на YouTube.

Раздел комментариев на YouTube доступен под следующим тегом и названием класса:

<ytd-comment-thread-renderer class="style-scope ytd-item-section-renderer">...</tyd-comment-thread-renderer>

Создадим новый скрипт (data_scraping_project/scripts/youtube_video_ comments.py). Импортируйте все необходимые пакеты, аналогично показанному ранее примеру, и добавьте следующий код для автоматического запуска браузера Chrome, просмотра URL-адреса видеоролика в YouTube и веб-скрапинга комментариев с помощью CSS-селектора:

#1 instantiate chrome driver
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

#2 Define the URL
url = "https://www.youtube.com/watch?v=hZB5bHDCmeY"

#3 Load the webpage
driver.get(url)

#4 define the CSS selector
comment_section = 'ytd-comment-thread-renderer.ytd-item-section-renderer’

#5 wait until element matching the given criteria to be found
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, comment_section))
    )
except:
    driver.quit()

#6. collect HTML elements within the CSS selector
comment_blocks = driver.find_elements(By.CSS_SELECTOR,comment_section)

Этот код инициализирует драйвер для Chrome и определяет ссылку на видео в YouTube, чтобы спарсить опубликованные комментарии. Затем он загружает страницу в браузере и ожидает 10 секунд, пока не появятся HTML-элементы, соответствующие нужному CSS-селектору.

Затем он собирает все HTML-элементы comment с помощью CSS-селектора ytd-comment-thread-renderer.ytd-item-section-renderer и сохраняет их в объекте WebElement comment_blocks.

Затем вы можете извлечь имя каждого автора с помощью «айдишника» author-text и текст с помощью еще одного id — content-text, из каждого комментария в объекте WebElement под названием comment_blocks:

#1 specify the id attribute for author and comment
author_id = 'author-text'
comment_id = 'content-text'

#2 Extract the text value for each comment and author in the list
comments = []
authors = []

for comment_element in comment_blocks:
    #3 collect author for each comment
    author = comment_element.find_element(By.ID, author_id)

    #4 append author name
    authors.append(author.text)

    #5 collect comments
    comment = comment_element.find_element(By.ID, comment_id)

    #6 append comment text
    comments.append(comment.text)

#7 save in pandas dataFrame
comments_df = pd.DataFrame(list(zip(authors, comments)), columns=['Author', 'Comment'])

#8 export data into a CSV file.
comments_df.to_csv("../data/youtube_comments_data.csv",index=False)

driver.quit()

Этот код задает id для автора и комментария. Затем он создает два списка для добавления имени и текста. Он собирает каждый HTML-элемент, имеющий указанные «айдишники», из объекта WebElement и добавляет данные в соответствующие списки.

Наконец, он сохраняет собранные данные в DataFrame (pandas) и экспортирует их в CSV-файл под названием youtube_comments_data.csv.

Вот как будут выглядеть авторы и комментарии из первых десяти строк в DataFrame (pandas):

Веб-скрапинг данных с помощью класса

В дополнение к извлечению данных с помощью CSS-селектора, вы также можете парсить информацию, используя для целеуказания определенный класс. Чтобы найти с помощью Selenium HTML-элемент по имени класса, необходимо вызвать метод find_element() и передать By.CLASS_NAME в качестве первого аргумента, а в качестве второго аргумента — название класса.

В этом разделе вы будете использовать имя класса для извлечения заголовков и ссылок на статьи, опубликованные на Hacker News. На этой странице HTML-элемент, содержащий заголовок и ссылку на статью, имеет класс titleline:

<span class="titleline"><a href="https://mullvad.net/en/browser">The Mullvad Browser</a><span class="sitebit comhead"> (<a href="from?site=mullvad.net"><span class="sitestr">mullvad.net</span></a>)</span></span></td></tr><tr><td colspan="2"></td><td class="subtext"><span class="subline">

<span class="score" id="score_35421034">302 points</span> by <a href="user?id=Foxboron" class="hnuser">Foxboron</a> <span class="age" title="2023-04-03T10:11:50"><a href="item?id=35421034">2 hours ago</a></span> <span id="unv_35421034"></span> | <a href="hide?id=35421034&amp;auth=60e6bdf9e482441408eb9ca98f92b13ee2fac24d&amp;goto=news" class="clicky">hide</a> | <a href="item?id=35421034">119&nbsp;comments</a> </span>

Создайте новый Python-скрипт (data_scraping_project/scripts/hacker_news.py), импортируйте все необходимые пакеты и добавьте следующий код для поиска заголовков и ссылок статей, опубликованных на Hacker News:

#1 define url
hacker_news_url = 'https://news.ycombinator.com/'

#2 instantiate chrome driver
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

#3 load the web page
driver.get(hacker_news_url)

#4 wait until element matching the given criteria to be found
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, 'titleline'))
    )
except:
    driver.quit()

#5 Extract the text value for each title and link in the list
titles= []
links = []

#6 Find all the articles on the web page
story_elements = driver.find_elements(By.CLASS_NAME, 'titleline')

#7 Extract title and link for each article
for story_element in story_elements:

    #8 append title to the titles list
    titles.append(story_element.text)

    #9 extract the URL of the article
    link = story_element.find_element(By.TAG_NAME, "a")

    #10 appen link to the links list
    links.append(link.get_attribute("href"))

driver.quit()

Этот код определяет URL страницы, автоматически запускает браузер Chrome, а затем переходит по URL новости на Hacker News. При этом он подождет 10 секунд, пока не появятся HTML-элементы, соответствующие нужному CLASS NAME.

Затем он создает два списка для добавления заголовков и ссылок статей. Он также собирает все HTML-элементы, содержащие класс titleline, и добавляет извлеченные заголовки и ссылки в объект WebElement под названием story_elements.

Наконец, код добавляет название статьи в список с заголовками и собирает HTML-элементы с тегом a из объекта story_element. Он извлекает ссылку с помощью метода get_attribute() и добавляет ее в список links.

Далее необходимо использовать метод to_csv() из библиотеки pandas для экспорта извлеченных данных. Вы будете экспортировать заголовки и ссылки в CSV-файл hacker_news_data.csv и сохранять данные в каталоге:

# save in pandas dataFrame
hacker_news = pd.DataFrame(list(zip(titles, links)),columns=['Title', 'Link'])

# export data into a csv file.
hacker_news.to_csv("../data/hacker_news_data.csv",index=False)

Вот как заголовки и ссылки из первых пяти строк появляются в DataFrame (pandas):

Как обрабатывать бесконечное прокручивание

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

Чтобы в этом случае получить больше данных, необходимо дать Selenium команду прокрутить страницу до самого низа, дождаться загрузки нового контента, а затем автоматически спарсить нужные данные. Например, следующий сценарий на языке Python прокрутит первые сорок результатов поиска книг по Python и спарсит ссылки на них:

#1 import packages
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium import webdriver  
from selenium.webdriver.common.by import By  
import time  
  
#2 Instantiate a Chrome webdriver  
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))  
  
#3 Navigate to the webpage  
driver.get("https://example.com/results?search_query=python+books")  
  
#4 instantiate a list to keep links  
books_list = []  
  
#5 Get the height of the current webpage  
last_height = driver.execute_script("return document.body.scrollHeight")  
  
#6 set target count  
books_count = 40  
  
#7 Keep scrolling down on the web page  
while books_count > len(books_list):  
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  
  
    #8 Wait for the page to load  
    time.sleep(5)  
  
    #9 Calculate the new height of the page  
    new_height = driver.execute_script("return document.body.scrollHeight")  
  
    #10 Check if you have reached the bottom of the page  
    if new_height == last_height:  
        break  
    last_height = new_height  
  
    #11 Extract the data  
    links = driver.find_elements(By.TAG_NAME, "a")  
    for link in links:  
        #12 append extracted data  
        books_list.append(link.get_attribute("href"))  
  
#13 Close the webdriver  
driver.quit()

Этот код импортирует пакеты Python, которые планируются для использования, а затем инициализирует и запускает Chrome. После этого он переходит на страницу и создает список для добавления ссылок.

Он получает высоту текущей страницы с помощью скрипта return document.body.scrollHeight и устанавливает количество ссылок, которые вы хотите собрать. Затем он продолжает прокручивать страницу вниз до тех пор, пока значение переменной book_count не станет больше длины списка в book_list. Далее наступает 5-секундное ожидание загрузки страницы.

Он вычисляет новую высоту, выполняя скрипт return  document.body.scrollHeight, и проверяет, достигнута ли нижняя часть страницы. Если да, то цикл завершается. В противном случае он обновляет значение  last_height  и продолжает прокрутку вниз. Наконец, он собирает HTML-элементы с именем тега   из объекта WebElement, а также извлекает и добавляет ссылки в соответствующий список. По завершению их сбора он закрывает Webdriver.

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

Веб-скрапинг с помощью инструментов Bright Data

Несмотря на возможность веб-скрапинга данных с помощью таких программ с открытым исходным кодом, как, например, Selenium, они, как правило, не имеют какой-либо сопроводительной техподдержки. Кроме того, процесс парсинга с их помощью может оказаться достаточно сложным и длительным. Поэтому если вы ищете мощное и надежное решение для веб-скрапинга, стоит обратить свое внимание на предложение компании Bright Data.

Bright Data — онлайн-платформа, которая облегчает задачу по веб-скрапингу информации, находящейся в публичном доступе, предоставляя собственные инструменты. Среди них есть эффективные решения для веб-скрапинга, прокси и предварительно собранные наборы данных. Вы даже можете использовать Web Scraper IDE для создания собственных скраперов в среде разработки JavaScript.

Web Scraper IDE также имеет готовые функции скрапинга и шаблоны кода для различных популярных динамических сайтов, включая Indeed scraper и Walmart scraper. Это означает, что с его помощью можно легко ускорить разработку и быстро масштабировать систему по парсингу.

Bright Data предлагает целый ряд опций для форматирования данных, включая JSON, NDJSON, CSV и Microsoft Excel. Он также интегрирован с различными платформами, что позволяет легко обмениваться собранными данными.

Заключение

Сбор данных с динамических сайтов требует усилий и планирования. С помощью Selenium вы сможете в автоматическом режиме собирать данные с любого динамического сайта.

Хотя вполне реально извлекать данные с помощью Selenium, такой способ потребует от вас много времени и больших трудозатрат. Именно поэтому мы рекомендуем использовать для парсинга динамических сайтов Web Scraper IDE. Благодаря готовым функциям и шаблонам кода с его помощью вы можете сразу же приступить к извлечению данных.