解析可用的街道地址、城市、州、从字符串中压缩

问题: 我有一个地址字段从访问数据库已转换为 SQLServer2005。这个领域的一切都在一个领域。我需要在一个规范化的表中将地址的各个部分解析为相应的字段。我需要做大约4000个记录,它需要是可重复的。

假设:

  1. 假设一个在美国的地址(暂时)

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

  3. 国家可以缩写

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

  5. 在某些情况下会出现打字错误

更新: 对于提出的问题,标准没有得到普遍遵守; 我需要存储单独的值,而不仅仅是地理代码和错误意味着输入错误(上面已经更正)

数据样本:

  • 19947年,乔治敦,路易斯-乔治敦,惠伊,A.P.Croll & Son 2299

  • 11522 Shawnee Road,格林伍德德1995年

  • 19901年,多佛西南部,国王高速144号

  • 综合康斯特。服务2 Penns Way Suite 405 New Castle,DE 19720

  • 1995年,德国,刘易斯,布里德尔里奇法院33号,休姆斯房地产公司

  • 尼科尔斯挖掘2742普拉斯基 Hwy 纽瓦克,DE 19711

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

  • VEI Dover 十字路口,LLC 蛇形路1500号,马里兰州巴尔的摩21号100室

  • 19901年,多佛北杜邦公路580号

  • 19903年,多佛778号邮政信箱

160007 次浏览

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

记录地址的方式是否有任何标准? 例如:

  1. 是否总是使用逗号或新行分隔 street1、 street2、 city、 state 和 zip?
  2. 地址类型(道路、街道、林荫大道等)总是拼写出来的吗? 总是缩写的吗? 每种都有一些吗?
  3. 定义一下“错误”。

我的一般答案是一系列正则表达式,尽管其复杂性取决于答案。如果根本没有一致性,那么你可能只能通过正则表达式(例如: 过滤掉邮政编码和状态)获得部分成功,剩下的就只能手工完成了(或者至少非常小心地完成剩下的部分,以确保你发现错误)。

我在这种解析方面做了很多工作。因为有些错误你不会得到100% 的准确性,但是有一些事情你可以做,以获得大部分的方式,然后做一个视觉 BS 测试。下面是一般的处理方法。它不是代码,因为它写起来很学术,没有什么奇怪的地方,只是有很多字符串处理。

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

  1. 倒退回去。从邮政编码开始,邮政编码将接近尾声,并采用两种已知格式之一: XXXXX 或 XXXXX-XXXX。如果这个没有出现,你可以假设你在城市,州部分,下面。
  2. 下一步,在 zip 之前,是状态,它可以是两个字母的格式,也可以是单词。你也知道这些是什么,只有50个。此外,您可以发音的单词,以帮助补偿拼写错误。
  3. 在那之前是城市 可能吧和州政府在同一条线上。您可以使用 邮政编码数据库基于 zip 检查城市和州,或者至少使用它作为 BS 检测器。
  4. 街道地址一般是一条或两条线。第二行通常是套件编号(如果有的话) ,但也可以是邮政信箱。
  5. 在第一行或第二行几乎不可能检测到名字,但是如果没有以数字作为前缀(或者如果前缀是“ attn:”或“ atton to:”,它可以给你一个提示,告诉你这是一个名字还是一个地址行。

我希望这能有所帮助。

有一些数据服务给出了一个邮政编码,这些数据服务会给出该邮政编码中的街道名称列表。

使用正则表达式提取 Zip 或城市国家-找到正确的一个或如果一个错误都得到。 从 资料来源中调出街道列表,更正城市和州名,然后找出街道地址。一旦您得到有效的地址行1、城市、州和邮政编码,您就可以对地址行2进行假设。

根据样本数据:

  1. 我会从弦的末端开始。解析邮政编码(任一格式)。读到第一个空格。如果没有找到邮政编码错误。

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

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

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

  5. 城市变得棘手,我会在这里使用逗号,冒着在城市中获得太多数据的风险。寻找逗号,或者行的开头。

  6. 如果字符串中还有字符,那么将所有这些字符全部放到一个地址字段中。

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

对样本数据的另一个请求。

正如前面提到的,我会从拉链开始向后工作。

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

这样就会留下一堆地址给你。大部分(所有?)地址将以一个数字开头,因此在剩余的字符串中找到第一个出现的数字,并获取从它到字符串(新)末尾的所有内容。这是你的地址。这个号码左边的任何东西都可能是收件人。

您现在应该将 City、 State 和 Zip 存储在一个表中,可能还有两个字符串、收件人和地址。对于地址,请检查是否存在“ Suite”或“ Apt”等等,然后把它分成两个值(地址行1和2)。

对于收件人,我会将字符串的最后一个单词作为最后一个名字,然后将其余的单词放入名字字段中。如果你不想这样做,你需要在开始时检查称呼(先生,女士,博士等) ,并根据空格的数量对名字是如何组成的做出一些假设。

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

我以前也这么干过。

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

手动操作每次大约需要10秒钟,这意味着你每小时可以做3600/10 = 360,所以4000应该需要大约11-12个小时。这会给你一个很高的准确率。

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

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

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

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

此外: 当你把东西邮寄出去的时候(这就是它的用途,对吗?)确保你把“地址更正请求”的信封(在正确的地方)和 更新的数据库。(我们为前台人员做了一个简单的 gui,他负责整理邮件)

最后,当您清除了数据之后,寻找重复的数据。

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

试试:

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

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

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

这不能解决你的问题,但是如果 你只需要 lat/long 数据 这些地址,谷歌地图 API 将解析非格式化地址 挺好的。

好建议,或者你可以对每个地址执行 CURL 请求到谷歌地图,它会返回正确格式的地址。由此,你可以随心所欲地正则化。

我已经在地址处理领域工作了大约5年,并且确实没有什么灵丹妙药。正确的解决方案将取决于数据的值。如果它不是非常有价值,那么按照其他答案的建议,将它扔到解析器中。如果它有一定的价值,那么肯定需要人工评估/修正解析器的所有结果。如果您正在寻找一个完全自动化的、可重复的解决方案,那么您可能需要与 Group1或 Trillium 这样的地址校正供应商进行交流。

在这里的建议之后,我在 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 Lewis-Georgetown Hwy,Georgetown,DE 19947”返回:

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

我认为外包这个问题是最好的选择: 把它发送到谷歌(或雅虎)的地理编码器。地理编码器不仅返回 lat/long (这里不感兴趣) ,而且还对地址进行了丰富的解析,填充了您没有发送的字段(包括 ZIP + 4和 County)。

例如,解析“1600圆形剧场大道,山景城,加利福尼亚州”产生

{
"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]
}
}
]
}

