フリーフォームのストリート/郵便の住所をテキストから解析してコンポーネントに変換する方法


136

私たちは主に米国でビジネスを行っており、すべての住所フィールドを1つのテキスト領域に結合することにより、ユーザーエクスペリエンスの向上を図っています。しかし、いくつかの問題があります。

  • ユーザーが入力した住所が正しくないか、標準形式ではない可能性があります
  • クレジットカードによる支払いを処理するには、住所を複数の部分(ストリート、都市、州など)に分割する必要があります
  • ユーザーは自分の住所以外にも入力できます(名前や会社名など)
  • Googleはこれを実行できますが、利用規約とクエリの制限は、特に予算が厳しい場合は禁止されます

どうやら、これはよくある質問です:

周囲のテキストからアドレスを分離し、それを断片に分割する方法はありますか?アドレスを解析するための正規表現はありますか?


以下の回答は、グローバルな問題を無視しないため、より有用です。そのアドレスは一般的なパターンに適合しません。
マークマックスマイスター2018

回答:


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番も完全です、信じられないかもしれません。これらの数値だけで、可能なすべてのアドレスのデータベースに対して解析すると、完全なアドレスを検出できます。各数値をコンポーネントとして見ると、欠落している方向指示子、2次指定子、ZIP + 4コードを入力するのは簡単です。完全に拡張され、標準化された状態は次のとおりです。

205 N 1105 W Apt 14

ビバリーヒルズCA 90210-5221

住所データはあなたのものではありません

ライセンスされたベンダーに公式の住所データを提供しているほとんどの国では、住所データ自体が管理機関に属しています。米国では、USPSがアドレスを所有しています。カナダポスト、ロイヤルメール、その他にも同じことが当てはまりますが、国によって所有権の適用または定義が少し異なります。これは通常、アドレスデータベースのリバースエンジニアリングを禁止するため、これを知ることは重要です。データの取得、保存、使用方法には注意が必要です。

Googleマップは、住所をすばやく修正するための一般的な手段ですが、TOSはかなり禁止されています。たとえば、Googleマップを表示せずにデータやAPIを使用することはできません。また、有料でない限り、非営利目的でのみ使用でき、データを保存することはできません(一時的なキャッシュを除く)。理にかなっています。Googleのデータは、世界で最高のものの1つです。ただし、Googleマップは住所を確認しませ。アドレスが存在しない場合は、それはまだアドレスがどこに表示されますでしょう、それがあれば可能でした(自分の路上でそれを試して、あなたが存在しないことを知っている家の番号を使用する)が存在します。これは時々役立ちますが、注意してください。

Nominatimの使用ポリシーも同様に制限されており、特に大量の商用利用の場合、データはほとんどが無料のソースから取得されているため、適切に維持されていません(オープンプロジェクトの性質など)。あなたの要望。素晴らしいコミュニティによってサポートされています。

USPS自体にAPIがありますが、それは大幅に低下し、保証もサポートもありません。使いにくいかもしれません。一部の人々は問題なく控えめにそれを使います。しかし、USPSがそれらを介して出荷する住所を確認するためにのみAPIを使用することをUSPSが要求することは見落としがちです。

人々は住所が難しいと期待している

残念ながら、私たちは社会が住所が複雑であることを期待するように条件付けました。これについては、インターネット上に数十の優れたUX記事がありますが、実際には、個々のフィールドを持つアドレスフォームがある場合、それはユーザーが期待することです。フォームが想定しているフォーマット、またはフォームが想定していないフィールドがフォームに必要な場合。または、ユーザーは自分のアドレスの特定の部分をどこに配置すればよいかわかりません。

最近、チェックアウトフォームの悪いUXについていくことはできますが、代わりに、住所を1つのフィールドに結合することは歓迎すべき変更です。 、あなたの長い形を理解しようとするのではなく。ただし、この変更は予期せぬものであり、ユーザーは最初は少し耳障りになることがあります。ただそれを意識してください。

この苦痛の一部は、演説の前にカントリーフィールドを前面に出すことで緩和できます。最初に国フィールドに入力すると、フォームを表示する方法がわかります。多分あなたは単一フィールドの米国の住所を扱う良い方法を持っているかもしれないので、彼らが米国を選択した場合、フォームを単一のフィールドに減らし、そうでなければコンポーネントフィールドを表示することができます。考えることだけ!

なぜそれが難しいのかがわかりました。あなたはそれについて何ができますか?

USPSは、CASS™認定と呼ばれるプロセスを通じてベンダーにライセンスを付与し、検証済みの住所を顧客に提供します。これらのベンダーは、毎月更新されるUSPSデータベースにアクセスできます。彼らのソフトウェアは、認定を受けるために厳しい基準に準拠する必要があり、多くの場合、上記のような制限条件への同意を必要としません。

