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

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

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

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

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



답변

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

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

주소가 일정 하지 않습니다

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

/ \ 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가 많습니다.

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

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


답변

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

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

기타 도구 / 서비스 :


답변

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

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

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

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


답변

업데이트 : 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 주변의 음식점을 찾고 있습니다


답변

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

다음은 간단한 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>


답변

OSM 데이터에 의존하고 싶다면 libpostal 은 매우 강력하며 주소 입력으로 가장 일반적인 경고를 처리합니다.


답변

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

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