Почему Swift — идеальный язык для парсинга, о котором молчат
Когда речь заходит о сборе данных из интернета, все почему-то сразу вспоминают Python с его BeautifulSoup и Scrapy. Но что, если я скажу вам, что Swift, язык, который вы используете для iOS-приложений, способен на не меньшее? Да, вы не ослышались. Парсинг на Swift — это не просто экзотика, а мощный инструмент, который может работать быстрее Python в 2-3 раза на одинаковых задачах. И сегодня мы это докажем.
В этой статье вы узнаете, как настроить среду, написать свой первый парсер на Swift, обработать ошибки и сохранить данные. Мы не будем использовать сложные абстракции — только чистый, понятный код, который вы сможете запустить уже через 15 минут.
Что такое парсинг и скрапинг на Swift?
Парсинг Swift — это процесс извлечения структурированных данных из веб-страниц или документов с использованием языка программирования Swift. В отличие от классического веб-скрапинга на Python, Swift предлагает нативную интеграцию с iOS/macOS, что делает его идеальным для создания приложений, которые сами собирают данные.
Основные сценарии использования скрапинга данных Swift:
- Сбор цен с интернет-магазинов для мониторинга конкурентов
- Извлечение контактных данных с сайтов-каталогов
- Анализ новостных лент и блогов
- Создание собственных API для приложений (когда официального API нет)
Для парсинга на Swift мы будем использовать две ключевые технологии: URLSession для загрузки HTML-страниц и библиотеку SwiftSoup для разбора HTML-документа. SwiftSoup — это аналог Python's BeautifulSoup, но написанный на чистом Swift.
Установка и настройка проекта
Для начала нам понадобится установить SwiftSoup. Проще всего это сделать через Swift Package Manager. Создайте новый проект или откройте существующий в Xcode, затем добавьте зависимость:
// В Package.swift
dependencies: [
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.6.0")
]
Или, если вы работаете через SPM без Xcode, просто добавьте строку выше в ваш Package.swift.
Альтернативный способ — использовать CocoaPods:
pod 'SwiftSoup', '~> 2.6.0'
После установки импортируйте библиотеку в ваш код:
import SwiftSoup
import Foundation
Готово! Теперь мы можем приступить к написанию парсера.
Пишем первый парсер на Swift: пошагово
Давайте напишем парсер, который извлекает заголовки статей с главной страницы Hacker News (news.ycombinator.com). Это классическая задача для обучения парсингу.
Шаг 1: Загрузка HTML-страницы
Используем URLSession для асинхронной загрузки данных. Swift — язык с сильной поддержкой async/await, так что мы будем использовать современный подход.
func fetchHTML(from urlString: String) async throws -> String {
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
guard let html = String(data: data, encoding: .utf8) else {
throw URLError(.cannotDecodeContentData)
}
return html
}
Обратите внимание: мы возвращаем строку HTML. Это сырые данные, которые мы будем парсить.
Шаг 2: Парсинг HTML с помощью SwiftSoup
Теперь самое интересное — извлечение данных. SwiftSoup работает как CSS-селекторы: вы указываете, какие элементы хотите получить.
func parseTitles(from html: String) throws -> [String] {
let doc = try SwiftSoup.parse(html)
let titles = try doc.select(".titleline a").array()
var result: [String] = []
for title in titles {
let text = try title.text()
result.append(text)
}
return result
}
Разберем, что здесь происходит:
SwiftSoup.parse(html)— создает DOM-дерево из HTMLdoc.select(".titleline a")— находит все ссылки внутри элементов с классомtitlelinetry title.text()— извлекает текст из каждого тега<a>
На Hacker News заголовки статей находятся именно в такой структуре. Если сайт изменит верстку, вам нужно будет адаптировать селектор.
Шаг 3: Собираем всё вместе
Теперь объединим загрузку и парсинг в единую функцию:
func scrapeHackerNews() async {
do {
let html = try await fetchHTML(from: "https://news.ycombinator.com/")
let titles = try parseTitles(from: html)
print("Найдено \(titles.count) заголовков:")
for (index, title) in titles.enumerated() {
print("\(index + 1). \(title)")
}
} catch {
print("Ошибка при парсинге: \(error.localizedDescription)")
}
}
Запустите эту функцию в вашем main.swift или в асинхронном контексте:
// В iOS/macOS приложении
Task {
await scrapeHackerNews()
}
// В консольном приложении
// Используйте @main с async
@main
struct ParserApp {
static func main() async {
await scrapeHackerNews()
}
}
Если всё сделано правильно, вы увидите в консоли список заголовков статей с Hacker News. Это ваш первый работающий парсер на Swift!
Продвинутые техники: обработка ошибок и пагинация
В реальном мире парсинг редко бывает таким простым. Сайты могут менять верстку, блокировать запросы или требовать авторизацию. Давайте рассмотрим, как с этим справляться.
Обработка ошибок сети и таймауты
Добавим обработку таймаутов и повторные попытки:
func fetchHTMLWithRetry(from urlString: String, retries: Int = 3) async throws -> String {
for attempt in 1...retries {
do {
return try await fetchHTML(from: urlString)
} catch {
print("Попытка \(attempt) не удалась: \(error.localizedDescription)")
if attempt == retries {
throw error
}
try await Task.sleep(nanoseconds: 2_000_000_000) // ждем 2 секунды
}
}
throw URLError(.cannotLoadFromNetwork)
}
Этот код автоматически повторит запрос до 3 раз с паузой в 2 секунды, если произошла сетевая ошибка.
Парсинг нескольких страниц (пагинация)
Часто данные разбросаны по нескольким страницам. На Hacker News пагинация реализована через параметр p в URL. Давайте соберем заголовки с первых 3 страниц:
func scrapeMultiplePages