从字符串中解析出可用的街道地址、城市、州和邮政编码

133

问题:我有一个来自Access数据库的地址字段,已转换为SQL Server 2005。这个字段将所有内容都放在一个字段中。我需要将地址的各个部分解析成其规范化表中相应的字段。我需要对大约4,000条记录执行此操作,并且需要可重复。

假设:

  1. 暂时假设地址位于美国

  2. 假设输入字符串有时会包含被寄信人(收件人)和/或第二个街道地址(即B套房)

  3. 州名可能会缩写

  4. 邮政编码可能是标准的5位数字或zip+4

  5. 有些情况下可能存在拼写错误

更新:回答的问题后,标准并不是普遍适用的;我需要存储单独的值,而不仅仅是地理编码和errors表示拼写错误(已更正)

示例数据:

  • A. P. Croll & Son 2299 Lewes-Georgetown Hwy, Georgetown, DE 19947

  • 11522 Shawnee Road, Greenwood DE 19950

  • 144 Kings Highway, S.W. Dover, DE 19901

  • Intergrated Const. Services 2 Penns Way Suite 405 New Castle, DE 19720

  • Humes Realty 33 Bridle Ridge Court, Lewes, DE 19958

  • Nichols Excavation 2742 Pulaski Hwy Newark, DE 19711

  • 2284 Bryn Zion Road, Smyrna, DE 19904

  • VEI Dover Crossroads, LLC 1500 Serpentine Road, Suite 100 Baltimore MD 21

  • 580 North Dupont Highway Dover, DE 19901

  • P.O. Box 778 Dover, DE 19903


几个问题:
  1. 有任何分隔符吗?
  2. 字符串中的字段顺序是什么?
  3. 在数据错误的情况下,您希望出现什么行为(例如将地址推入SQL表中的单个字段,其他字段留空)?
- Jay Mooney
好问题,非常有趣的答案。从邮编倒推似乎是一个常见的主题,但如果你从客户那里获取原始数据,邮编可能不准确。我猜大多数网站例如90210都有不成比例的地址数量。 - Kevin Williams
4
@Kevin:是的,因为你们美国人喜欢用“邮政编码”来锁住我们加拿大人,不接受我们的邮政编码,从而迫使我们输入一些无意义的内容来绕过系统...不幸的是,我唯一知道的邮编是90210 :-) 编辑:算了吧...你显然和我在不远处的卑诗省住得很近。那么你可能也会做同样的事情 :-P - mpen
2
请参考这个 Stack Overflow 问题以获取更详细的概述。 - Matt
24个回答

121

我在这种解析方面做了很多工作。由于存在错误,您无法获得100%的准确性,但是有一些方法可以让您做到大部分准确,并进行视觉 BS 测试。以下是一般的操作方式。这不是代码,因为编写它相当学术,没有奇怪的东西,只有大量的字符串处理。

