Swarm v0.3.0

Swarm v0.3.0

Denys Telezhkin维护。



Swarm v0.3.0


CI codecov.io Platform OS X | iOS | tvOS | watchOS | Linux Swift Package Manager compatible Packagist

Swarm是Swift编写的快速、简单且模块化的网络抓取解决方案。

特性

  • 同时运行的爬虫实例
  • 根据服务器请求自动重试和减速
  • 可定制的网络层(默认为URLSession
  • 深度优先/宽度优先选项
  • 跨平台

快速开始

class Scrapper: SwarmDelegate {
    lazy var swarm = Swarm(startURLs: [startingURL, anotherStartingURL], delegate: self)

    init() {
        swarm.start()
    }

    func scrappedURL(_ url: VisitedURL, nextScrappableURLs: @escaping ([ScrappableURL]) -> Void) {
        if let htmlString = url.htmlString() {
            // Scrap data from htmlString

            nextScrappableURLs([ScrappableURL(url: nextURL)])
        } else {
            nextScrappableURLs([])
        }
    }

    func scrappingCompleted() {
        print("Scrapping took: \(swarm.scrappingDuration ?? 0)")
    }
}

需求

  • Xcode 12及以上
  • Swift 5.3及以上
  • iOS 10 / macOS 10.12 / Linux / tvOS 10.0 / watchOS 3.0

如果您在Apple Watch上进行网络抓取,您的用例非常特别:)

安装

Swift Package Manager(需要Xcode 11)

  • 将软件包添加到项目设置 -> Swift 包

如果您基于Swarm构建一个包,请在Package.swift中添加以下代码

.package(url: "https://github.com/DenTelezhkin/Swarm.git", .upToNextMajor(from: "0.2.0"))

CocoaPods

pod 'Swarm'

添加更多URL

初始化Swarm并设置起始URL后,您可能需要按照以下方法添加更多

swarm.add(ScrappableURL(url: newURL, depth: desiredDepth))

您还可以在scrappedURL(_:nextScrappableURLs:)委托方法回调中添加更多要抓取的URL

nextScrappableURLs([ScrappableURL(url: nextURL)])

请注意,无需检查这些URL的唯一性,因为内部URL存储在Set中,并且在抓取过程中,已访问的URL会被保存在日志中。

请注意,在scrappedURL(_:nextScrappableURLs:)委托方法中调用nextScrappableURLs闭包是必需的,因为Swarm正在等待所有此类闭包被调用,以完成网络抓取。

配置

SwarmConfiguration对象在Swarm初始化期间传递。对于所有参数,它都有合理的默认值,但是如果您需要,可以修改其中任何一个

  • 成功状态码
  • 延迟重试状态码
  • 延迟重试延迟
  • 在放弃之前,延迟重试的次数
  • 最大自动节流延迟(Swarm会尝试遵守“Retry-After”响应头,但延迟不会大于该变量中指定的延迟)
  • 放弃前,最大自动节流请求重试次数
  • 下载延迟(每个蜘蛛的冷却时间)
  • 最大并发连接数

示例:下载延迟=1,.maxConcurrentConnections=8等于每秒钟约8个请求,不包括解析时间

深度或宽度?

网页可能包含多个要跟踪的链接,根据您的目标,您可能希望更深入或更广泛地进行(例如,我是否要先获取页面上所有项目,然后再加载数据详情,反之亦然)。

默认情况下,Swarm作为LIFO队列运行,完全忽略深度。但是,您可以设置SwarmConfiguration对象中的此值来要求首先考虑深度。

configuration.scrappingBehavior = .depthFirst

在这种情况下,当选择要抓取的下一个URL时,Swarm将选择具有最大depth值的ScrappableURL实例。另请考虑,如果使用.breadthFirst行为,最低depth URL将优先考虑。

网络传输

默认情况下,Swarm使用Foundation.URLSession作为所有网络请求的网络传输。您可以通过采用代理方法来自定义请求的发送方式。

func spider(for url: ScrappableURL) -> Spider {
    let spider = URLSessionSpider()

    // Handle cookies instead of ignoring them
    spider.httpShouldHandleCookies = true
    spider.userAgent = .static("com.my_app.custom_user_agent")
    spider.requestModifier = {
        var request = $0
        // Modify URLRequest instance for each request
        request.timeoutInterval = 20
        return request
    }
    // Modify HTTP headers
    spider.httpHeaders["Authorization"] = "Basic dGVzdDp0ZXN0"

    return spider
}

如果您需要,也可以通过实现简单的Spider协议来实现自己的网络传输。

