133

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

假设:

  1. 假设在美国的地址(现在)

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

  3. 状态可以缩写

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

  5. 在某些情况下有错别字

更新:针对提出的问题,标准并未得到普遍遵守;我需要存储各个值,而不仅仅是地理编码和错误意味着错字(在上面更正)

样本数据:

  • AP Croll & Son 2299 Lewes-Georgetown Hwy, Georgetown, DE 19947

  • 11522 肖尼路,格林伍德 DE 19950

  • 144 Kings Highway, SW Dover, DE 19901

  • 综合常数。服务 2 Penns Way Suite 405 New Castle, DE 19720

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

  • 尼科尔斯挖掘 2742 Pulaski Hwy Newark, DE 19711

  • 2284 Bryn Zion Road, 士麦那, DE 19904

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

  • 580 North Dupont Highway Dover, DE 19901

  • 邮政信箱 778 多佛,DE 19903

4

24 回答 24

119

我在这种解析上做了很多工作。因为存在错误,您不会获得 100% 的准确率,但是您可以做一些事情来获得大部分的准确率,然后进行视觉 BS 测试。这是解决它的一般方法。这不是代码,因为编写它非常学术,没有奇怪之处,只是大量的字符串处理。

(现在你已经发布了一些示例数据,我做了一些小的改动)

  1. 向后工作。从接近末尾的邮政编码开始,并采用两种已知格式之一:XXXXX 或 XXXXX-XXXX。如果这没有出现,您可以假设您在下面的城市、州部分。
  2. 接下来的事情,在 zip 之前,将是状态,它将是两个字母的格式,或者是单词。你也知道这些会是什么——只有 50 个。此外,您可以将单词发音以帮助弥补拼写错误。
  3. 在那之前是城市,它可能与州在同一条线上。您可以使用邮政编码数据库根据邮政编码检查城市和州,或者至少将其用作 BS 检测器。
  4. 街道地址通常是一两行。第二行通常是套房号码(如果有的话),但也可以是邮政信箱。
  5. 几乎不可能在第一行或第二行检测到名称,但如果它没有以数字为前缀(或者如果它以“attn:”或“attention to:”为前缀,它可能会给你一个提示无论是名称还是地址行。

我希望这会有所帮助。

于 2008-08-19T15:52:05.397 回答
92

我认为将问题外包是最好的选择:将其发送给谷歌(或雅虎)地理编码器。地理编码器不仅返回纬度/经度(这里不感兴趣),还返回地址的丰富解析,其中填写了您未发送的字段(包括 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]
      }
    }
  ]
}

现在可以解析了!

于 2008-08-19T20:30:52.027 回答
25

最初的海报可能已经很久了,但我尝试将 geocoder.us 使用的 Perl Geo::StreetAddress:US 模块移植C # ,将其转储到 CodePlex,并认为人们在未来偶然发现这个问题可能觉得有用:

美国地址解析器

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

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

它不会尝试解析街道线以上的任何内容,但人们可能会使用正则表达式来获得相当接近的东西——我可能只是在门牌号处将其中断。

于 2011-12-24T21:38:50.550 回答
17

我过去做过这件事。

要么手动完成(构建一个很好的 gui,帮助用户快速完成),要么让它自动化并检查最近的地址数据库(你必须购买)并手动处理错误。

手动处理每个大约需要 10 秒,这意味着您可以每小时进行 3600/10 = 360,因此 4000 应该需要大约 11-12 小时。这将为您提供很高的准确率。

对于自动化,您需要一个最近的美国地址数据库,并针对它调整您的规则。我建议不要看中正则表达式(很难长期维护,有很多例外)。对数据库进行 90% 的匹配,其余的手动完成。

请务必在http://pe.usps.gov/cpim/ftp/pubs/Pub28/pub28.pdf上获取一份邮政地址标准 (USPS) 的副本,并注意它有 130 多页长。实现这将是疯狂的正则表达式。

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

或者,使用数据服务。但是,我没有任何建议。

此外:当您确实在邮件中发送这些东西时(这就是它的用途,对吗?)确保您在信封上(在正确的位置)放置“地址更正请求”并更新数据库。(我们为前台人员制作了一个简单的 gui 来执行此操作;实际整理邮件的人)

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

于 2008-08-19T16:04:45.340 回答
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函数“AP Croll & Son 2299 Lewes-Georgetown Hwy, Georgetown, DE 19947”返回:

