The official Swift SDK for Spidra that allows you to scrape pages, run browser actions, batch-process URLs, and crawl entire sites using modern Swift Concurrency. All results come back as structured data ready to feed into your iOS, macOS, or server-side Swift applications.
Add Spidra to your Package.swift dependencies:
dependencies: [
.package(url: "https://github.com/spidra-io/spidra-swift.git", from: "1.0.0")
]Or add it directly via Xcode: File > Add Packages... and paste the repository URL.
import Spidra
Task {
do {
let spidra = SpidraClient(apiKey: "spd_YOUR_API_KEY")
let params = ScrapeParams(
urls: [ScrapeUrl(url: "https://news.ycombinator.com")],
prompt: "List the top 5 stories with title, points, and comment count",
output: "json"
)
let job = try await spidra.scrape.run(params)
// Print the extracted content
if let content = job.result?.content?.value {
print(content)
}
} catch {
print("Error: \(error.localizedDescription)")
}
}All scrape jobs run asynchronously using Swift's async/await. The run() method submits a job and polls until it finishes. Up to 3 URLs can be passed per request and they are processed in parallel.
let params = ScrapeParams(
urls: [ScrapeUrl(url: "https://example.com/pricing")],
prompt: "Extract all pricing plans with name, price, and included features",
output: "json"
)
let job = try await spidra.scrape.run(params)
print(job.result?.content?.value ?? "No data")When you need a guaranteed shape, pass a schema. The API will enforce the structure and return null for missing fields rather than hallucinating values.
let schemaDict: [String: Any] = [
"type": "object",
"required": ["title", "company", "remote"],
"properties": [
"title": ["type": "string"],
"company": ["type": "string"],
"remote": ["type": ["boolean", "null"]]
]
]
let params = ScrapeParams(
urls: [ScrapeUrl(url: "https://jobs.example.com/senior-engineer")],
prompt: "Extract the job listing details",
output: "json",
schema: AnyCodable(schemaDict)
)
let job = try await spidra.scrape.run(params)Pass useProxy: true and a proxyCountry code to route the request through a specific country. Useful for geo-restricted content.
let params = ScrapeParams(
urls: [ScrapeUrl(url: "https://www.amazon.de/gp/bestsellers")],
prompt: "List the top 10 products",
useProxy: true,
proxyCountry: "de"
)Pass cookies as a string to scrape pages that require a login session.
let params = ScrapeParams(
urls: [ScrapeUrl(url: "https://app.example.com/dashboard")],
prompt: "Extract the monthly revenue",
cookies: "session=abc123; auth_token=xyz789"
)Actions let you interact with the page before the scrape runs. They execute in order.
let url = ScrapeUrl(
url: "https://example.com/products",
actions: [
.click(selector: "#accept-cookies", value: nil),
.wait(duration: 1000),
.scroll(to: "80%")
]
)
let params = ScrapeParams(urls: [url], prompt: "Extract product names and prices")
let job = try await spidra.scrape.run(params)Available actions:
.click(selector:value:).type(selector:value:).check(selector:value:).uncheck(selector:value:).wait(duration:).scroll(to:).forEach(observe:mode:...)
forEach finds a set of elements and processes each individually. Best used when dealing with pagination, clicking into detail pages, or looping over long lists.
let forEachAction = BrowserAction.forEach(
observe: "Find all book cards in the product grid",
mode: "inline",
captureSelector: "article.product_pod",
maxItems: 20,
itemPrompt: "Extract title, price, and star rating. Return as JSON",
waitAfterClick: nil,
actions: nil,
pagination: nil
)
let url = ScrapeUrl(
url: "https://books.toscrape.com/",
actions: [forEachAction]
)Modes available:
- inline: Read element content directly without navigating.
- navigate: Follow each element's link to its destination page and capture content there.
- click: Click each element, capture the content that appears (e.g., a modal), then move on.
You can also use pagination to navigate through multiple pages automatically:
let pagination = BrowserActionPagination(nextSelector: "li.next > a", maxPages: 3)Use submit() and get() when you want to manage polling yourself.
// Submit a job immediately
let queued = try await spidra.scrape.submit(ScrapeParams(
urls: [ScrapeUrl(url: "https://example.com")],
prompt: "Extract the main headline"
))
// Check status later
let status = try await spidra.scrape.get(queued.jobId)
if status.status == "completed" {
print(status.result?.content?.value ?? "")
}Override default polling intervals via PollOptions:
let options = PollOptions(pollInterval: 2.0, timeout: 60.0)
let job = try await spidra.scrape.run(params, options: options)Submit up to 50 URLs in a single request. All URLs are processed in parallel.
let params = BatchScrapeParams(
urls: [
"https://shop.example.com/product/1",
"https://shop.example.com/product/2",
"https://shop.example.com/product/3"
],
prompt: "Extract product name, price, and availability",
output: "json",
useProxy: true
)
let batch = try await spidra.batch.run(params)
for item in batch.items {
if item.status == "completed" {
print("Completed: \(item.url)")
} else if item.status == "failed" {
print("Failed: \(item.error ?? "Unknown")")
}
}You can also list(), retry(), or cancel() batches.
Given a starting URL, Spidra discovers pages automatically according to your instruction and extracts structured data from each one.
let params = CrawlParams(
baseUrl: "https://competitor.com/blog",
crawlInstruction: "Find all blog posts published in 2024",
transformInstruction: "Extract the title, author, publish date",
maxPages: 30
)
let job = try await spidra.crawl.run(params)
if let pages = job.result {
for page in pages {
print(page.url, page.data?.value ?? "No Data")
}
}Fetch signed download URLs for HTML and Markdown for all crawled pages:
let response = try await spidra.crawl.pages(job.jobId)Scrape logs are stored for every job that runs through the API.
let params = ScrapeLogsParams(status: "failed", limit: 20)
let response = try await spidra.logs.list(params)
for log in response.logs {
print("Log: \(log.uuid) - Status: \(log.status) - Credits: \(log.creditsUsed)")
}
// Get full extraction result for a specific log
let detail = try await spidra.logs.get("log-uuid")Returns credit and request usage broken down by day or week.
let rows = try await spidra.usage.get("30d") // "7d" | "30d" | "weekly"
for row in rows {
print("Date: \(row.date) - Requests: \(row.requests) - Credits: \(row.credits)")
}Every API error throws a SpidraError. Catch the specific case you care about.
do {
let job = try await spidra.scrape.run(params)
} catch SpidraError.authenticationError(let msg) {
// 401: API key is missing or invalid
print("Check your API key: \(msg)")
} catch SpidraError.insufficientCreditsError(let msg) {
// 403: Monthly credit limit reached
print("Out of credits: \(msg)")
} catch SpidraError.rateLimitError(let msg) {
// 429: Too many requests
print("Rate limited: \(msg)")
} catch SpidraError.serverError(let msg) {
// 500: Server error
print("Server error: \(msg)")
} catch {
// Decoding errors, network timeouts, etc.
print("Other error: \(error.localizedDescription)")
}- Swift 5.9+
- iOS 15.0+ / macOS 12.0+ / tvOS 15.0+ / watchOS 8.0+
- A Spidra API key (sign up free)
MIT