자유 거리 / 우편 주소를 텍스트 및 구성 요소로 구문 분석하는 방법


136

우리는 미국에서 주로 사업을하고 있으며 모든 주소 필드를 단일 텍스트 영역으로 결합하여 사용자 경험을 향상시키기 위해 노력하고 있습니다. 그러나 몇 가지 문제가 있습니다.

  • 사용자가 입력 한 주소가 정확하지 않거나 표준 형식이 아닐 수 있습니다
  • 신용 카드 결제를 처리하려면 주소를 부분 (거리, 도시, 주 등)으로 분리해야합니다.
  • 사용자는 자신의 주소 나 이름과 같은 주소 이상을 입력 할 수 있습니다
  • Google은이 작업을 수행 할 수 있지만 특히 예산이 부족한 경우 서비스 약관 및 쿼리 제한이 금지됩니다.

분명히 이것은 일반적인 질문입니다.

주변 텍스트와 주소를 분리하여 조각으로 나누는 방법이 있습니까? 주소를 구문 분석하는 정규식이 있습니까?


아래의 답변은 전역 문제를 무시하지 않기 때문에 더 유용합니다. 주소는 일반적인 패턴에 맞지 않습니다.
Marc Maxmeister

답변:


290

주소 확인 회사에서 일할 때이 질문을 많이 보았습니다. 동일한 질문으로 주변을 검색하는 프로그래머가 더 쉽게 액세스 할 수 있도록 여기에 답변을 게시하고 있습니다. 제가 수십억 개의 주소를 처리 한 회사에서 그 과정에서 많은 것을 배웠습니다.

먼저 주소에 대한 몇 가지 사항을 이해해야합니다.

주소가 일정 하지 않습니다

이것은 정규 표현식이 없음을 의미합니다. 매우 구체적인 형식의 주소와 일치하는 간단한 정규식에서 다음과 같이 모든 것을 보았습니다.

/ \ s + (\ d {2,5} \ s +) (?! [a | p] m \ b) (([a-zA-Z | \ s +] {1,5}) {1,2}) ? ([\ s |, |.] +)? (([a-zA-Z | \ s +] {1,30}) {1,4}) (court | ct | street | st | drive | dr | 레인 | ln | road | rd | blvd) ([\ s |, |. |;] +)? (([a-zA-Z | \ s +] {1,30}) {1,2}) ([ \ s |, |.] +)? \ b (AK | AL | AR | AZ | CA | CO | CT | DC | DE | FL | GA | GU | HI | IA | ID | IL | IN | KS | KY | LA | MA | MD | ME | MI | MN | MO | MS | MT | NC | ND | NE | NH | NJ | NM | NV | NY | OH | OK | OR | PA | RI | SC | SD | TN | TX | UT | VA | VI | VT | WA | WI | WV | WY) ([\ s |, |.] +)? (\ s + \ d {5})? ([\ s |, |.] +) / i

...에 900+ 라인 클래스 파일 더욱 일치하도록 즉석에서 초대형 정규 표현식을 생성하는 곳. 나는 이것을 권장하지 않습니다 (예를 들어, 여기에 위의 정규 표현식의 바이올린이 있습니다. 많은 실수가 있습니다 ). 이것을 작동시키는 쉬운 마술 공식은 없습니다. 이론과 이론 따르면 정규식과 주소를 일치시킬 수 없습니다.

USPS Publication 28 은 모든 키워드 및 변수와 함께 가능한 많은 형식의 주소를 문서화합니다. 무엇보다도 주소는 종종 모호합니다. 단어는 둘 이상의 것을 의미 할 수 있으며 ( "St"는 "Saint"또는 "Street"일 수 있음), 그들이 발명 한 것이 확실합니다. ( "Stravenue"가 거리 접미사임을 누가 알았습니까?)

실제로 주소를 이해하는 코드가 필요하며 해당 코드가 존재하면 영업 비밀입니다. 그러나 당신이 그것에 정말로 있다면 당신은 아마 당신 자신을 굴릴 수 있습니다.

주소는 예상치 못한 모양과 크기로 나타납니다.

다음은 몇 가지 고안된 (그러나 완전한) 주소입니다.

1)  102 main street
    Anytown, state

2)  400n 600e #2, 52173

3)  p.o. #104 60203

이것들조차도 가능할 것입니다 :

4)  829 LKSDFJlkjsdflkjsdljf Bkpw 12345

5)  205 1105 14 90210

