设计分布式网络抓取器

sne*_*son 0 cloud distributed-computing amazon-web-services

问题

最近,我一直在考虑如何抓取某个大型跨国网站的内容,以获取该公司出售的产品的具体详细信息。该网站没有 API,但您可以通过将带有产品 ID 的 GET 请求发送到特定 URL 来下载每个产品的一些 XML。所以至少是这样。

问题在于,可能存在数亿个潜在产品 ID(例如,000000001 到 500000000 之间),但实际存在的产品只有几十万个。而且不可能知道哪些产品 ID 是有效的。

方便的是,向产品 URL 发送 HEAD 请求会根据产品 ID 是否有效(即产品实际存在)产生不同的响应。一旦我们知道该产品确实存在,我们就可以下载完整的 XML 并从中获取所需的数据位。

显然,如果在单个服务器上运行,发送数亿个 HEAD 请求将花费大量时间才能完成,因此我想借此机会学习如何开发某种分布式应用程序(对于我)。在这一点上,我应该提到这个特定的网站可以轻松地每秒处理大量传入请求,而不会出现 DOS 风险。我不想透露该网站的名称,但它每天很容易获得数百万次点击。该抓取工具对网站性能的影响可以忽略不计。不过,如果公司投诉,我会立即制止。

该设计

我不知道这是否是正确的方法,但我当前的想法是启动一个“协调服务器”,以及一些与该服务器通信并执行抓取的节点,所有这些都作为 EC2 实例运行。

每个节点将启动一定数量的进程,并且每个进程将由协调服务器指定一个作业,其中包含要抓取的不同范围的潜在产品ID(例如产品ID 00001 到10000)。这些作业将存储在协调服务器上的数据库表中。每个作业将包含以下信息:

  • 产品 ID 起始编号
  • 产品 ID 尾号
  • 作业状态(空闲、进行中、完成、过期)
  • 职位到期时间
  • 时间开始
  • 完成时间

当节点启动时,查询将被发送到协调服务器,询问一些配置数据以及要处理的作业。当节点完成作业时,将发送一个查询来更新刚刚完成的作业的状态,并发送另一个查询来请求执行新作业。每个作业都有一个到期时间,因此如果进程崩溃,或者节点因任何原因发生故障,另一个节点可以接管过期的作业并重试。

为了最大限度地提高系统性能,我需要计算出应立即启动多少个节点、每个节点有多少个进程、发送 HTTP 请求的速率以及哪种 EC2 实例类型将提供最大的性价比(我猜测高网络性能、高 CPU 性能和高磁盘 I/O 将是关键因素?)。

目前,计划是用 Python 编写抓取工具,在 Ubuntu EC2 实例上运行,可能在 Docker 容器中启动,并使用某种键值存储数据库来保存协调服务器(MongoDB?)上的作业。关系数据库也应该可以工作,因为作业表的 I/O 应该相当低。

我很想知道更有经验的工程师这是否是正确的方法,或者我是否完全忽略了完成此任务的更好方法?

非常感谢,谢谢!

ket*_*iya 5

您正在尝试设计一个分布式工作流系统,这实际上是一个已解决的问题。与其重新发明轮子,我建议您看看 AWS 的SWF,它可以轻松地为您完成所有状态管理,让您只需担心编写业务逻辑即可。

这就是使用 SWF 设计的系统的样子(在这里,我将使用 SWF 的标准术语 - 您可能需要阅读文档才能准确理解这些术语):

  • 每个 启动一个工作流程productID
  • 第一个活动将productID通过发出您提到的 HEAD 请求来检查这是否有效。
  • 如果不是,则终止工作流程。否则,第二个活动将通过发出必要的 GET 请求来获取相关的 XML 内容,并将其保留在 S3 中。
  • 第三个活动将获取 S3 文件、抓取 XML 数据并对其执行任何操作。

您可以轻松更改上述设计,让一个工作流程处理一批产品 ID。

我建议您记住其他一些要点:

  • 了解爬行和抓取之间的区别:爬行是指从网站中获取相关内容,抓取是指从中提取必要的数据。
  • 确保您所做的事情是严格合法的!
  • 不要太用力地访问该网站,否则他们可能会将您的 IP 范围列入黑名单。您有两个选择:
    • 在两次抓取之间添加延迟。这也可以在 SWF 中轻松实现。
    • 使用匿名代理。
  • 不要过分依赖某些未记录的 API 的 XML 结果,因为这可能随时发生变化。
  • 您将需要高网络性能的 EC2 实例。我认为高 CPU 或内存性能对您来说并不重要。