【问题标题】:Extract domain from URLs using scala使用 scala 从 URL 中提取域
【发布时间】:2022-01-16 14:05:20
【问题描述】:

我正在尝试从 URL 中提取域。

输入:

    import org.apache.spark.sql._
    import org.apache.spark.sql.functions._
    val b = Seq(
        ("subdomain.example.com/test.php"),
        ("example.com"),
        ("example.buzz"),
        ("test.example.buzz"),
        ("subdomain.example.co.uk"),
    ).toDF("raw_url")
    var c = b.withColumn("host", callUDF("parse_url", $"raw_url", lit("HOST"))).show()

预期结果:

    +--------------------------------+---------------+
    | raw_url                        | host          |
    +--------------------------------+---------------+
    | subdomain.example.com/test.php | example.com   |
    | example.com                    | example.com   | 
    | example.buzz                   | example.buzz  |
    | test.example.buzz              | example.buzz  |
    | subdomain.example.co.uk        | example.co.uk |
    +------------------------------- +---------------+

非常感谢任何建议。

编辑:根据@AlexOtt 的提示,我离我更近了几步。

    import com.google.common.net.InternetDomainName
    import org.apache.spark.sql._
    import org.apache.spark.sql.functions._
    val b = Seq(
        ("subdomain.example.com/test.php"),
        ("example.com"),
        ("example.buzz"),
        ("test.example.buzz"),
        ("subdomain.example.co.uk"),
    ).toDF("raw_url")
    var c = b.withColumn("host", callUDF("InternetDomainName.from", $"raw_url", topPrivateDomain)).show()

但是,我显然没有用 withColumn 正确实现它。这是错误:

错误:未找到:值 topPrivateDomain var c = b.withColumn("host", callUDF("InternetDomainName.from", $"raw_url", topPrivateDomain)).show()

编辑 2:

从@sarveshseri 那里得到了一些好的指示,在清理了一些语法错误之后,以下代码能够从大多数 URL 中删除子域。

    import org.apache.spark.sql.functions.udf
    import org.apache.spark.sql._
    import org.apache.spark.sql.functions._
    import com.google.common.net.InternetDomainName
    import java.net.URL

    val b = Seq(
       ("subdomain.example.com/test.php"),
       ("example.com"),
       //("example.buzz"),
       //("test.example.buzz"),
       ("subdomain.example.co.uk"),
       ).toDF("raw_url")

    val hostExtractUdf = org.apache.spark.sql.functions.udf { 
        (urlString: String) =>
        val url = new URL("https://" + urlString)
        val host = url.getHost
        InternetDomainName.from(host).topPrivateDomain().name()
    }

    var c = b.select("raw_url").withColumn("HOST", 
       hostExtractUdf(col("raw_url")))
        .show(false)

但是,它仍然无法按预期工作。 .buzz.site.today 等较新的后缀会导致以下错误:

Caused by: java.lang.IllegalStateException: Not under a public suffix: example.buzz

【问题讨论】:

  • 你需要包装一些支持通过公共后缀列表查找的库:publicsuffix.org - 规则相当复杂
  • 感谢@AlexOtt 的提示,这在一定程度上有所帮助。我找到了这个stackoverflow.com/questions/45046265/…。但是,我仍然坚持如何将 InternetDomainName.from().topPrivateDomain 应用于 withColumn

标签: scala apache-spark guava url-parsing


【解决方案1】:

首先,您需要将guava 添加到build.sbt 中的依赖项。

libraryDependencies += "com.google.guava" % "guava" % "31.0.1-jre"

现在可以如下提取主机了,

import com.google.common.net.InternetDomainName
import org.apache.sedona.core.serde.SedonaKryoRegistrator
import org.apache.spark.SparkConf
import org.apache.spark.serializer.KryoSerializer
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._

import java.net.URL

import spark.implicits._

