Парсинг на Swift: создаем парсер данных за 30 минут

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему 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-дерево из HTML
  • doc.select(".titleline a") — находит все ссылки внутри элементов с классом titleline
  • try 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                

Блоги

Рекомендаций по книгам