现在 那是可解析了!

是一个解析美国和欧洲地址的 WindowsCOM 对象。您可以直接尝试使用它 Http://www.loquisoft.com/index.php?page=8

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

罗森建议的解决方案 + 1,因为它对我很有效,但是对于完美主义者来说,这个网站是一个迷人的阅读,也是我在世界范围内看到的记录地址的最好尝试: http://www.columbia.edu/kermit/postal.html

我不知道这是否可行,但我还没有看到提到这一点,所以我想我应该继续并提出这样的建议:

如果你是严格在美国... 得到一个巨大的数据库,所有的邮政编码,州,城市和街道。在你们的地址里找到这些。您可以通过测试(比如说)所发现的城市是否存在于所发现的状态,或者通过检查所发现的街道是否存在于所发现的城市中来验证所发现的内容。如果不是,约翰很可能不是约翰的街道,而是收件人的名字... 基本上,获取尽可能多的信息,并检查您的地址对它。 一个极端的例子就是获取 A 美国所有地址的列表,然后找出哪一个与你的每个地址最相关..。

由于在单词中存在错误的机会,考虑使用 SOUNDEX 结合 LCS 算法来比较字符串,这将有很大的帮助!

你可能想看看这个 http://jgeocoder.sourceforge.net/parser.html 对我来说非常有效。

最初的海报可能已经过去很长时间了,但是我尝试将 Geocoder.us使用的 Perl 地理位置: : StreetAddress: US模块移植到 C # 上,然后把它放到 CodePlex 上,并且认为人们在将来偶然发现这个问题可能会发现它是有用的:

美国地址解析器

在这个项目的主页上,我试着谈论它的(非常真实的)局限性。由于它没有得到美国邮政局有效街道地址数据库的支持,解析可能是模棱两可的,它不能确认或否认给定地址的有效性。它可以尝试从字符串中提取数据。

当你需要在正确的字段中获取一组数据,或者想要提供一个数据输入的快捷方式(让用户把地址粘贴到文本框中,而不是在多个字段之间选项卡)时,它就是为这种情况准备的。它是 没有,用于验证地址的可交付性。

它不试图解析街道线以上的任何内容,但是可以利用正则表达式获得相当接近的内容——我可能只需要在门牌号码中断它。

使用 google 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);

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

它成功地从上述问题中给出的样本输入中提取出所有地址。(顺便说一下,这10个地址中只有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 进行验证,所以您可以确定它是否有效。每个经过验证的结果都是有保证的,但是我们知道其他的结果不会是完美的,因为正如本线程中的 非常清楚一样,地址是不可预测的,有时甚至对人类来说也是如此。

对于 Ruby 或 Rails 开发人员来说,有一个很好的 gem 可用,称为 街道地址。 我一直在我的一个项目中使用它,它做我需要的工作。

我唯一的问题是,每当一个地址是在这种格式 P. O. Box 1410 Durham, NC 27702它返回零,因此我必须替换“邮政信箱”与“ ,在此之后,它能够解析它。

这种类型的问题很难解决,因为数据中存在潜在的模糊性。

下面是一个基于 Perl 的解决方案,它定义了一个基于正则表达式的递归下降语法树,用于解析许多有效的街道地址组合: http://search.cpan.org/~kimryan/Lingua-EN-AddressParse-1.20/lib/Lingua/EN/AddressParse.pm。这包括地址中的子属性,如: 美国加州12345号,第一大道 N 套房2号

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

下面是您的一个示例地址的输出。请注意,名称部分需要首先从“ A.P.Croll & Son 2299 Lewis-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'

有一个 javascript 端口 perl Geo: : StreetAddress: : US 包: https://github.com/hassansin/parse-address