Парсинг с помощью языка программирования Scala

Парсинг с помощью языка программирования Scala

В этом руководстве объясняется, как использовать три технологии для парсинга с помощью Scala. Сначала в статье объясняется, как на языке Scala с парсить статическую HTML-страницу с помощью jsoup и Scala Scraper. Затем объясняется, как на языке Scala отсканировать динамический HTML-сайт с помощью Selenium.

Настройка проекта Scala

Первым шагом будет создание проекта на языке Scala. В этом руководстве используется Scala версии 2.13.10 с sbt версии 1.7.2. Однако эти примеры также работают с Scala 2.12 и 3.

Выполните следующий скрипт для создания необходимых директорий:

mkdir scala-web-scraping && cd $_
git init
echo '.bsp/
.idea/
target/' > .gitignore
mkdir -p src/{main,test}/{scala,resources} project
echo 'sbt.version = 1.7.2' > ./project/build.properties
echo 'ThisBuild / version := "0.1.0"
ThisBuild / scalaVersion := "2.13.10"
lazy val root = (project in file("."))" > build.sbt

Назовите свой проект dev.draft. Затем измените файл build.sbt, чтобы включить зависимости для jsoup 1.15.3, scala-scraper 3.0.0 и selenium-java 4.5.0:

ThisBuild / version := "0.1.0-SNAPSHOT"

ThisBuild / scalaVersion := "2.13.10"

lazy val root = (project in file("."))
 .settings(
   name := "scala-web-scraping",
   libraryDependencies ++= Seq(
     "org.jsoup" % "jsoup" % "1.15.3",
     "net.ruippeixotog" %% "scala-scraper" % "3.0.0",
     "org.seleniumhq.selenium" % "selenium-java" % "4.5.0"
   )
 )

Основы парсинга с помощью jsoup

В каталоге src/main/scala/ создайте новый пакет dev.draft, а внутри этого пакета создайте файл JsoupScraper.scala со следующим содержимым:

package dev.draft

import org.jsoup._
import scala.jdk.CollectionConverters._

object JsoupScraper {

  def main(args: Array[String]): Unit = {
    val doc = Jsoup.connect("http://en.wikipedia.org/").get()
  }
}

Следуя документации jsoup, эта строка вызывает метод connect класса org.jsoup.Jsoup для загрузки веб-страницы, которую вы собираете:

val doc = Jsoup.connect("http://en.wikipedia.org/").get()

Большинство классов jsoup находятся в пакете org.jsoup.Jsoup. Здесь вы используете метод connect для загрузки всего тела страницы. Хотя parse – это другой метод, работающий с тем же синтаксисом, он может исследовать только документы, хранящиеся локально. Основное различие между этими двумя методами заключается в том, что connect загружает и анализирует, а parse просто анализирует без загрузки.

doc – это тип nodes.Document, который содержит следующее:

doc: nodes.Document = <! doctype html >
       

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

val title = doc.title()

Если вы используете println(title), тип и значение title должны отображаться следующим образом:

title: String = "Wikipedia, the free encyclopedia"

Для практических целей мы будем использовать только методы выбора (select) и извлечения (text и attr). Однако jsoup имеет множество других функций, помимо выполнения запросов и модификации HTML-документов. Например, его можно использовать для выполнения модульных тестов на сгенерированном HTML-коде.

Выборка с помощью jsoup

В этом учебнике вы выберете один из трех разделов на главной странице Википедии:

  • In the news
  • On this day
  • Did you know

Находясь в браузере на странице Википедии, щелкните правой кнопкой мыши на разделе "В новостях". В контекстном меню выберите пункт Иследовать в Firefox или Просмотреть Код в Chrome. Поскольку соответствующий исходный код содержится в

для получения содержимого раздела мы будем использовать id элемента со значением mp-itn:
val inTheNews = doc.select("#mp-itn b a")

Если вы используете println(inTheNews), вывод будет выглядеть следующим образом:

inTheNews: select.Elements = Annie Ernaux
An attack
Svante Paabo
the London Marathon
Ongoing
Recent deaths
Nominate an article

Выполнив те же шаги, что и раньше, чтобы просмотреть исходный код и получить содержимое раздела On this day, мы должны найти id со значением mp-otd, который можем использовать для получения элементов этого раздела:

val onThisDay = doc.select("#mp-otd b a")

Вывод будет выглядеть примерно так, как показано ниже:

onThisDay: select.Elements = October 10
Thanksgiving
Battle of Karbala
Ndyuka people
Triton
Spiro Agnew
Vidyasagar Setu

Снова выполним те же шаги, чтобы просмотреть исходный код и получить содержимое раздела Did you know, и получить id со значением mp-dyk, который можно использовать для получения элементов этого раздела:

val didYouKnow = doc.select("#mp-dyk b a")

Вывод будет выглядеть примерно так, как показано ниже:

List(JsoupElement(East African Mounted Rifles),
  JsoupElement(Kiriko),
  JsoupElement(the second season),
  JsoupElement(First National Bank Tower),
  JsoupElement(Roger Robinson),
  JsoupElement(Michigan banner))