분명히, 이들은 표준화되지 않았습니다. 문장 부호와 줄 바꿈이 보장되지 않습니다. 진행중인 작업은 다음과 같습니다.

  1. 1 번 주소에는 거리 주소와 도시 및 주가 포함되어 있으므로 완전합니다. 이 정보를 사용하면 주소를 충분히 식별 할 수 있으며 "전달할 수있는"것으로 간주 될 수 있습니다 (일부 표준화 사용).

  2. 주소 2 는 주소 (2 차 / 장치 번호 포함)와 주소를 식별하기에 충분한 5 자리 우편 번호도 포함하므로 완료됩니다.

  3. 번호 3 은 우편 번호 가 포함 된 완전한 우체국 형식입니다.

  4. 우편 번호가 고유 하기 때문에 번호 4 도 완료되었습니다 . 즉, 개인 엔티티 또는 회사가 해당 주소 공간을 구매했음을 의미합니다. 고유 한 우편 번호는 대량 또는 집중 배송 공간을위한 것입니다. 우편 번호 12345로 주소가 지정된 것은 뉴욕 주 스 체넥 터디의 General Electric에 있습니다. 이 예는 특히 누구에게도 도달하지 않지만 USPS는 여전히이를 전달할 수 있습니다.

  5. 번호 5 도 완성되었습니다. 믿거 나 말거나. 이 숫자 만 사용하면 가능한 모든 주소의 데이터베이스에 대해 구문 분석 할 때 전체 주소를 발견 할 수 있습니다. 누락 된 방향, 보조 지정자 및 ZIP + 4 코드를 작성하는 것은 각 숫자를 구성 요소로 볼 때 사소한 것입니다. 완전히 확장되고 표준화 된 모습은 다음과 같습니다.

205 N 1105 W 아파트 14

비벌리 힐스 CA 90210-5221

주소 데이터는 당신의 것이 아닙니다

허가 된 공급 업체에 공식 주소 데이터를 제공하는 대부분의 국가에서 주소 데이터 자체는 관리 기관에 속합니다. 미국에서는 USPS가 주소를 소유합니다. 각 국가마다 소유권을 조금 다르게 시행하거나 정의하지만 Canada Post, Royal Mail 및 기타 국가도 마찬가지입니다. 주소 데이터베이스를 리버스 엔지니어링하지 못하기 때문에이를 아는 것이 중요합니다. 데이터를 수집, 저장 및 사용하는 방법에주의해야합니다.

Google지도는 빠른 주소 수정을위한 일반적인 방법이지만 TOS 는 다소 금지되어 있습니다. 예를 들어 Google지도를 표시하지 않고 데이터 또는 API를 사용할 수 없으며 유료가 아닌 경우 비 상업용으로 만 사용할 수 있으며 데이터를 저장할 수 없습니다 (임시 캐싱 제외). 말이된다. Google의 데이터는 세계 최고입니다. 그러나 Google지도는 주소를 확인 하지 않습니다 . 주소가 존재하지 않는 경우, 아직 주소가 어디에을 보여줍니다 것이다 이 경우 일 했다 (자신의 거리에 그것을 시도, 당신은 존재하지 않는 알고 집 번호를 사용)이 존재합니다. 이것은 때때로 유용하지만 그 점을 알고 있어야합니다.

Nominatim의 사용 정책 은 특히 ​​대량 및 상업용으로 유사하게 제한되며 데이터는 대부분 무료 소스에서 가져 오므로 잘 관리되지 않습니다 (오픈 프로젝트의 특성). 그러나 여전히 적합 할 수 있습니다. 너의 요구. 훌륭한 커뮤니티에서 지원합니다.

USPS 자체에는 API가 있지만 많이 다운 되고 보장이나 지원이 제공되지 않습니다. 사용하기 어려울 수도 있습니다. 어떤 사람들은 문제없이 그것을 거의 사용하지 않습니다. 그러나 USPS는 주소를 확인할 주소 확인을 위해서만 API를 사용해야한다는 사실을 놓치기 쉽습니다.

사람들은 주소가 어려울 것으로 예상합니다

불행하게도, 우리는 주소가 복잡 할 것으로 기대하기 위해 우리 사회를 조정했습니다. 인터넷에는 수십 개의 훌륭한 UX 기사가 있지만, 개별 필드가있는 주소 양식이 있으면 사용자가 기대하는 것입니다. 단지 주소가 적합하지 않은 경우 양식이 예상하는 형식이거나 양식에 필요하지 않은 필드가 필요할 수 있습니다. 또는 사용자는 주소의 특정 부분을 어디에 둘지 모릅니다.

요즘에는 잘못된 UX 체크 아웃 양식에 대해 계속 이야기 할 수 있지만 대신 주소를 단일 필드로 결합하는 것은 환영받는 변화가 될 것입니다. 사람들은 자신이 맞는 방식으로 주소를 입력 할 수 있습니다 , 당신의 긴 형식을 알아 내려고 노력하기보다는. 그러나이 변경은 예상치 못한 것이며 사용자는 처음에는 약간 혼란 스러울 수 있습니다. 그냥 알아 둬

이 고통의 일부는 연설 전에 국가 필드를 앞쪽에 두어 완화 할 수 있습니다. 국가 필드를 먼저 채울 때 양식을 표시하는 방법을 알고 있습니다. 단일 필드 미국 주소를 처리하는 좋은 방법이있을 수 있으므로 미국을 선택하면 양식을 단일 필드로 줄이거 나 구성 요소 필드를 표시 할 수 있습니다. 생각해야 할 것들!

이제 왜 어려운지 알았습니다. 그것에 대해 무엇을 할 수 있습니까?

