В этом руководстве объясняется, как использовать три технологии для парсинга с помощью 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. Поскольку соответствующий исходный код содержится в Если вы используете println(inTheNews), вывод будет выглядеть следующим образом: Выполнив те же шаги, что и раньше, чтобы просмотреть исходный код и получить содержимое раздела On this day, мы должны найти id со значением mp-otd, который можем использовать для получения элементов этого раздела: Вывод будет выглядеть примерно так, как показано ниже: Снова выполним те же шаги, чтобы просмотреть исходный код и получить содержимое раздела Did you know, и получить id со значением Вывод будет выглядеть примерно так, как показано ниже: В трех примерах выше мы объединили селекторы с пробелами, например #mp-otd b a. Это означает, что в каждом элементе в рамках внешнего тега Как и в примере с jsoup, следующим шагом будет получение данных внутри каждого элемента. Соответствующие методы Scala Scraper для трех различных частей элементов HTML выглядят следующим образом: Например, следующая команда получает заголовок и ссылку href каждого элемента: Вывод будет следующим: Следующий пример позволяет получить только заголовки: Вывод: Одним из ограничений jsoup и Scala Scraper является то, что динамические веб-сайты и одностраничные приложения (SPA) не могут быть спаршены. Как уже упоминалось, JsoupBrowser просто парсит HTML-документы. Если вы хотите получить информацию с динамического веб-сайт или взаимодействовать с кодом JavaScript, вам нужно использовать безголовый браузер, такой как Selenium. Selenium - это инструмент, который можно использовать для создания ботов и автоматизации модульных тестов, а также для парсинга. Ниже мы будем использовать Selenium для выполнения тех же примеров, которые мы выполняли с помощью jsoup и Scala Scraper. Сначала с помощью Selenium загрузим WebDriver. Обратите внимание, что инструкции по загрузке и установке клиента отличаются для Firefox и Chrome. Чтобы использовать модуль WebDriver, загрузите последний релиз geckodriver и убедитесь, что его можно найти в системном PATH. В каталоге src/main/scala/dev/draft создайте файл SeleniumScraper.scala со следующим содержимым: В приведенном выше коде мы получаем те же три раздела главной страницы Википедии, используя синтаксис селектора CSS. Как уже упоминалось, Selenium также может парсить динамические веб-страницы. Например, на сайте Related Words можно ввести слово, чтобы получить все связанные слова и соответствующие им ссылки. Следующий код позволяет получить все динамически сгенерированные слова, связанные со словом Draft: Scala Scraper и jsoup достаточно, когда вам нужно разобрать статическую веб-страницу HTML или проверить сгенерированный HTML-код. Однако, когда вам нужно проверить динамические веб-страницы или код JavaScript, вам нужно использовать такие инструменты, как Selenium. В этой статье мы рассказали, как создать проект Scala и использовать jsoup и Scala Scraper для загрузки и разбора HTML. Мы также познакомились с некоторыми методами парсинга. Наконец, мы увидели, как можно использовать библиотеку безголового браузера, например Selenium, для анализа динамических веб-сайтов.val inTheNews = doc.select("#mp-itn b a")
inTheNews: select.Elements = Annie Ernaux
An attack
Svante Paabo
the London Marathon
Ongoing
Recent deaths
Nominate an article
val onThisDay = doc.select("#mp-otd b a")
onThisDay: select.Elements = October 10
Thanksgiving
Battle of Karbala
Ndyuka people
Triton
Spiro Agnew
Vidyasagar Setu
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))
и внутреннего тега
есть ссылка на каждую статью.
Извлечение данных с помощью Scala Scraper
Он извлекает строку из таких элементов, как 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)
Ограничения этих методов
Продвинутый веб парсинг с помощью Selenium
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()
}
}
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()
}
}
Заключение