val hostExtractUdf = org.apache.spark.sql.functions.udf { (urlString: String) =>
  val url = new URL("https://" + urlString)
  val host = url.getHost
  InternetDomainName.from(host).topPrivateDomain().toString
}

val b = sc.parallelize(Seq(
  ("a.b.com/c.php"),
  ("a.b.site/c.php"),
  ("a.b.buzz/c.php"),
  ("a.b.today/c.php"),
  ("b.com"),
  ("b.site"),
  ("b.buzz"),
  ("b.today"),
  ("a.b.buzz"),
  ("a.b.co.uk"),
  ("a.b.site")
)).toDF("raw_url")

val c = b.withColumn("HOST", hostExtractUdf(col("raw_url")))

c.show()

c.show 输出

+---------------+-------+
|        raw_url|   HOST|
+---------------+-------+
|  a.b.com/c.php|  b.com|
| a.b.site/c.php| b.site|
| a.b.buzz/c.php| b.buzz|
|a.b.today/c.php|b.today|
|          b.com|  b.com|
|         b.site| b.site|
|         b.buzz| b.buzz|
|        b.today|b.today|
|       a.b.buzz| b.buzz|
|      a.b.co.uk|b.co.uk|
|       a.b.site| b.site|
+---------------+-------+

【讨论】:

  • 有什么办法可以避免使用 sbt?
  • 您可以使用任何其他构建工具,如 maven、gradle、pants、bazel 等来构建您的项目。
  • 很高兴知道。到目前为止,我一直在使用 spark-shell 中的:load
  • 我清理了一些语法错误并让您的代码(大部分)正常工作。但是它无法处理像.buzz.today.site这样的新后缀。
  • 是的,我只是在代码中添加了缺失的部分,它不是完整的代码。解决了这个问题。 “无法处理.buzz'today.site”是什么意思?您可以在代码中看到它可以毫无问题地处理这些问题。
【解决方案2】:

也许您可以将正则表达式与 Spark regexp_extractregexp_replace 内置函数一起使用。这是一个例子:

val c = b.withColumn(
  "HOST",
  regexp_extract(col("raw_url"), raw"^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www.)?([^:\/\n?]+)", 1)
).withColumn(
  "sub_domain",
  regexp_extract(col("HOST"), raw"(.*?)\.(?=[^\/]*\..{2,5})/?.*", 1)
).withColumn(
  "HOST",
  expr("trim(LEADING '.' FROM regexp_replace(HOST, sub_domain, ''))")
).drop("sub_domain")

c.show(false)
//+-----------------------------------+-------------+
//|raw_url                            |HOST         |
//+-----------------------------------+-------------+
//|subdomain.example.com/test.php     |example.com  |
//|example.com                        |example.com  |
//|example.buzz                       |example.buzz |
//|test.example.buzz                  |example.buzz |
//|https://www.subdomain.example.co.uk|example.co.uk|
//|subdomain.domain.buzz              |domain.buzz  |
//|dev.example.today                  |example.today|
//+-----------------------------------+-------------+

第一个从 URL 中提取完整的主机名(包括子域)。然后,使用来自this answer 的正则表达式,我们搜索子域并将其替换为空白。

没有针对所有可能的情况对其进行测试,但对于您问题中的给定示例,它可以正常工作。

【讨论】:

  • 虽然,由于各种原因,我个人不喜欢这种自写的正则表达式方法,并建议在生产代码中避免此类事情,但您仍然支持我的努力。首先,很难正确创建。其次,很难维持 ragex。第三,Guava 已经在为你做这件事了。 Guava 实现不仅遵循最新的域名规范,还跟踪所有 ICANN 公共域。
猜你喜欢
  • 1970-01-01
  • 2019-09-24
  • 1970-01-01
  • 2018-05-31
  • 2018-09-08
  • 2011-12-30
  • 2015-03-08
  • 2021-04-12
  • 2010-10-24
相关资源
最近更新 更多