USPS는 CASS ™ 인증이라는 프로세스를 통해 공급 업체에 라이센스를 부여하여 고객에게 확인 된 주소를 제공합니다. 이러한 공급 업체는 USPS 데이터베이스에 액세스하며 매월 업데이트됩니다. 해당 소프트웨어는 인증을 받기 위해 엄격한 표준을 준수해야하며 위에서 설명한 제한 조건에 동의하지 않아도됩니다.

목록을 처리하거나 API를 보유 할 수있는 CASS 인증 회사는 Melissa Data, Experian QAS 및 SmartyStreets가 많습니다.

( "광고"에 대한 답답함 때문에이 시점에서 답을 잘랐습니다. 귀하에게 맞는 솔루션을 찾는 것은 귀하의 몫입니다.)

진실 : 여러분, 저는이 회사들에서 일하지 않습니다. 광고가 아닙니다.


1
남미 (우루과이) 주소는 어떻습니까? : D
Bart Calixto

11
@Brian-아마도 사용자가 회사 제품 사용 여부에 관계없이 질문과 답변을 읽는 사람들에게 유용한 정보를 많이 제공했기 때문일 수 있습니다.
Zarepheth

7
@Brian이 사이트는 컨텐츠 스크레이퍼입니다. 그들은 SERP 순위를 얻기 위해 콘텐츠를 모으고 있습니다. 나는 전에 본 적이 없다. 이 콘텐츠를 다른 곳 앞이나 뒤에 게시 한 적이 없습니다.
Matt

2
@khuderm 나는 당신의 의견을 읽을 때 모든 반대 의견이 사라 졌다는 것을 알았습니다. 언제 어떻게 발생했는지 잘 모르겠습니다. 그러나 어쨌든 내 대답의 편집 기록을 보면 도움이 될 수있는 미국 주소 추출기에 대한 직접적인 참조를 찾을 수 있습니다. 나는 마지막 직장에서 일할 때 그것을 만들었지 만 독점 코드이므로 공유 할 수는 없지만 존재합니다. 희망적으로 도움이됩니다.
Matt

2
죄송합니다. @Matt 죄송합니다. 글쎄, 나는 당신의 질문과 Github을 통해 당신을 따르기 시작했습니다. 당신은 매우 인상적입니다.
Sayka

28

libpostal : OpenStreetMap, OpenAddresses 및 OpenCage의 데이터를 사용하여 주소를 구문 분석하는 오픈 소스 라이브러리입니다.

https://github.com/openvenues/libpostal ( 자세한 정보 )

기타 도구 / 서비스 :


13

많은 주소 파서가 있습니다. 장소 이름과 거리 이름이있는 데이터베이스와 그렇지 않은 데이터베이스의 두 가지 기본 형태로 제공됩니다.

정규식 주소 구문 분석기는 많은 문제없이 최대 약 95 %의 성공률을 얻을 수 있습니다. 그런 다음 특이한 경우를 치기 시작합니다. CPAN의 Perl은 "Geo :: StreetAddress :: US"에 관한 것입니다. 파이썬과 자바 스크립트 포트가 있으며 모두 오픈 소스입니다. 더 많은 경우를 처리하여 성공률을 약간 높이는 Python 버전이 향상되었습니다. 그러나 마지막 3 %를 제대로 달성하려면 명확성을 돕기 위해 데이터베이스가 필요합니다.

3 자리 우편 번호와 미국 주 이름 및 약어가 포함 된 데이터베이스는 큰 도움이됩니다. 구문 분석기가 일관된 우편 번호 및 상태 이름을 발견하면 형식에 대한 잠금을 시작할 수 있습니다. 이것은 미국과 영국에서 매우 잘 작동합니다.

올바른 주소 구문 분석은 끝에서 시작하여 거꾸로 작동합니다. USPS 시스템이 그렇게하는 방식입니다. 국가 이름, 도시 이름 및 우편 번호를 비교적 쉽게 식별 할 수있는 마지막에는 주소가 모호하지 않습니다. 거리 이름은 일반적으로 격리 될 수 있습니다. 거리의 위치는 파싱하기가 가장 복잡합니다. "5 층"과 "Staples Pavillion"과 같은 것들이 있습니다. 데이터베이스가 큰 도움이 될 때입니다.


CPAN 모듈 Lingua : EN :: AddressParse도 있습니다. "Geo :: StreetAddress :: US보다 속도는 느리지 만 성공률이 높습니다.
Kim Ryan

8

업데이트 : Geocode.xyz는 이제 전 세계에서 작동합니다. 예는 https://geocode.xyz를 참조 하십시오.

미국, 멕시코 및 캐나다의 경우 geocoder.ca를 참조하십시오 .

예를 들면 다음과 같습니다.

입력 : 메인과 Arthur Kill rd New York의 교차점 근처에서 일어나는 일

산출:

<geodata>
  <latt>40.5123510000</latt>
  <longt>-74.2500500000</longt>
  <AreaCode>347,718</AreaCode>
  <TimeZone>America/New_York</TimeZone>
  <standard>
    <street1>main</street1>
    <street2>arthur kill</street2>
    <stnumber/>
    <staddress/>
    <city>STATEN ISLAND</city>
    <prov>NY</prov>
    <postal>11385</postal>
    <confidence>0.9</confidence>
  </standard>