リストを処理したり、APIを使用したりできるCASS認定企業は数多くあります。たとえば、Melissa Data、Experian QAS、SmartyStreetsなどです。

(「広告」のために失敗したため、この時点で私の答えを切り捨てました。あなたのために働く解決策を見つけるのはあなた次第です。)

真実:実際、私はこれらの会社で働いていません。それは広告ではありません。


1
南米(ウルグアイ)の住所はどうですか?:D
Bart Calixto 2014年

11
@Brian-おそらく、ユーザーが自分の会社の製品を使用するかどうかに関係なく、質問と回答を読む人に多くの有用な情報を提供したためです。
ザレフェス2014年

7
@Brianこれらのサイトはコンテンツスクレイパーです。彼らはSERPランキングを得るためにコンテンツを動いています。今まで見たことがない。このコンテンツを他の場所の前または後に投稿したことはありません。
Matt

2
@khuderm私があなたのコメントを読んだとき、私はすべての反対するコメントが消えていることに気づきました。それがいつどのようにして起こったかはわかりません。しかし、とにかく、私の回答の編集履歴を見ると、役立つかもしれない米国の住所抽出プログラムへの直接参照が見つかります。私は最後の仕事で働いたときにそれをビルドしましたが、それはプロプライエタリなコードなので共有することはできません...しかし、それらは存在します うまくいけば役に立ちます。
Matt

2
おっとっと。すみません、@マット。さて、私はあなたの質問とGithubをフォローし始めました。あなたはかなり印象的です。
Sayka 2016

28

libpostal:住所を解析するためのオープンソースライブラリ。OpenStreetMap、OpenAddresses、OpenCageからのデータを使用してトレーニングします。

https://github.com/openvenues/libpostal詳細について

その他のツール/サービス:


13

多くの番地パーサーがあります。それらには2つの基本的なフレーバーがあります。地名とストリート名のデータベースがあるものと、ないものです。

正規表現のストリートアドレスパーサーは、ほとんど問題なく最大95%の成功率を得ることができます。次に、異常なケースに遭遇し始めます。CPANのPerlの1つである「Geo :: StreetAddress :: US」は、その点で優れています。PythonとJavaScriptの移植版があり、すべてオープンソースです。より多くのケースを処理することで成功率をわずかに上げるPythonの改良版があります。ただし、最後の3%を正しくするには、明確化を支援するデータベースが必要です。

3桁の郵便番号と米国の州名と略称を含むデータベースは、非常に役立ちます。パーサーが一貫した郵便番号と州名を見ると、形式に固執し始める可能性があります。これは、米国と英国で非常にうまく機能します。

適切な番地解析は最後から始まり、逆方向に機能します。それがUSPSシステムが行う方法です。住所は、国名、都市名、郵便番号が比較的認識しやすい最後のあいまいさがほとんどありません。通常通りの名前は分離できます。道路上の場所は、解析が最も複雑です。そこで「フィフス・フロア」や「ステープルズ・パビリオン」などに出会います。それはデータベースが大きな助けになるときです。


CPANモジュールLingua:​​EN :: AddressParseもあります。。「ジオ:: StreetAddress ::米国より遅く、より高い成功率与えながら
キム・ライアン

8

更新:Geocode.xyzが世界中で機能するようになりました。例については、https://geocode.xyzを参照してください

米国、メキシコ、カナダについては、geocoder.caを参照してください。

例えば:

入力:メインとアーサーの交差点付近で何かが起こっている

出力:

<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>

Webインターフェースで結果を確認するか、JsonまたはJsonpとして出力を取得することもできます。例えば。ニューヨークのメインストリート123周辺のレストランを探しています。


openaddressを使用してアドレス解析システムをどのように実装しましたか?ブルートフォース戦略を使用していますか?
Nithin K Anil

1
「ブルートフォース」とはどういう意味ですか?テキストを可能なアドレス文字列のすべての可能な組み合わせに分割し、それぞれをアドレスのデータベースと比較することは実用的ではなく、このシステムよりも回答を提供するのに時間がかかります。Openaddressesは、アルゴリズムのアドレス形式の「トレーニングセット」を構築するためのデータソースの1つです。この情報を使用して、非構造化テキストからアドレスを解析します。
Ervin Ruci

2
別の同様のシステムは、Geo :: libpostal(perltricks.com/article/announcing-geo--libpostal)です。また、openstreetmapとopenaddressesを使用して、オンザフライでアドレステンプレートを構築します
Ervin Ruci