В трех примерах выше мы объединили селекторы с пробелами, например #mp-otd b a. Это означает, что в каждом элементе в рамках внешнего тега и внутреннего тега есть ссылка на каждую статью.

Извлечение данных с помощью Scala Scraper

Как и в примере с jsoup, следующим шагом будет получение данных внутри каждого элемента. Соответствующие методы Scala Scraper для трех различных частей элементов HTML выглядят следующим образом:

  • Метод children используется для извлечения дочерних элементов.
  • Медот Text используется для извлечени текстового контента
    Он извлекает строку из таких элементов, как
  • Метод attr извлекает атрибуты. Например, вы можете использовать .attr("bar"), чтобы получить значение foo из bar="foo".
  • Например, следующая команда получает заголовок и ссылку href каждого элемента:

    val otds = for (otd <- onThisDay) yield (otd >> attr("title"), otd >> attr("href"))

    Вывод будет следующим:

    List((October 11,/wiki/October_11),
    (Mawlid,/wiki/Mawlid),
    (James the Deacon,/wiki/James_the_Deacon),
    (National Coming Out Day,/wiki/National_Coming_Out_Day),
    (Jin–Song Wars,/wiki/Jin%E2%80%93Song_Wars),
    (Ordinances of 1311,/wiki/Ordinances_of_1311),
    (Battle of Camperdown,/wiki/Battle_of_Camperdown))

    Следующий пример позволяет получить только заголовки:

    val headers = for (otd <- onThisDay) yield otd >> text

    Вывод:

    List(October 11,
     Mawlid,
     Saint James the Deacon,
     National Coming Out Day,
     Jin–Song Wars,
     Ordinances of 1311,
     Battle of Camperdown)

    Ограничения этих методов

    Одним из ограничений jsoup и Scala Scraper является то, что динамические веб-сайты и одностраничные приложения (SPA) не могут быть спаршены. Как уже упоминалось, JsoupBrowser просто парсит HTML-документы. Если вы хотите получить информацию с динамического веб-сайт или взаимодействовать с кодом JavaScript, вам нужно использовать безголовый браузер, такой как Selenium.

    Продвинутый веб парсинг с помощью Selenium

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

    Ниже мы будем использовать Selenium для выполнения тех же примеров, которые мы выполняли с помощью jsoup и Scala Scraper.

    Сначала с помощью Selenium загрузим WebDriver. Обратите внимание, что инструкции по загрузке и установке клиента отличаются для Firefox и Chrome. Чтобы использовать модуль WebDriver, загрузите последний релиз geckodriver и убедитесь, что его можно найти в системном PATH.

    В каталоге src/main/scala/dev/draft создайте файл SeleniumScraper.scala со следующим содержимым:

    package dev.draft
    
    import java.time.Duration
    import org.openqa.selenium.By
    import org.openqa.selenium.firefox.FirefoxDriver
    
    object SeleniumScraper {
      def main(args: Array[String]): Unit = {
        System.setProperty("webdriver.gecko.driver", "/usr/local/bin/geckodriver")
        val driver = new FirefoxDriver
        driver.manage.window.maximize()
        driver.manage.deleteAllCookies()
        driver.manage.timeouts.pageLoadTimeout(Duration.ofSeconds(40))
        driver.manage.timeouts.implicitlyWait(Duration.ofSeconds(30))
        driver.get("http://en.wikipedia.org/")
        val inTheNews = driver.findElement(By.id("#mp-itn b a"))
        println(inTheNews.getText)
        val onThisDay = driver.findElement(By.id("#mp-otd b a"))
        println(onThisDay.getText)
        val didYouKnow = driver.findElement(By.id("#mp-dyk b a"))
        println(didYouKnow.getText)
        driver.quit()
      }
    }

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

    Как уже упоминалось, Selenium также может парсить динамические веб-страницы. Например, на сайте Related Words можно ввести слово, чтобы получить все связанные слова и соответствующие им ссылки. Следующий код позволяет получить все динамически сгенерированные слова, связанные со словом Draft:

    package dev.draft
    
    import java.time.Duration
    import org.openqa.selenium.By
    import org.openqa.selenium.firefox.FirefoxDriver
    
    object SeleniumScraper {
      def main(args: Array[String]): Unit = {
        System.setProperty("webdriver.gecko.driver", "/usr/local/bin/geckodriver")
        val driver = new FirefoxDriver
        driver.manage.window.maximize()
        driver.manage.deleteAllCookies()
        driver.manage.timeouts.pageLoadTimeout(Duration.ofSeconds(40))
        driver.manage.timeouts.implicitlyWait(Duration.ofSeconds(30))
        driver.get("https://relatedwords.org/relatedto/" + "Draft")
        val relatedWords = driver.findElement(By.className("words"))
        println(relatedWords.getText)
        driver.quit()
      }
    }

    Заключение

    Scala Scraper и jsoup достаточно, когда вам нужно разобрать статическую веб-страницу HTML или проверить сгенерированный HTML-код. Однако, когда вам нужно проверить динамические веб-страницы или код JavaScript, вам нужно использовать такие инструменты, как Selenium.

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


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