2299 Lewes-Georgetown Hwy
A. P. Croll & Son  
Georgetown
DE
19947
于 2008-08-19T19:52:34.210 回答
13

我已经在地址处理领域工作了大约 5 年,真的没有灵丹妙药。正确的解决方案将取决于数据的价值。如果它不是很有价值,请按照其他答案的建议将其通过解析器。如果它甚至有点有价值,您肯定需要人工评估/纠正解析器的所有结果。如果您正在寻找一个完全自动化、可重复的解决方案,您可能希望与 Group1 或 Trillium 等地址更正供应商交谈。

于 2008-08-19T17:08:25.697 回答
13

SmartyStreets 具有从任意输入字符串中提取地址的新功能。(注:我不在 SmartyStreets 工作。)

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

这是一些输出:在此处输入图像描述

这是同一请求的 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 进行验证,因此您可以确定它是否有效。每个经过验证的结果都是有保证的,但我们知道其他结果不会是完美的,因为正如在这个线程中非常清楚的那样,地址是不可预测的,有时甚至对人类来说也是如此。

于 2013-05-08T18:49:38.607 回答
8

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

好建议,或者您可以对 Google 地图的每个地址执行 CURL 请求,它将返回格式正确的地址。由此,您可以根据自己的喜好进行正则表达式。

于 2008-08-19T16:51:08.913 回答
7

对 James A. Rosen 建议的解决方案 +1,因为它对我来说效果很好,但是对于完成者来说,这个网站是一个引人入胜的读物,也是我在记录全球地址方面看到的最好的尝试:http: //www.columbia.edu/kermit /postal.html

于 2010-09-17T20:05:38.043 回答
6

记录地址的方式有什么标准吗?例如:

  1. 是否总是有逗号或换行符将 street1 与 street2 与 city 与 state 与 zip 分开?
  2. 地址类型(道路、街道、林荫大道等)是否总是拼写出来?总是缩写?每个都有一些?
  3. 定义“错误”。

我的一般答案是一系列正则表达式,尽管这取决于答案。如果根本没有一致性,那么您可能只能使用正则表达式获得部分成功(即:过滤邮政编码和状态),并且必须手动完成其余的工作(或者至少非常完成剩下的工作)仔细以确保您发现错误)。

于 2008-08-19T15:40:18.917 回答
6

另一个样本数据请求。

如前所述,我将从 zip 向后工作。

一旦你有一个 zip,我会查询一个 zip 数据库,存储结果,然后从字符串中删除它们和 zip。

这会让你的地址一团糟。大多数(全部?)地址将以数字开头,因此在剩余的字符串中找到第一次出现的数字,然后从它到字符串的(新)结尾处获取所有内容。那将是你的地址。该号码左侧的任何内容都可能是收件人。

您现在应该将 City、State 和 Zip 存储在一个表中,可能还有两个字符串,addressee 和 address。对于地址,检查是否存在“Suite”或“Apt”。等并将其拆分为两个值(地址行 1 和 2)。

对于收件人,我会选择并抓住该字符串的最后一个单词作为姓氏,然后将其余单词放入名字字段中。如果您不想这样做,则需要在开始时检查称呼(先生,女士,博士等),并根据空格数对名称的方式做出一些假设捏造。

我认为没有任何方法可以 100% 准确地解析。

于 2008-08-19T16:03:28.387 回答
6

试试www.address-parser.com。我们使用他们的网络服务,您可以在线测试

于 2010-02-08T14:53:10.687 回答
5

根据样本数据:

  1. 我将从字符串的末尾开始。解析邮政编码(任何一种格式)。读到第一个空格。如果没有找到邮政编码错误。

  2. 然后修剪结尾的空格和特殊字符(逗号)

  3. 然后转到状态,再次使用空格作为分隔符。也许使用查找列表来验证 2 个字母的州代码和完整的州名称。如果未找到有效状态,则出错。

  4. 再次从末尾修剪空格和逗号。

  5. 城市变得棘手,我实际上会在这里使用逗号,冒着在城市中获取太多数据的风险。查找逗号或行首。

  6. 如果字符串中仍有字符,请将所有字符都放入地址字段中。

这并不完美,但它应该是一个很好的起点。

于 2008-08-19T15:55:41.303 回答
4

如果是人工输入的数据,那么您将花费太多时间尝试围绕异常进行编码。

