使用 Apache Spark 进行分布式 Web 爬虫——这可能吗?

Distributed Web crawling using Apache Spark - Is it Possible?

当我参加一次关于网络挖掘的采访时,有人问我一个有趣的问题。问题是,是否可以使用 Apache Spark 抓取网站?

我猜是可以的,因为它支持Spark的分布式处理能力。采访结束后,我搜索了这个,但找不到任何有趣的答案。 Spark 可以吗?

这样怎么样:

您的应用程序将获得一组网站 URL 作为您的爬虫的输入,如果您只是实现一个普通的应用程序,您可以按如下方式进行:

  1. 将所有要抓取的网页拆分成一个单独的站点列表,每个站点都足够小以适合单线程: for example: you have to crawl www.example.com/news from 20150301 to 20150401, split results can be: [www.example.com/news/20150301, www.example.com/news/20150302, ..., www.example.com/news/20150401]
  2. 将每个基数 url(www.example.com/news/20150401) 分配给单个线程,它在真正的数据获取发生的线程中
  3. 将每个线程的结果保存到文件系统中。

当应用程序成为一个 spark 时,同样的过程发生但封装在 Spark 概念中:我们可以自定义一个 CrawlRDD 做同样的工作人员:

  1. 拆分站点:def getPartitions: Array[Partition] 是执行拆分任务的好地方。
  2. 抓取每个拆分的线程数:def compute(part: Partition, context: TaskContext): Iterator[X] 将分布到应用程序的所有执行程序,运行 并行。
  3. 将rdd保存到HDFS中。

最终程序如下:

class CrawlPartition(rddId: Int, idx: Int, val baseURL: String) extends Partition {}

class CrawlRDD(baseURL: String, sc: SparkContext) extends RDD[X](sc, Nil) {

  override protected def getPartitions: Array[CrawlPartition] = {
    val partitions = new ArrayBuffer[CrawlPartition]
    //split baseURL to subsets and populate the partitions
    partitions.toArray
  }

  override def compute(part: Partition, context: TaskContext): Iterator[X] = {
    val p = part.asInstanceOf[CrawlPartition]
    val baseUrl = p.baseURL

    new Iterator[X] {
       var nextURL = _
       override def hasNext: Boolean = {
         //logic to find next url if has one, fill in nextURL and return true
         // else false
       }          

       override def next(): X = {
         //logic to crawl the web page nextURL and return the content in X
       }
    } 
  }
}

object Crawl {
  def main(args: Array[String]) {
    val sparkConf = new SparkConf().setAppName("Crawler")
    val sc = new SparkContext(sparkConf)
    val crdd = new CrawlRDD("baseURL", sc)
    crdd.saveAsTextFile("hdfs://path_here")
    sc.stop()
  }
}

Spark 基本上没有为此任务增加任何价值。

当然,您可以进行分布式爬取,但优秀的爬取工具已经支持开箱即用。 Spark 提供的 RRD 等数据结构在这里几乎没有用,只是为了启动爬网作业,您可以直接使用 YARN、Mesos 等,开销较小。

当然,您可以在 Spark 上执行此操作。就像你可以在 Spark 上做一个文字处理器一样,因为它是图灵完整的......但它并没有变得更容易。

有一个名为SpookyStuff的项目,它是一个

Scalable query engine for web scraping/data mashup/acceptance QA, powered by Apache Spark

希望对您有所帮助!

是的。

查看开源项目:Sparkler(spark-爬虫)https://github.com/USCDataScience/sparkler

检查 Sparkler Internals 以获得 flow/pipeline 图表。 (抱歉,这是一张 SVG 图片,我无法在此处 post)

posted 问题时该项目不可用,但截至 2016 年 12 月,它是非常活跃的项目之一!

是否可以使用 Apache Spark 抓取网站?

下面的文章可以帮助你理解为什么有人会问这样的问题,也可以帮助你回答。

  • Spark 框架的创建者在开创性论文 [1] 中写道,RDD 不太适合对共享状态进行异步细粒度更新的应用程序,例如存储系统 对于网络应用程序或增量网络爬虫
  • RDD 是 Spark 中的关键组件。但是,您可以创建传统的 map reduce 应用程序(很少或没有滥用 RDD)
  • 有一个广泛流行的分布式网络爬虫叫做 Nutch [2]。 Nutch 是用 Hadoop Map-Reduce 构建的(实际上,Hadoop Map Reduce 是从 Nutch 代码库中提取出来的)
  • 如果您可以在 Hadoop Map Reduce 中完成一些任务,您也可以使用 Apache Spark 完成。

[1] http://dl.acm.org/citation.cfm?id=2228301
[2] http://nutch.apache.org/


PS: 我是 Sparkler 的共同创建者和提交者,Apache Nutch 的 PMC。


当我设计 Sparkler 时,我创建了一个 RDD,它是基于 Solr/Lucene 的索引存储的代理。它使我们的爬虫数据库 RDD 能够 对共享状态进行异步细粒度更新 ,否则这在本地是不可能的。

我认为接受的答案在一个根本方面是不正确的; real-life large-scale Web 提取是一个拉取过程。

这是因为请求 HTTP 内容通常比构建响应要轻松得多。我构建了一个小程序,它能够在四个 CPU 内核和 3GB RAM 下每天抓取 1600 万个页面,而且甚至没有优化得很好。对于类似的服务器,这种负载(每秒约 200 个请求)并非微不足道,通常需要多层优化。

真实 web-sites 可能会破坏他们的缓存系统,例如,如果您抓取它们的速度太快(而不是在缓存中包含最受欢迎的页面,它可能会被 long-tail 抓取的内容淹没).所以从这个意义上说,一个好的 web-scraper 总是尊重 robots.txt 等等

分布式爬虫真正的好处不在于拆分一个域的工作负载,而是将多个域的工作负载拆分到一个分布式进程,这样一个进程就可以自信地跟踪有多少请求系统接通。

当然,在某些情况下,您想成为坏孩子并破坏规则;然而,根据我的经验,此类产品不会长期存在,因为 web-site 所有者喜欢保护他们的资产免受看起来像 DoS 攻击的东西。

Golang 非常适合构建网络抓取工具,因为它具有作为本机数据类型的渠道,并且它们很好地支持 pull-queues。由于 HTTP 协议和抓取通常很慢,您可以将提取管道作为流程的一部分,这将减少存储在数据仓库系统中的数据量。您可以花费不到 1 美元的资源来抓取 1TB,并且在使用 Golang 和 Google 云(可能也可以使用 AWS 和 Azure)时快速完成。

Spark 没有给您带来额外的价值。使用 wget 作为客户端很聪明,因为它会自动正确地尊重 robots.txt:如果您是专业工作,那么 wget 的并行域特定拉队列是必经之路。