数百の実際の住所でgeocode.xyzのジオパーサー(テキストで送信し、場所を取得)をテストしました。GoogleマップのAPIとアドレスのグローバルセットが並んでいるため、geocode.xyzscantextメソッドはほとんどの場合失敗しました。それは常に「スイス、ジュネーブ」より「米国、ジュネーブ」を選び、一般的に米国に偏っていた。
マークマックスマイスター

状況によります。geocode.xyz/?scantext=Geneva,%20Switzerland以下を生成します:一致場所ジュネーブ、スイス、スイス信頼スコア:0.8一方geocode.xyz/?scantext=Geneva,%20USAは一致場所ジュネーブ、米国信頼スコア:1.0また、次のように地域バイアスを設定できます。geocode.xyz
Ervin Ruci

4

コードなし?残念!

簡単なJavaScriptアドレスパーサーを次に示します。マットが上記の論文で述べているすべての理由でかなりひどいです(私はほぼ100%同意します:アドレスは複雑なタイプであり、人間は間違いを犯します。これをアウトソーシングして自動化するほうがよい-余裕がある場合)。

しかし、泣くのではなく、次のことを試みることにしました。

このコードは、ほとんどのEsriの結果を解析するのに問題なく機能しますfindAddressCandidateまた、ストリート/都市/州がカンマで区切られている単一行の住所を返す他のいくつかの(リバース)ジオコーダーを使用します。国固有のパーサーが必要な場合、または作成する場合は拡張できます。または、この演習がいかに困難であるか、または私がJavaScriptでどれほどひどいかについてのケーススタディとしてこれを使用してください。私はこれに約30分しか費やさなかったことを認めます(今後の反復により、キャッシュ、zip検証、状態のルックアップ、およびユーザーの場所のコンテキストが追加される可能性があります)、それは私のユースケースで機能しました:エンドユーザーは、ジオコード検索応答を4に解析するフォームを表示しますテキストボックス。アドレスの解析が正しく行われなかった場合(ソースデータが不十分である場合を除き、これはまれです)、大した問題ではありません。ユーザーが確認して修正することができます。(ただし、自動化されたソリューションの場合、破棄/無視するか、エラーとしてフラグを立てることができるため、開発者は新しい形式をサポートするか、ソースデータを修正できます。)

/* 
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サーバーを実行しています。google、OSM、ArcGisOnline、またはどこからでもデータを取得する場合は、それを保存して使用しても問題ないことを確認してください(多くのサービスでは、保存方法と保存期間に制限があります)
18年

上記の最初の答えは、グローバルアドレスリストを扱っている場合、この問題は正規表現では解決できないという説得力のあるケースを作り出します。200か国には例外が多すぎます。私のテストでは、文字列から国をかなり確実に特定し、国ごとに特定の正規表現を検索できます-これはおそらくより良いAPIが機能する方法です。
Marc Maxmeister 2018


2

米国ベースの住所の別のオプションは、YAddress(私が働いている会社が作成したもの)です。

この質問に対する多くの回答は、ジオコーディングツールをソリューションとして提案しています。住所の解析とジオコーディングを混同しないようにすることが重要です。彼らは同じではありません。ジオコーダーは、副次的な利点として住所をコンポーネントに分解することがありますが、通常、非標準の住所セットに依存しています。つまり、ジオコーダーで解析された住所は、公式の住所と同じではない可能性があります。たとえば、Google geocoding APIがマンハッタンの「6th Ave」と呼ぶものに対して、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

パーティーに遅れました。ここに、数年前にオーストラリア向けに書いたExcel VBAスクリプトがあります。他の国をサポートするように簡単に変更できます。ここでC#コードのGitHubリポジトリを作成しました。私は自分のサイトでホストしています。ここからダウンロードできます。http//jeremythompson.net/rocks/ParseAddress.xlsm

戦略

数値であるか、RegExと一致する可能性があるPostCodeを持つ国では、私の戦略は非常にうまく機能します。

  1. 最初に、一番上の行と見なされる姓と名を検出します。名前をスキップして、チェックボックスをオフにすることでアドレスから開始するのは簡単です(以下に示すように、「名前は最上列」と呼ばれます)。

  2. 次に、StreetとNumberで構成されるアドレスが郊外の前にあり、St、Pde、Ave、Av、Rd、Cres、loopなどがセパレーターであると予期しても安全です。

  3. 郊外vs州、さらには国を検出すると、競合が発生する可能性があるため、最も洗練されたパーサーをだますことができます。これを克服するために、ストリートおよびアパートメント/ユニット番号、およびPoBox、Ph、Fax、モバイルなどを削除した後、PostCode 番号のみが残るという事実に基づいて、PostCodeルックアップを使用します。これはregExと照合して、郊外と国を検索するのが簡単です。

国立郵便局サービスは、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.