</geodata>

웹 인터페이스에서 결과를 확인하거나 Json 또는 Jsonp로 출력 할 수도 있습니다. 예. 뉴욕 123 Main Street 주변의 음식점을 찾고 있습니다


openaddress를 사용하여 주소 구문 분석 시스템을 어떻게 구현 했습니까? 무차별 대입 전략을 사용하고 있습니까?
Nithin K Anil

1
'브 루트 포스'란 무엇을 의미합니까? 텍스트를 가능한 주소 문자열의 가능한 모든 조합으로 나누고 각각을 주소 데이터베이스와 비교하는 것은 실용적이지 않으며이 시스템보다 답변을 제공하는 데 더 많은 시간이 걸립니다. Openaddresses는 알고리즘에 대한 주소 형식의 '훈련 세트'를 구축하기위한 데이터 소스 중 하나입니다. 이 정보를 사용하여 구조화되지 않은 텍스트에서 주소를 구문 분석합니다.
Ervin Ruci

2
또 다른 유사한 시스템은 Geo :: libpostal ( perltricks.com/article/announcing-geo--libpostal )입니다. 또한 openstreetmap과 openaddresses를 사용하여 주소 템플릿을 즉석에서 구축합니다
Ervin Ruci

방금 수백 개의 실제 주소에서 geocode.xyz의 지오 파서 (텍스트로 보내고 위치를 반환)를 테스트했습니다. Google지도의 API 및 전체 주소 세트와 나란히 님 geocode.xyzscantext메소드는 대부분 실패했습니다. 그것은 항상 스위스 제네바보다 "제네바, 미국"을 선택했고 일반적으로 미국 편견이었다.
Marc Maxmeister

상황에 따라 다릅니다. geocode.xyz/?scantext= 제네바, % 20 스위스가 생성합니다 : 스위스, 제네바, CH 일치 점수와 일치 : 0.8, geocode.xyz/?scantext= 제네바, % 20USA 는 일치 위치 제네바, 미국 신뢰도를 생성합니다 : 1.0, 당신은 지역 바이어스는 다음 수 있습니다 geocode.xyz/?scantext=Geneva,%20USA®ion=CH
어빈 Ruci

4

코드가 없습니까? 부끄러운 줄 아세요!