尝试:

  1. 正则表达式提取邮政编码

  2. 邮政编码查找(通过适当的政府数据库)以获取正确的地址

  3. 让实习生手动验证新数据是否与旧数据匹配

于 2008-08-19T16:47:16.677 回答
3

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

于 2008-08-19T15:40:15.593 回答
3

RecogniContact 是一个解析美国和欧洲地址的 Windows COM 对象。您可以在http://www.loquisoft.com/index.php?page=8上尝试一下

于 2009-05-12T14:07:31.417 回答
3

你可能想看看这个!!http://jgeocoder.sourceforge.net/parser.html 对我来说就像一个魅力。

于 2011-10-15T01:23:49.240 回答
3

由于数据中存在潜在的歧义,此类问题很难解决。

这是一个基于 Perl 的解决方案,它定义了一个基于正则表达式的递归下降语法树来解析许多有效的街道地址组合:http ://search.cpan.org/~kimryan/Lingua-EN-AddressParse-1.20/lib/Lingua /EN/AddressParse.pm。这包括地址内的子属性,例如:12 1st Avenue N Suite #2 Somewhere CA 12345 USA

它类似于上面提到的http://search.cpan.org/~timb/Geo-StreetAddress-US-1.03/US.pm,但也适用于非美国地址,例如英国、澳大利亚和加拿大。

这是您的示例地址之一的输出。请注意,首先需要从“AP Croll & Son 2299 Lewes-Georgetown Hwy, Georgetown, DE 19947”中删除名称部分,以将其缩减为“2299 Lewes-Georgetown Hwy, Georgetown, DE 19947”。这很容易通过删除字符串中找到的第一个数字之前的所有数据来实现。

Non matching part       ''
Error                   '0'
Error descriptions      ''
Case all                '2299 Lewes-Georgetown Hwy Georgetown DE 19947'
COMPONENTS              ''
country                 ''
po_box_type             ''
post_box                ''
post_code               '19947'
pre_cursor              ''
property_identifier     '2299'
property_name           ''
road_box                ''
street                  'Lewes-Georgetown'
street_direction        ''
street_type             'Hwy'
sub_property_identifier ''
subcountry              'DE'
suburb                  'Georgetown'
于 2014-10-01T10:59:07.187 回答
2

由于word有出错的可能,考虑使用SOUNDEX结合LCS算法来比较字符串,这将有很大帮助!

于 2011-01-22T12:49:26.720 回答
2

使用谷歌 API

$d=str_replace(" ", "+", $address_url);
$completeurl ="http://maps.googleapis.com/maps/api/geocode/xml?address=".$d."&sensor=true"; 
$phpobject = simplexml_load_file($completeurl);
print_r($phpobject);
于 2012-10-09T09:47:20.883 回答
2

对于 ruby​​ 或 rails 开发人员来说,有一个不错的 gem,叫做street_address。我一直在我的一个项目中使用它,它完成了我需要的工作。

我遇到的唯一问题是,只要地址采用这种格式P. O. Box 1410 Durham, NC 27702 ,它就会返回 nil,因此我必须用 '' 替换“PO Box”,然后它才能解析它。

于 2013-11-01T21:08:45.700 回答
1

有些数据服务提供了邮政编码,会为您提供该邮政编码中的街道名称列表。

使用正则表达式来提取 Zip 或 City State - 找到正确的或者如果有错误则两者都得到。从数据源中提取街道列表更正城市和州,然后是街道地址。一旦您获得有效的地址行 1、城市、州和邮政编码,您就可以对地址行 2..3 进行假设

于 2008-08-19T15:52:48.313 回答
1

我不知道这有多可行,但我还没有看到这个,所以我想我会继续建议这个:

如果您严格地在美国……获取所有邮政编码、州、城市和街道的庞大数据库。现在在您的地址中查找这些。您可以通过测试您找到的城市是否存在于您找到的州,或者检查您找到的街道是否存在于您找到的城市中来验证您找到的内容。如果不是,很可能 John 不是针对 John 的街道,而是收件人的姓名……基本上,尽可能多地获取信息并对照它检查您的地址。一个极端的例子是获取 A 的美国所有地址的列表,然后找到与您的每个地址最相关的匹配...

于 2010-10-01T02:50:01.567 回答
1

perl Geo::StreetAddress::US 包有 javascript 端口:https://github.com/hassansin/parse-address。它基于正则表达式并且运行良好。

于 2014-12-13T01:33:31.090 回答