(现在您已经发布了一些示例数据,我进行了一些微小的更改)

  1. 向后工作。从邮政编码开始,它将接近结尾,并且是两种已知格式之一:XXXXX或XXXXX-XXXX。如果未出现此内容,则可以假定您位于下面的城市、州部分。
  2. 在邮政编码之前的下一个内容将是所在的州,它将以两个字母的格式或以单词形式呈现。您也知道这些将是什么--只有50个。此外,您可以对这些单词进行声纳以帮助补偿拼写错误。
  3. 在那之前是城市,它可能与州在同一行上。您可以使用邮政编码数据库根据邮政编码检查城市和州,或者至少将其用作 BS 侦测器。
  4. 街道地址通常是一到两行。如果有一个套房号,第二行通常会是套房号,但也可能是邮政信箱。
  5. 几乎不可能在第一或第二行上检测到姓名,但如果它没有带有数字前缀(或如果带有“attn:”或“attention to:”,则可以提示它是名称还是地址行。

我希望这些有所帮助。


14
虽然美国有50个州,但美国邮政局称在其管辖范围内有59个两字母缩写,如果包括美国武装部队则为65个。https://www.usps.com/send/official-abbreviations.htm - Mike Sherrill 'Cat Recall'
18
“Only 50” 意味着数量非常少。可能是“只有65”,但对于解决手头的问题并不重要。 - Tim Sullivan
4
这个算法也在美国邮政服务出版物28中有详细介绍。 - Matt

92

我认为将问题外包是最好的选择:将其发送到Google(或Yahoo)地理编码器。地理编码器不仅返回纬度/经度(这里不感兴趣),还会对地址进行丰富的解析,填写您未发送的字段(包括ZIP+4和县)。

例如,对“1600 Amphitheatre Parkway, Mountain View, CA”进行解析可得:

{
  "name": "1600 Amphitheatre Parkway, Mountain View, CA, USA",
  "Status": {
    "code": 200,
    "request": "geocode"
  },
  "Placemark": [
    {
      "address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA",
      "AddressDetails": {
        "Country": {
          "CountryNameCode": "US",
          "AdministrativeArea": {
            "AdministrativeAreaName": "CA",
            "SubAdministrativeArea": {
              "SubAdministrativeAreaName": "Santa Clara",
              "Locality": {
                "LocalityName": "Mountain View",
                "Thoroughfare": {
                  "ThoroughfareName": "1600 Amphitheatre Pkwy"
                },
                "PostalCode": {
                  "PostalCodeNumber": "94043"
                }
              }
            }
          }
        },
        "Accuracy": 8
      },
      "Point": {
        "coordinates": [-122.083739, 37.423021, 0]
      }
    }
  ]
}

现在这就是可解析的!


4
由于这是批处理过程,我建议使用线程池进行地理编码,以便您可以一次提交多个地址(Google是否支持任何批处理接口?) - David
这并不能真正帮助到地址行二(问题中的第5点)。 - Christopher Mahan
71
服务条款常常是商业和/或非公共用途的限制因素。 - Jay
这是一个不错的解决方案,但是有些边缘情况谷歌/雅虎无法返回结果,例如新地址和他们数据库中缺失的地址。 - Peter DeWeese
如果谷歌不限制对其地图API的批量调用,那么这将是一个很好的解决方案。 - Hector

25
原帖的发布者可能已经离开了,但我尝试将geocoder.us使用的Perl Geo::StreetAddress:US模块移植到C#,并将其放在CodePlex上。我认为未来遇到这个问题的人可能会发现它有用:

美国地址解析器

在该项目的主页上,我试图谈论它(非常真实的)局限性。由于它没有得到USPS数据库中有效街道地址的支持,因此解析可能是有歧义的,并且无法确认或否认给定地址的有效性。它只能尝试从字符串中提取数据。

它适用于需要大部分正确字段数据的情况,或者想要提供数据输入的快捷方式(允许用户将地址粘贴到文本框中而不是在多个字段之间切换)。它适用于验证地址的可交付性。

它不会尝试解析街道线以上的任何内容,但是可以通过调整正则表达式来获得相当接近的东西-我可能只会在房屋号码处中断它。


17

我以前做过这个。

要么手动处理(构建一个漂亮的 GUI,帮助用户快速完成操作),要么自动化并检查最近的地址数据库(你得购买它),然后手动处理错误。

手动处理每个需要约 10 秒钟,意味着你可以每小时处理 3600/10=360 个,所以大约需要 11-12 小时才能处理完 4000 个,这将给你高准确性的结果。

对于自动化,你需要一份最近的美国地址数据库,并根据其调整规则。我建议不要在正则表达式上花太多心思(长期难以维护,有很多例外情况)。针对数据库进行 90% 的匹配,剩下的手动处理。

请在这里获取《邮政地址标准》(USPS)的副本,请注意它有 130 多页,实现正则表达式是疯狂的。

对于国际地址,所有赌注都取消了。基于美国的工作者将无法验证。

或者,使用数据服务。但是,我没有推荐的服务。

此外:当你将东西邮寄出去时(这是它的目的,对吧?),请确保在信封上放置“要求地址更正”(在正确的位置),并更新数据库。(我们为前台人员制作了一个简单的 GUI,让实际处理邮件的人可以做到这一点)

最后,当你清理数据后,查找重复项。


14
根据此处的建议,我已经设计了以下VB函数,可以创建可用但不一定完美的数据(如果提供公司名称和套房行,则将套房和城市组合在一起)。请随意评论/重构/批评我违反自己规则等等:
Public Function parseAddress(ByVal input As String) As Collection
    input = input.Replace(",", "")
    input = input.Replace("  ", " ")
    Dim splitString() As String = Split(input)
    Dim streetMarker() As String = New String() {"street", "st", "st.", "avenue", "ave", "ave.", "blvd", "blvd.", "highway", "hwy", "hwy.", "box", "road", "rd", "rd.", "lane", "ln", "ln.", "circle", "circ", "circ.", "court", "ct", "ct."}
    Dim address1 As String
    Dim address2 As String = ""
    Dim city As String
    Dim state As String
    Dim zip As String
    Dim streetMarkerIndex As Integer

    zip = splitString(splitString.Length - 1).ToString()
    state = splitString(splitString.Length - 2).ToString()
    streetMarkerIndex = getLastIndexOf(splitString, streetMarker) + 1
    Dim sb As New StringBuilder

    For counter As Integer = streetMarkerIndex To splitString.Length - 3
        sb.Append(splitString(counter) + " ")
    Next counter
    city = RTrim(sb.ToString())
    Dim addressIndex As Integer = 0

    For counter As Integer = 0 To streetMarkerIndex
        If IsNumeric(splitString(counter)) _
            Or splitString(counter).ToString.ToLower = "po" _
            Or splitString(counter).ToString().ToLower().Replace(".", "") = "po" Then
                addressIndex = counter
            Exit For
        End If
    Next counter

    sb = New StringBuilder
    For counter As Integer = addressIndex To streetMarkerIndex - 1
        sb.Append(splitString(counter) + " ")
    Next counter

    address1 = RTrim(sb.ToString())

    sb = New StringBuilder

    If addressIndex = 0 Then
        If splitString(splitString.Length - 2).ToString() <> splitString(streetMarkerIndex + 1) Then
            For counter As Integer = streetMarkerIndex To splitString.Length - 2
                sb.Append(splitString(counter) + " ")
            Next counter
        End If
    Else
        For counter As Integer = 0 To addressIndex - 1
            sb.Append(splitString(counter) + " ")
        Next counter
    End If
    address2 = RTrim(sb.ToString())

    Dim output As New Collection
    output.Add(address1, "Address1")
    output.Add(address2, "Address2")
    output.Add(city, "City")
    output.Add(state, "State")
    output.Add(zip, "Zip")
    Return output
End Function

Private Function getLastIndexOf(ByVal sArray As String(), ByVal checkArray As String()) As Integer
    Dim sourceIndex As Integer = 0
    Dim outputIndex As Integer = 0
    For Each item As String In checkArray
        For Each source As String In sArray
            If source.ToLower = item.ToLower Then
                outputIndex = sourceIndex
                If item.ToLower = "box" Then
                    outputIndex = outputIndex + 1
                End If
            End If
            sourceIndex = sourceIndex + 1
        Next
        sourceIndex = 0
    Next
    Return outputIndex
End Function

传递parseAddress函数"A. P. Croll & Son 2299 Lewes-Georgetown Hwy, Georgetown, DE 19947"会返回:

2299 Lewes-Georgetown Hwy
A. P. Croll & Son  
Georgetown
DE
19947

13

SmartyStreets推出了一项新功能,可以从任意输入字符串中提取地址。(注:我不在SmartyStreets工作。)

它成功地从上面问题中给出的示例输入中提取了所有地址。(顺便说一句,其中只有9个地址是有效的。)

以下是部分输出:enter image description here

这是同一请求的CSV格式化输出:

ID,Start,End,Segment,Verified,Candidate,Firm,FirstLine,SecondLine,LastLine,City,State,ZIPCode,County,DpvFootnotes,DeliveryPointBarcode,Active,Vacant,CMRA,MatchCode,Latitude,Longitude,Precision,RDI,RecordType,BuildingDefaultIndicator,CongressionalDistrict,Footnotes
1,32,79,"2299 Lewes-Georgetown Hwy, Georgetown, DE 19947",N,,,,,,,,,,,,,,,,,,,,,,
2,81,119,"11522 Shawnee Road, Greenwood DE 19950",Y,0,,11522 Shawnee Rd,,Greenwood DE 19950-5209,Greenwood,DE,19950,Sussex,AABB,199505209226,Y,N,N,Y,38.82865,-75.54907,Zip9,Residential,S,,AL,N#
3,121,160,"144 Kings Highway, S.W. Dover, DE 19901",Y,0,,144 Kings Hwy,,Dover DE 19901-7308,Dover,DE,19901,Kent,AABB,199017308444,Y,N,N,Y,39.16081,-75.52377,Zip9,Commercial,S,,AL,L#
4,190,232,"2 Penns Way Suite 405 New Castle, DE 19720",Y,0,,2 Penns Way Ste 405,,New Castle DE 19720-2407,New Castle,DE,19720,New Castle,AABB,197202407053,Y,N,N,Y,39.68332,-75.61043,Zip9,Commercial,H,,AL,N#
5,247,285,"33 Bridle Ridge Court, Lewes, DE 19958",Y,0,,33 Bridle Ridge Cir,,Lewes DE 19958-8961,Lewes,DE,19958,Sussex,AABB,199588961338,Y,N,N,Y,38.72749,-75.17055,Zip7,Residential,S,,AL,L#
6,306,339,"2742 Pulaski Hwy Newark, DE 19711",Y,0,,2742 Pulaski Hwy,,Newark DE 19702-3911,Newark,DE,19702,New Castle,AABB,197023911421,Y,N,N,Y,39.60328,-75.75869,Zip9,Commercial,S,,AL,A#
7,341,378,"2284 Bryn Zion Road, Smyrna, DE 19904",Y,0,,2284 Bryn Zion Rd,,Smyrna DE 19977-3895,Smyrna,DE,19977,Kent,AABB,199773895840,Y,N,N,Y,39.23937,-75.64065,Zip7,Residential,S,,AL,A#N#
8,406,450,"1500 Serpentine Road, Suite 100 Baltimore MD",Y,0,,1500 Serpentine Rd Ste 100,,Baltimore MD 21209-2034,Baltimore,MD,21209,Baltimore,AABB,212092034250,Y,N,N,Y,39.38194,-76.65856,Zip9,Commercial,H,,03,N#
9,455,495,"580 North Dupont Highway Dover, DE 19901",Y,0,,580 N DuPont Hwy,,Dover DE 19901-3961,Dover,DE,19901,Kent,AABB,199013961803,Y,N,N,Y,39.17576,-75.5241,Zip9,Commercial,S,,AL,N#
10,497,525,"P.O. Box 778 Dover, DE 19903",Y,0,,PO Box 778,,Dover DE 19903-0778,Dover,DE,19903,Kent,AABB,199030778781,Y,N,N,Y,39.20946,-75.57012,Zip5,Residential,P,,AL,

我是最初编写该服务的开发人员。我们实现的算法与任何具体答案都有所不同,但每个提取出来的地址都会通过地址查找API进行验证,因此您可以确定它是否有效。每个经过验证的结果都是有保证的,但我们知道其他结果不会完美,因为正如本帖子中已经被充分明确的那样,有时候地址即使对于人类来说也是不可预测的。


2
Smartystreets非常擅长他们所做的事情。非常高兴听到这是他们支持的API。 - ftrotter

13

我已经在地址处理领域工作了大约5年了,但真的没有万能解决方案。正确的解决方案将取决于数据的价值。如果它不是非常有价值,就像其他答案建议的那样,将其通过解析器进行处理。如果它甚至略微有价值,您肯定需要让人类评估/纠正解析器的所有结果。如果您正在寻找一个完全自动化、可重复的解决方案,您可能需要与像Group1或Trillium这样的地址校正供应商交谈。


8

这并不能解决你的问题,但如果你只需要这些地址的纬度/经度数据,Google Maps API可以很好地解析非格式化的地址。

好的建议是,你可以针对每个地址执行一个CURL请求到Google Maps,它将返回正确格式化的地址。从那里,你可以使用正则表达式来处理。


7

我同意James A. Rosen提出的解决方案,因为它对我很有效。但是,对于完美主义者来说,这个网站是一个迷人的阅读材料,也是我见过的最好的记录全球地址的尝试:http://www.columbia.edu/kermit/postal.html


6

1
这在查找大型HTML文档中的地址等内容方面非常有效。只是我希望它们有一个REST接口,而不是SOAP。感谢您分享此链接。 - jspooner
1
如果你与他们有关联,你需要披露这一点。 - Matt
1
他们提供一个价格估算会更好,而不是在给出价格之前要求我告诉他们他们的服务有多有价值。 - Toaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接