다음은 간단한 JavaScript 주소 파서입니다. Matt가 위의 논문에서 제시하는 모든 이유에 대해 꽤 끔찍합니다 (거의 100 % 동의합니다 : 주소는 복잡한 유형이며 인간은 실수를 범합니다.

그러나 울기보다는 나는 노력하기로 결정했다.

이 코드는 대부분의 Esri 결과를 구문 분석 할 때 정상적으로 작동합니다.findAddressCandidate또한 거리 / 도시 / 주가 쉼표로 구분되어있는 단일 행 주소를 반환하는 다른 (역) 지오 코더도 있습니다. 국가 별 파서를 작성하거나 작성하려는 경우 확장 할 수 있습니다. 또는이 연습이 어려울 수있는 문제 나 JavaScript에 얼마나 큰 문제가 있는지에 대한 사례 연구로 사용하십시오. 나는 이것에 약 30 분을 보냈다는 것을 인정하지만 (미래 반복은 캐시, zip 유효성 검사 및 상태 조회뿐만 아니라 사용자 위치 컨텍스트를 추가 할 수 있음) 내 사용 사례에 효과적이었습니다. 텍스트 상자. 주소 구문 분석이 잘못되면 (소스 데이터가 열악하지 않는 한 드문 경우) 큰 문제는 아닙니다. 사용자는이를 확인하고 수정해야합니다! (그러나 자동화 솔루션의 경우 폐기 / 무시하거나 오류로 플래그를 지정할 수 있으므로 개발자는 새로운 형식을 지원하거나 소스 데이터를 수정할 수 있습니다.)

/* 
address assumptions:
- US addresses only (probably want separate parser for different countries)
- No country code expected.
- if last token is a number it is probably a postal code
-- 5 digit number means more likely
- if last token is a hyphenated string it might be a postal code
-- if both sides are numeric, and in form #####-#### it is more likely
- if city is supplied, state will also be supplied (city names not unique)
- zip/postal code may be omitted even if has city & state
- state may be two-char code or may be full state name.
- commas: 
-- last comma is usually city/state separator
-- second-to-last comma is possibly street/city separator
-- other commas are building-specific stuff that I don't care about right now.
- token count:
-- because units, street names, and city names may contain spaces token count highly variable.
-- simplest address has at least two tokens: 714 OAK
-- common simple address has at least four tokens: 714 S OAK ST
-- common full (mailing) address has at least 5-7:
--- 714 OAK, RUMTOWN, VA 59201
--- 714 S OAK ST, RUMTOWN, VA 59201
-- complex address may have a dozen or more:
--- MAGICICIAN SUPPLY, LLC, UNIT 213A, MAGIC TOWN MALL, 13 MAGIC CIRCLE DRIVE, LAND OF MAGIC, MA 73122-3412
*/

var rawtext = $("textarea").val();
var rawlist = rawtext.split("\n");

function ParseAddressEsri(singleLineaddressString) {
  var address = {
    street: "",
    city: "",
    state: "",
    postalCode: ""
  };

  // tokenize by space (retain commas in tokens)
  var tokens = singleLineaddressString.split(/[\s]+/);
  var tokenCount = tokens.length;
  var lastToken = tokens.pop();
  if (
    // if numeric assume postal code (ignore length, for now)
    !isNaN(lastToken) ||
    // if hyphenated assume long zip code, ignore whether numeric, for now
    lastToken.split("-").length - 1 === 1) {
    address.postalCode = lastToken;
    lastToken = tokens.pop();
  }

  if (lastToken && isNaN(lastToken)) {
    if (address.postalCode.length && lastToken.length === 2) {
      // assume state/province code ONLY if had postal code
      // otherwise it could be a simple address like "714 S OAK ST"
      // where "ST" for "street" looks like two-letter state code
      // possibly this could be resolved with registry of known state codes, but meh. (and may collide anyway)
      address.state = lastToken;
      lastToken = tokens.pop();
    }
    if (address.state.length === 0) {
      // check for special case: might have State name instead of State Code.
      var stateNameParts = [lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken];

      // check remaining tokens from right-to-left for the first comma
      while (2 + 2 != 5) {
        lastToken = tokens.pop();
        if (!lastToken) break;
        else if (lastToken.endsWith(",")) {
          // found separator, ignore stuff on left side
          tokens.push(lastToken); // put it back
          break;
        } else {
          stateNameParts.unshift(lastToken);
        }
      }
      address.state = stateNameParts.join(' ');
      lastToken = tokens.pop();
    }
  }

  if (lastToken) {
    // here is where it gets trickier:
    if (address.state.length) {
      // if there is a state, then assume there is also a city and street.
      // PROBLEM: city may be multiple words (spaces)
      // but we can pretty safely assume next-from-last token is at least PART of the city name
      // most cities are single-name. It would be very helpful if we knew more context, like
      // the name of the city user is in. But ignore that for now.
      // ideally would have zip code service or lookup to give city name for the zip code.
      var cityNameParts = [lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken];

      // assumption / RULE: street and city must have comma delimiter
      // addresses that do not follow this rule will be wrong only if city has space
      // but don't care because Esri formats put comma before City
      var streetNameParts = [];

      // check remaining tokens from right-to-left for the first comma
      while (2 + 2 != 5) {
        lastToken = tokens.pop();
        if (!lastToken) break;
        else if (lastToken.endsWith(",")) {
          // found end of street address (may include building, etc. - don't care right now)
          // add token back to end, but remove trailing comma (it did its job)
          tokens.push(lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken);
          streetNameParts = tokens;
          break;
        } else {
          cityNameParts.unshift(lastToken);
        }
      }
      address.city = cityNameParts.join(' ');
      address.street = streetNameParts.join(' ');
    } else {
      // if there is NO state, then assume there is NO city also, just street! (easy)
      // reasoning: city names are not very original (Portland, OR and Portland, ME) so if user wants city they need to store state also (but if you are only ever in Portlan, OR, you don't care about city/state)
      // put last token back in list, then rejoin on space
      tokens.push(lastToken);
      address.street = tokens.join(' ');
    }
  }
  // when parsing right-to-left hard to know if street only vs street + city/state
  // hack fix for now is to shift stuff around.
  // assumption/requirement: will always have at least street part; you will never just get "city, state"  
  // could possibly tweak this with options or more intelligent parsing&sniffing
  if (!address.city && address.state) {
    address.city = address.state;
    address.state = '';
  }
  if (!address.street) {
    address.street = address.city;
    address.city = '';
  }

  return address;
}

// get list of objects with discrete address properties
var addresses = rawlist
  .filter(function(o) {
    return o.length > 0
  })
  .map(ParseAddressEsri);
$("#output").text(JSON.stringify(addresses));
console.log(addresses);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea>
27488 Stanford Ave, Bowden, North Dakota
380 New York St, Redlands, CA 92373
13212 E SPRAGUE AVE, FAIR VALLEY, MD 99201
1005 N Gravenstein Highway, Sebastopol CA 95472
A. P. Croll &amp; 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
714 S OAK ST
714 S OAK ST, RUM TOWN, VA, 99201
3142 E SPRAGUE AVE, WHISKEY VALLEY, WA 99281
27488 Stanford Ave, Bowden, North Dakota
380 New York St, Redlands, CA 92373
</textarea>
<div id="output">
</div>


면책 조항 : 내 고객은 자신의 주소 데이터를 소유하고 자체 Esri 서버를 실행합니다. 당신이 구글, OSM, ArcGisOnline 등 어디에서 데이터를 잡아 경우,이 저장소에 OK 있는지 확인하고 사용 (많은 서비스가 당신이 저장할 수있는 방법에 제한이 있고, 얼마나 오래)
nothingisnecessary

위의 첫 번째 대답은 전체 주소 목록을 처리하는 경우이 문제를 정규식으로 해결할 수 없다는 강력한 사례입니다. 200 개국에서 예외가 너무 많습니다. 필자의 테스트에서는 문자열에서 국가를 다소 안정적으로 결정한 다음 각 국가별로 특정 정규식을 찾을 수 있습니다. 이는 아마도 더 나은 API가 작동하는 방식 일 것입니다.
Marc Maxmeister


2

미국 기반 주소의 또 다른 옵션은 YAddress (내 회사에서 만든 회사)입니다.

이 질문에 대한 많은 답변은 솔루션으로서 지오 코딩 도구를 제안합니다. 주소 구문 분석과 지오 코딩을 혼동하지 않는 것이 중요합니다. 그들은 동일하지 않습니다. 지오 코더는 부수적으로 주소를 구성 요소로 분류 할 수 있지만 일반적으로 비표준 주소 세트에 의존합니다. 이는 지오 코더 구문 분석 된 주소가 공식 주소와 같지 않을 수 있음을 의미합니다. 예를 들어 맨해튼에서 Google 지오 코딩 API가 "6 번가"라고 부르는 USPS는 "미국 애비뉴"라고합니다.


2

미국 주소 구문 분석의 경우

usaddress에만 pip로 제공되는 usaddress 패키지를 선호합니다

python3 -m pip install usaddress

PyPi 문서

이것은 미국 주소에 대해 잘 작동했습니다.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# address_parser.py
import sys
from usaddress import tag
from json import dumps, loads

if __name__ == '__main__':
    tag_mapping = {
        'Recipient': 'recipient',
        'AddressNumber': 'addressStreet',
        'AddressNumberPrefix': 'addressStreet',
        'AddressNumberSuffix': 'addressStreet',
        'StreetName': 'addressStreet',
        'StreetNamePreDirectional': 'addressStreet',
        'StreetNamePreModifier': 'addressStreet',
        'StreetNamePreType': 'addressStreet',
        'StreetNamePostDirectional': 'addressStreet',
        'StreetNamePostModifier': 'addressStreet',
        'StreetNamePostType': 'addressStreet',
        'CornerOf': 'addressStreet',
        'IntersectionSeparator': 'addressStreet',
        'LandmarkName': 'addressStreet',
        'USPSBoxGroupID': 'addressStreet',
        'USPSBoxGroupType': 'addressStreet',
        'USPSBoxID': 'addressStreet',
        'USPSBoxType': 'addressStreet',
        'BuildingName': 'addressStreet',
        'OccupancyType': 'addressStreet',
        'OccupancyIdentifier': 'addressStreet',
        'SubaddressIdentifier': 'addressStreet',
        'SubaddressType': 'addressStreet',
        'PlaceName': 'addressCity',
        'StateName': 'addressState',
        'ZipCode': 'addressPostalCode',
    }
    try:
        address, _ = tag(' '.join(sys.argv[1:]), tag_mapping=tag_mapping)
    except:
        with open('failed_address.txt', 'a') as fp:
            fp.write(sys.argv[1] + '\n')
        print(dumps({}))
    else:
        print(dumps(dict(address)))

address_parser.py 실행

 python3 address_parser.py 9757 East Arcadia Ave. Saugus MA 01906
 {"addressStreet": "9757 East Arcadia Ave.", "addressCity": "Saugus", "addressState": "MA", "addressPostalCode": "01906"}

0

프로젝트 중 하나에서 다음 주소 구문 분석기를 사용했습니다. 세계 대부분의 국가에 대한 주소를 정확하게 분석합니다.

http://address-parser.net/

독립형 라이브러리 또는 라이브 API로 사용 가능합니다.


1
그러나 그것은 제품에 대한 지불입니다.
제레미 톰슨

0

나는 파티에 늦었습니다. 여기 전에 호주에서 작성한 Excel VBA 스크립트가 있습니다. 다른 국가를 지원하도록 쉽게 수정할 수 있습니다. 여기에 C # 코드의 GitHub 리포지토리를 만들었습니다. 내 사이트에서 호스팅했으며 여기에서 다운로드 할 수 있습니다 : http://jeremythompson.net/rocks/ParseAddress.xlsm

전략

PostCode가 숫자이거나 RegEx와 일치하는 국가의 경우 내 전략이 매우 효과적입니다.

  1. 먼저 우리는 첫 줄과 성 씨를 감지합니다. 확인란을 선택 취소하여 이름을 건너 뛰고 주소로 쉽게 시작할 수 있습니다 ( '이름은 맨 위 행'이라고 함).

  2. 다음으로 거리와 숫자로 구성된 주소가 교외보다 먼저 나오고 St, Pde, Ave, Av, Rd, Cres, 루프 등이 구분 기호라고 기대하는 것이 안전합니다.

  3. 교외 대 주 및 심지어 국가를 감지하면 충돌이 발생할 수 있으므로 가장 복잡한 파서를 속일 수 있습니다. 이를 극복하기 위해 PoBox, Ph, Fax , Mobile 등 거리와 아파트 / 장치 번호를 제거한 후 PostCode 번호 만 남아 있다는 사실을 기반으로 PostCode 조회를 사용합니다 . 이것은 정규 표현식과 쉽게 일치하여 교외와 국가를 검색 할 수 있습니다.

National Post Office Service는 교외 및 주와 함께 Excel 코드, db 테이블, text / json / xml 파일 등에 저장할 수있는 우편 번호 목록을 무료로 제공합니다.

  1. 마지막으로 일부 우편 번호에는 여러 교외가 있으므로 주소에 어떤 교외가 나타나는지 확인합니다.

여기에 이미지 설명을 입력하십시오

VBA 코드

면책 조항, 나는이 코드가 완벽하지는 않지만 잘 작성되었지만 모든 프로그래밍 언어로 변환하고 모든 유형의 응용 프로그램에서 실행하기가 매우 쉽다는 것을 알고 있습니다. 전략은 국가 및 규칙에 따라 답입니다. :

Option Explicit

Private Const TopRow As Integer = 0

Public Sub ParseAddress()
Dim strArr() As String
Dim sigRow() As String
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim Stat As String
Dim SpaceInName As Integer
Dim Temp As String
Dim PhExt As String

On Error Resume Next

Temp = ActiveSheet.Range("Address")

'Split info into array
strArr = Split(Temp, vbLf)

'Trim the array
For i = 0 To UBound(strArr)
strArr(i) = VBA.Trim(strArr(i))
Next i

'Remove empty items/rows    
ReDim sigRow(LBound(strArr) To UBound(strArr))
For i = LBound(strArr) To UBound(strArr)
    If Trim(strArr(i)) <> "" Then
        sigRow(j) = strArr(i)
        j = j + 1
    End If
Next i
ReDim Preserve sigRow(LBound(strArr) To j)

'Find the name (MUST BE ON THE FIRST ROW UNLESS CHECKBOX UNTICKED)
i = TopRow
If ActiveSheet.Shapes("chkFirst").ControlFormat.Value = 1 Then

SpaceInName = InStr(1, sigRow(i), " ", vbTextCompare) - 1

If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("FirstName") = VBA.Left(sigRow(i), SpaceInName)
Else
 If MsgBox("First Name: " & VBA.Mid$(sigRow(i), 1, SpaceInName), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("FirstName") = VBA.Left(sigRow(i), SpaceInName)
End If

If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("Surname") = VBA.Mid(sigRow(i), SpaceInName + 2)
Else
  If MsgBox("Surame: " & VBA.Mid(sigRow(i), SpaceInName + 2), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Surname") = VBA.Mid(sigRow(i), SpaceInName + 2)
End If
sigRow(i) = ""
End If

'Find the Street by looking for a "St, Pde, Ave, Av, Rd, Cres, loop, etc"
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
    For j = 0 To 8
    If InStr(1, VBA.UCase(sigRow(i)), Street(j), vbTextCompare) > 0 Then

    'Find the position of the street in order to get the suburb
    SpaceInName = InStr(1, VBA.UCase(sigRow(i)), Street(j), vbTextCompare) + Len(Street(j)) - 1

    'If its a po box then add 5 chars
    If VBA.Right(Street(j), 3) = "BOX" Then SpaceInName = SpaceInName + 5

    If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
    ActiveSheet.Range("Street") = VBA.Mid(sigRow(i), 1, SpaceInName)
    Else
      If MsgBox("Street Address: " & VBA.Mid(sigRow(i), 1, SpaceInName), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Street") = VBA.Mid(sigRow(i), 1, SpaceInName)
    End If
    'Trim the Street, Number leaving the Suburb if its exists on the same line
    sigRow(i) = VBA.Mid(sigRow(i), SpaceInName) + 2
    sigRow(i) = Replace(sigRow(i), VBA.Mid(sigRow(i), 1, SpaceInName), "")

    GoTo PastAddress:
    End If
    Next j
End If
Next i
PastAddress:

'Mobile
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
    For j = 0 To 3
    Temp = Mb(j)
        If VBA.Left(VBA.UCase(sigRow(i)), Len(Temp)) = Temp Then
        If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
        ActiveSheet.Range("Mobile") = VBA.Mid(sigRow(i), Len(Temp) + 2)
        Else
          If MsgBox("Mobile: " & VBA.Mid(sigRow(i), Len(Temp) + 2), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Mobile") = VBA.Mid(sigRow(i), Len(Temp) + 2)
        End If
    sigRow(i) = ""
    GoTo PastMobile:
    End If
    Next j
End If
Next i
PastMobile:

'Phone
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
    For j = 0 To 1
    Temp = Ph(j)
        If VBA.Left(VBA.UCase(sigRow(i)), Len(Temp)) = Temp Then

            'TODO: Detect the intl or national extension here.. or if we can from the postcode.
            If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
            ActiveSheet.Range("Phone") = VBA.Mid(sigRow(i), Len(Temp) + 3)
            Else
              If MsgBox("Phone: " & VBA.Mid(sigRow(i), Len(Temp) + 3), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Phone") = VBA.Mid(sigRow(i), Len(Temp) + 3)
            End If

        sigRow(i) = ""
        GoTo PastPhone:
        End If
    Next j
End If
Next i
PastPhone:


'Email
For i = 1 To UBound(sigRow)
    If Len(sigRow(i)) > 0 Then
        'replace with regEx search
        If InStr(1, sigRow(i), "@", vbTextCompare) And InStr(1, VBA.UCase(sigRow(i)), ".CO", vbTextCompare) Then
        Dim email As String
        email = sigRow(i)
        email = Replace(VBA.UCase(email), "EMAIL:", "")
        email = Replace(VBA.UCase(email), "E-MAIL:", "")
        email = Replace(VBA.UCase(email), "E:", "")
        email = Replace(VBA.UCase(Trim(email)), "E ", "")
        email = VBA.LCase(email)

            If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
            ActiveSheet.Range("Email") = email
            Else
              If MsgBox("Email: " & email, vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Email") = email
            End If
        sigRow(i) = ""
        Exit For
        End If
    End If
Next i

'Now the only remaining items will be the postcode, suburb, country
'there shouldn't be any numbers (eg. from PoBox,Ph,Fax,Mobile) except for the Post Code

'Join the string and filter out the Post Code
Temp = Join(sigRow, vbCrLf)
Temp = Trim(Temp)

For i = 1 To Len(Temp)

Dim postCode As String
postCode = VBA.Mid(Temp, i, 4)

'In Australia PostCodes are 4 digits
If VBA.Mid(Temp, i, 1) <> " " And IsNumeric(postCode) Then

    If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
    ActiveSheet.Range("PostCode") = postCode
    Else
      If MsgBox("Post Code: " & postCode, vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("PostCode") = postCode
    End If

    'Lookup the Suburb and State based on the PostCode, the PostCode sheet has the lookup
    Dim mySuburbArray As Range
    Set mySuburbArray = Sheets("PostCodes").Range("A2:B16670")

    Dim suburbs As String
    For j = 1 To mySuburbArray.Columns(1).Cells.Count
    If mySuburbArray.Cells(j, 1) = postCode Then
        'Check if the suburb is listed in the address
        If InStr(1, UCase(Temp), mySuburbArray.Cells(j, 2), vbTextCompare) > 0 Then

        'Set the Suburb and State
        ActiveSheet.Range("Suburb") = mySuburbArray.Cells(j, 2)
        Stat = mySuburbArray.Cells(j, 3)
        ActiveSheet.Range("State") = Stat

        'Knowing the State - for Australia we can get the telephone Ext
        PhExt = PhExtension(VBA.UCase(Stat))
        ActiveSheet.Range("PhExt") = PhExt

        'remove the phone extension from the number
        Dim prePhone As String
        prePhone = ActiveSheet.Range("Phone")
        prePhone = Replace(prePhone, PhExt & " ", "")
        prePhone = Replace(prePhone, "(" & PhExt & ") ", "")
        prePhone = Replace(prePhone, "(" & PhExt & ")", "")
        ActiveSheet.Range("Phone") = prePhone
        Exit For
        End If
    End If
    Next j
Exit For
End If
Next i

End Sub


Private Function PhExtension(ByVal State As String) As String
Select Case State
Case Is = "NSW"
PhExtension = "02"
Case Is = "QLD"
PhExtension = "07"
Case Is = "VIC"
PhExtension = "03"
Case Is = "NT"
PhExtension = "04"
Case Is = "WA"
PhExtension = "05"
Case Is = "SA"
PhExtension = "07"
Case Is = "TAS"
PhExtension = "06"
End Select
End Function

Private Function Ph(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Ph = "PH"
Case Is = 1
Ph = "PHONE"
'Case Is = 2
'Ph = "P"
End Select
End Function

Private Function Mb(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Mb = "MB"
Case Is = 1
Mb = "MOB"
Case Is = 2
Mb = "CELL"
Case Is = 3
Mb = "MOBILE"
'Case Is = 4
'Mb = "M"
End Select
End Function

Private Function Fax(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Fax = "FAX"
Case Is = 1
Fax = "FACSIMILE"
'Case Is = 2
'Fax = "F"
End Select
End Function

Private Function State(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
State = "NSW"
Case Is = 1
State = "QLD"
Case Is = 2
State = "VIC"
Case Is = 3
State = "NT"
Case Is = 4
State = "WA"
Case Is = 5
State = "SA"
Case Is = 6
State = "TAS"
End Select
End Function

Private Function Street(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Street = " ST"
Case Is = 1
Street = " RD"
Case Is = 2
Street = " AVE"
Case Is = 3
Street = " AV"
Case Is = 4
Street = " CRES"
Case Is = 5
Street = " LOOP"
Case Is = 6
Street = "PO BOX"
Case Is = 7
Street = " STREET"
Case Is = 8
Street = " ROAD"
Case Is = 9
Street = " AVENUE"
Case Is = 10
Street = " CRESENT"
Case Is = 11
Street = " PARADE"
Case Is = 12
Street = " PDE"
Case Is = 13
Street = " LANE"
Case Is = 14
Street = " COURT"
Case Is = 15
Street = " BLVD"
Case Is = 16
Street = "P.O. BOX"
Case Is = 17
Street = "P.O BOX"
Case Is = 18
Street = "PO BOX"
Case Is = 19
Street = "POBOX"
End Select
End Function
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.