public protocol Spider {
    func request(url: ScrappableURL, completion: @escaping (Data?, URLResponse?, Error?) -> Void)
}

该实现的示例可以在Swarm的单元测试中找到,其中使用MockSpider来模拟测试套件中的所有网络请求。

Spider生命周期

在这种情况下,图像胜于千言万语。

在Vapor应用中使用SwiftNIO管理冷却时间

默认情况下,Swarm使用DispatchQueue.main.asyncAfter(deadline:execute:)方法在网络请求之间执行延迟。这对于Mac应用来说是可以的,但如果您在Vapor等服务器环境中运行,则不应使用主线程来安排任何事情。实际上,将任务调度到主线程可能甚至无法工作。

为了在Vapor应用中正确地执行冷却时间,请按照以下方式设置Swarm实例上的cooldown

swarm.cooldown = { interval, closure in
    eventLoop.scheduleTask(in: TimeAmount.seconds(Int64(interval)), closure)
}

成为一个友好的邻里网络爬虫

工欲善其事,必先利其器。您可能会想要减少下载冷却时间,增加并行性,并以您令人敬畏的千兆以太网的传输速度下载所有内容。请不要这样做,请!)

首先,走得更慢实际上可能会导致抓包成功,并且您不会在尝试获取数据的服务器上被禁止。此外,走得更慢意味着您将对服务器施加较小的压力,并允许服务器在处理接收到的所有数据请求时,不会使所有人的(包括您的)请求减慢。

阅读scrapy 常用实践文档,该文档的许多内容也适用于这个框架(原则上)。

为了进一步讲解如何避免被封,以下是一些关于Swarm使用的技巧。

使用用户代理池

spider.userAgent = .randomized(["com.my_app.custom_user_agent", "com.my_app.very_custom_user_agent"])

禁用cookies

spider.httpShouldHandleCookies = false

如果您遇到重试响应,通过增加蜘蛛的冷却时间并减少并发性来减慢速度。

configuration.downloadDelay = 2
configuration.maxConcurrentConnections = 5

为了监控重试响应,实现以下委托方法,您可以观察为什么Swarm决定延迟后续请求。

func delayingRequest(to url: ScrappableURL, for timeInterval: TimeInterval, becauseOf response: URLResponse?) {
  // Try inspecting (response as? HTTPURLResponse)?.statusCode
  // Also: (response as? HTTPURLResponse)?.allHeaderFields
}

常见问题解答(FAQ)

我正在做一些严肃的网页抓取,这个框架适合我吗?

这取决于您的情况。本项目注重简洁性,并且首先适用于Apple平台,然后才是Linux。它适用于我的用例,但如果您的用例更复杂,您应该查看scrapy,它具有更多的特性和企业级支持。

此外,本项目是用Swift编写的,而scrapy是用Python编写的,这取决于您的起点,两者可以是一个优势也可以是一个劣势。

为什么没有内置的数据提取机制?

这又回到了简洁性的问题。您可能会喜欢使用SwiftSoup进行HTML解析,或者您可能喜欢使用Kanna使用XPath提取数据。也许您甚至需要使用无头浏览器来先渲染您的网页。当前的方法是安装Swarm,并使用您需要的任何解析库(如果需要的话)。

有命令行界面(CLI)吗?

没有,也暂时没有计划。现在用SwiftUI在Mac上构建应用程序只需要几行代码,因此在这个时候,CLI的需求已经不存在了。

Carthage包管理器是否有支持?

目前还没有。仓库中没有Xcode项目,Carthage 并不直接支持使用由SwiftPM生成的项目。

然而,你可以遵循这个指南来解决这个问题。

关于为什么我不在这个仓库中包含Xcode项目的原因——这是个人的,我只是非常厌倦使用Carthage来管理依赖项,我发现这非常慢,并且非常可能出现问题。CocoaPods虽然被一些人评判,但非常流行,并且在我的生产环境中已经使用多年,比Carthage少了很多问题。此外,Swift Package Manager是目前我选择的依赖管理器,因为它具有Xcode集成,并且在编译速度和简单性方面实际上超过了CocoaPods。

未来规划有哪些?

根据社区反馈和我在框架上的使用情况,以下功能可能会被实现

  • Linux支持
  • 自动解析robots.txt
  • 自动解析网站地图
  • 自动检测链接并限制域名
  • Vapor服务器上提供示例使用
  • 为访问过的页面历史记录提供外部存储
  • 其他我目前尚未了解的内容

许可证

Swarm在MIT许可证下发布。详情见LICENSE。