Cara mengurai alamat jalan / pos bentuk bebas dari teks, dan ke dalam komponen

136

Kami melakukan bisnis sebagian besar di Amerika Serikat dan berusaha meningkatkan pengalaman pengguna dengan menggabungkan semua bidang alamat ke dalam satu area teks. Tetapi ada beberapa masalah:

  • Alamat yang diketikkan oleh pengguna mungkin tidak benar atau dalam format standar
  • Alamat harus dipisahkan menjadi beberapa bagian (jalan, kota, negara bagian, dll.) Untuk memproses pembayaran kartu kredit
  • Pengguna dapat memasukkan lebih dari sekedar alamat mereka (seperti nama atau perusahaan mereka dengan itu)
  • Google dapat melakukan ini tetapi Ketentuan Layanan dan batas kueri tidak diperbolehkan, terutama dengan anggaran yang ketat

Rupanya, ini adalah pertanyaan umum:

Apakah ada cara untuk mengisolasi alamat dari teks di sekitarnya dan memecahnya menjadi beberapa bagian? Apakah ada ekspresi reguler ke alamat parse?

Mat
sumber
Jawaban di bawah ini lebih berguna karena mereka tidak mengabaikan masalah global - bahwa alamat tidak sesuai dengan pola umum.
Marc Maxmeister

Jawaban:

290

Saya sering melihat pertanyaan ini ketika saya bekerja di perusahaan verifikasi alamat. Saya memposting jawaban di sini untuk membuatnya lebih mudah diakses oleh programmer yang mencari-cari dengan pertanyaan yang sama. Perusahaan tempat saya memproses miliaran alamat, dan kami belajar banyak dalam prosesnya.

Pertama, kita perlu memahami beberapa hal tentang alamat.

Alamat tidak teratur

Ini berarti bahwa ekspresi reguler keluar. Saya telah melihat semuanya, dari ekspresi reguler sederhana yang cocok dengan alamat dalam format yang sangat spesifik, hingga ini:

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

... untuk ini di mana 900+ file kelas garis menghasilkan ekspresi reguler supermasif dengan cepat untuk mencocokkan lebih banyak lagi. Saya tidak merekomendasikan ini (misalnya, ini biola dari regex di atas, yang membuat banyak kesalahan ). Tidak ada formula ajaib yang mudah untuk membuatnya bekerja. Dalam teori dan oleh teori, itu tidak mungkin untuk mencocokkan alamat dengan ekspresi reguler.

USPS Publikasi 28 mendokumentasikan banyak format alamat yang mungkin, dengan semua kata kunci dan variasinya. Yang terburuk dari semua, alamat seringkali ambigu. Kata-kata dapat berarti lebih dari satu hal ("St." dapat "Saint" atau "Street") dan ada kata-kata yang saya yakin mereka ciptakan. (Siapa yang tahu bahwa "Stravenue" adalah sufiks jalanan?)

Anda memerlukan beberapa kode yang benar-benar memahami alamat, dan jika kode itu memang ada, itu adalah rahasia dagang. Tapi Anda mungkin bisa menggulung sendiri jika Anda benar-benar menyukainya.

Alamat datang dalam bentuk dan ukuran yang tidak terduga

Berikut adalah beberapa alamat yang dibuat-buat (tapi lengkap):

1)  102 main street
    Anytown, state

2)  400n 600e #2, 52173

3)  p.o. #104 60203

Bahkan ini mungkin valid:

4)  829 LKSDFJlkjsdflkjsdljf Bkpw 12345

5)  205 1105 14 90210

Jelas, ini tidak standar. Tanda baca dan jeda baris tidak dijamin. Inilah yang terjadi:

  1. Nomor 1 selesai karena berisi alamat jalan dan kota dan negara bagian. Dengan informasi itu, cukup mengidentifikasi alamat, dan itu dapat dianggap "terkirim" (dengan beberapa standardisasi).

  2. Angka 2 selesai karena juga berisi alamat jalan (dengan nomor sekunder / unit) dan kode ZIP 5 digit, yang cukup untuk mengidentifikasi alamat.

  3. Nomor 3 adalah format kotak pos lengkap, karena berisi kode ZIP.

  4. Nomor 4 juga lengkap karena kode ZIP unik , artinya entitas swasta atau perusahaan telah membeli ruang alamat itu. Kode ZIP unik adalah untuk ruang pengiriman volume tinggi atau terkonsentrasi. Apa pun yang ditujukan ke kode ZIP 12345 pergi ke General Electric di Schenectady, NY. Contoh ini tidak akan menjangkau siapa pun secara khusus, tetapi USPS masih dapat mengirimkannya.

  5. Angka 5 juga lengkap, percaya atau tidak. Dengan hanya angka-angka itu, alamat lengkap dapat ditemukan ketika diuraikan terhadap basis data semua alamat yang memungkinkan. Mengisi directionals yang hilang, designator sekunder, dan kode ZIP + 4 adalah sepele ketika Anda melihat setiap angka sebagai komponen. Begini tampilannya, sepenuhnya diperluas dan terstandarisasi:

205 N 1105 W Apt 14

Beverly Hills CA 90210-5221

Data alamat bukan milik Anda

Di sebagian besar negara yang menyediakan data alamat resmi untuk vendor berlisensi, data alamat itu sendiri milik badan pengatur. Di AS, USPS memiliki alamat. Hal yang sama berlaku untuk Canada Post, Royal Mail, dan lainnya, meskipun masing-masing negara memberlakukan atau mendefinisikan kepemilikan sedikit berbeda. Mengetahui hal ini penting, karena biasanya melarang rekayasa ulang database alamat. Anda harus berhati-hati cara mendapatkan, menyimpan, dan menggunakan data.

Google Maps adalah tujuan umum untuk perbaikan alamat cepat, tetapi KL agak mahal; misalnya, Anda tidak dapat menggunakan data atau API mereka tanpa menunjukkan Google Map, dan hanya untuk tujuan non-komersial (kecuali Anda membayar), dan Anda tidak dapat menyimpan data (kecuali untuk caching sementara). Masuk akal. Data Google adalah beberapa yang terbaik di dunia. Namun, Google Maps tidak memverifikasi alamat. Jika alamat tidak ada, masih akan menunjukkan di mana alamat akan menjadi jika memang ada (mencobanya di jalan Anda sendiri, menggunakan nomor rumah yang Anda tahu tidak ada). Terkadang ini berguna, tetapi perlu diperhatikan.

Kebijakan penggunaan Nominatim sama-sama membatasi, terutama untuk volume tinggi dan penggunaan komersial, dan data sebagian besar diambil dari sumber-sumber gratis, sehingga tidak dipelihara dengan baik (seperti sifat proyek terbuka) - namun, ini mungkin masih sesuai kebutuhanmu. Itu didukung oleh komunitas yang hebat.

USPS sendiri memiliki API, tetapi turun banyak dan tidak disertai jaminan atau dukungan. Mungkin juga sulit digunakan. Beberapa orang menggunakannya dengan hemat tanpa masalah. Tetapi mudah untuk dilewatkan bahwa USPS mengharuskan Anda menggunakan API mereka hanya untuk mengonfirmasi alamat untuk dikirimkan melalui mereka.

Orang berharap alamat menjadi sulit

Sayangnya, kami mengkondisikan masyarakat kami untuk mengharapkan alamat menjadi rumit. Ada lusinan artikel UX yang bagus di seluruh Internet tentang hal ini, tetapi faktanya, jika Anda memiliki formulir alamat dengan bidang individual, itulah yang diharapkan pengguna, meskipun itu membuat lebih sulit untuk alamat tepi-kasus yang tidak sesuai dengan format formulir mengharapkan, atau mungkin formulir memerlukan bidang yang seharusnya tidak. Atau pengguna tidak tahu di mana harus meletakkan bagian tertentu dari alamat mereka.

Saya bisa terus-menerus tentang UX buruk dari formulir checkout hari ini, tetapi sebagai gantinya saya hanya akan mengatakan bahwa menggabungkan alamat ke dalam satu bidang akan menjadi perubahan yang disambut baik - orang akan dapat mengetikkan alamat mereka sesuai keinginan mereka , daripada mencoba mencari tahu bentuk panjang Anda. Namun, perubahan ini akan tidak terduga dan pengguna mungkin merasa sedikit menggelegar pada awalnya. Sadarilah itu.

Bagian dari rasa sakit ini dapat dikurangi dengan menempatkan bidang negara di depan, di depan alamat. Ketika mereka mengisi bidang negara terlebih dahulu, Anda tahu cara membuat formulir Anda muncul. Mungkin Anda memiliki cara yang baik untuk menangani alamat US bidang tunggal, jadi jika mereka memilih Amerika Serikat, Anda bisa mengurangi formulir Anda menjadi satu bidang, jika tidak, tunjukkan bidang komponen. Hanya hal-hal untuk dipikirkan!

Sekarang kita tahu mengapa itu sulit; apa yang bisa kamu lakukan?

USPS melisensikan vendor melalui proses yang disebut Sertifikasi CASS ™ untuk memberikan alamat terverifikasi kepada pelanggan. Vendor ini memiliki akses ke basis data USPS, diperbarui setiap bulan. Perangkat lunak mereka harus sesuai dengan standar ketat untuk disertifikasi, dan mereka tidak sering memerlukan persetujuan untuk persyaratan pembatasan seperti yang dibahas di atas.

Ada banyak perusahaan CASS-Certified yang dapat memproses daftar atau memiliki API: Data Melissa, Experian QAS, dan SmartyStreets.

(Karena mendapatkan kritik untuk "iklan", saya telah memotong jawaban saya pada titik ini. Terserah Anda untuk menemukan solusi yang cocok untuk Anda.)

Kebenaran: Sungguh, kawan, saya tidak bekerja di perusahaan mana pun. Itu bukan iklan.

Mat
sumber
1
Bagaimana dengan alamat Amerika Selatan (Uruguay)? : D
Bart Calixto
11
@Brian - Mungkin karena pengguna telah memberikan banyak informasi berguna bagi mereka yang membaca pertanyaan dan jawaban, terlepas dari apakah mereka memilih untuk menggunakan produk perusahaannya atau tidak.
Zarepheth
7
@Brian Situs-situs itu adalah pencakar konten. Mereka meracau konten untuk mendapatkan peringkat SERP. Saya belum pernah melihat mereka sebelumnya. Saya belum pernah memposting konten ini sebelum atau setelah di tempat lain.
Matt
2
@khuderm Saya perhatikan baru saja ketika saya membaca komentar Anda bahwa semua komentar yang berbeda telah menghilang; tidak yakin bagaimana / kapan itu terjadi. Tapi bagaimanapun, lihat riwayat edit jawaban saya dan Anda akan menemukan referensi langsung ke ekstraktor alamat AS yang mungkin membantu Anda. Saya membangunnya ketika saya bekerja di pekerjaan terakhir saya, tetapi itu adalah kode hak milik sehingga saya tidak dapat membagikannya ... tetapi mereka memang ada. Semoga bermanfaat.
Matt
2
Ups. Maaf @Matt. Yah saya sudah mulai mengikuti Anda melalui pertanyaan Anda dan juga Github. Cukup mengesankan Anda.
Sayka
28

libpostal: perpustakaan open-source untuk mengurai alamat, pelatihan dengan data dari OpenStreetMap, OpenAddresses, dan OpenCage.

https://github.com/openvenue/libpostal ( info lebih lanjut tentang itu )

Alat / layanan lain:

David Portabella
sumber
13

Ada banyak parser alamat jalan. Mereka datang dalam dua rasa dasar - yang memiliki database nama tempat dan nama jalan, dan yang tidak.

Pengurai alamat jalan ekspresi reguler dapat mencapai tingkat keberhasilan 95% tanpa banyak kesulitan. Kemudian Anda mulai memukul kasus-kasus yang tidak biasa. Yang Perl di CPAN, "Geo :: StreetAddress :: US", adalah tentang itu baik. Ada port Python dan Javascript itu, semua open source. Saya memiliki versi perbaikan dalam Python yang menggerakkan tingkat keberhasilan sedikit dengan menangani lebih banyak kasus. Untuk mendapatkan 3% terakhir yang benar, Anda perlu basis data untuk membantu disambiguasi.

Basis data dengan kode ZIP 3 digit serta nama dan singkatan negara bagian AS sangat membantu. Ketika parser melihat kode pos yang konsisten dan nama negara, itu dapat mulai mengunci ke format. Ini bekerja sangat baik untuk AS dan Inggris.

Penguraian alamat jalan yang benar dimulai dari akhir dan bekerja mundur. Begitulah cara sistem USPS melakukannya. Alamat paling tidak ambigu pada akhirnya, di mana nama negara, nama kota, dan kode pos relatif mudah dikenali. Nama jalan biasanya dapat diisolasi. Lokasi di jalan adalah yang paling rumit untuk diurai; di sana Anda menemukan hal-hal seperti "Lantai Kelima" dan "Paviliun Staples". Saat itulah database sangat membantu.

John Nagle
sumber
Ada juga modul CPAN Lingua: EN :: AddressParse. Meskipun lebih lambat dari "Geo :: StreetAddress :: AS, ini memberikan tingkat keberhasilan yang lebih tinggi.
Kim Ryan
8

UPDATE: Geocode.xyz sekarang berfungsi di seluruh dunia. Untuk contoh, lihat https://geocode.xyz

Untuk AS, Meksiko, dan Kanada, lihat geocoder.ca .

Sebagai contoh:

Input: sesuatu terjadi di dekat persimpangan main dan arthur membunuh new york

Keluaran:

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

Anda juga dapat memeriksa hasilnya di antarmuka web atau mendapatkan output sebagai Json atau Jsonp. misalnya. Saya mencari restoran di sekitar 123 Main Street, New York

Ervin Ruci
sumber
Bagaimana Anda menerapkan sistem parsing alamat menggunakan openaddress? Apakah Anda menggunakan strategi brute force?
Nithin K Anil
1
Apa yang kamu maksud dengan 'brute force'? Memecah teks menjadi semua kombinasi yang mungkin dari string alamat yang mungkin dan membandingkan masing-masing dengan database alamat tidak praktis dan akan mengambil lebih banyak waktu untuk memberikan jawaban daripada sistem ini. Openaddresses adalah salah satu sumber data untuk membangun 'set pelatihan' format alamat untuk algoritma. Ini menggunakan informasi ini untuk mem-parsing alamat dari teks yang tidak terstruktur.
Ervin Ruci
2
Sistem serupa lainnya adalah Geo :: libpostal ( perltricks.com/article/announcing-geo--libpostal ) Tampaknya mereka juga menggunakan openstreetmap dan openaddresses tampaknya, untuk membangun templat alamat dengan cepat
Ervin Ruci
Saya baru saja menguji geoparser geocode.xyz (mengirim teks, mendapatkan kembali lokasi) pada ratusan alamat aktual. Mengingat berdampingan dengan API google peta itu, dan satu set global alamat, geocode.xyz's scantextmetode gagal sebagian besar waktu. Itu selalu memilih "Jenewa, AS" daripada "Jenewa, Swiss" dan umumnya bias AS.
Marc Maxmeister
Itu tergantung pada konteksnya. geocode.xyz/?scantext=Geneva,%20Switzerland akan menghasilkan: Lokasi Pertandingan Geneva, Swiss, CH Skor Keyakinan: 0,8 sementara geocode.xyz/?scantext=Geneva,%20USA akan menghasilkan Lokasi Pertandingan Jenewa, Skor Keyakinan AS: 1,0 Juga, Anda dapat membuat bias wilayah sebagai berikut: geocode.xyz/?scantext=Geneva,%20USA®ion=CH
Ervin Ruci
4

Tidak ada kode? Karena malu!

Berikut adalah pengurai alamat JavaScript sederhana. Ini cukup mengerikan untuk setiap alasan yang diberikan Matt dalam disertasinya di atas (yang saya hampir 100% setuju dengan: alamat adalah tipe yang kompleks, dan manusia membuat kesalahan; lebih baik untuk melakukan outsourcing dan mengotomatisasi ini - ketika Anda mampu).

Tetapi bukannya menangis, saya memutuskan untuk mencoba:

Kode ini berfungsi baik untuk parsing sebagian besar hasil untuk EsrifindAddressCandidatedan juga dengan beberapa geocoder (mundur) lain yang mengembalikan alamat satu baris di mana jalan / kota / negara dibatasi oleh koma. Anda dapat memperluas jika Anda ingin atau menulis parser khusus negara. Atau gunakan ini sebagai studi kasus tentang betapa sulitnya latihan ini atau betapa buruknya saya di JavaScript. Saya akui saya hanya menghabiskan sekitar tiga puluh menit untuk ini (iterasi di masa depan dapat menambahkan cache, validasi zip, dan pencarian status serta konteks lokasi pengguna), tetapi itu berfungsi untuk kasus penggunaan saya: Pengguna akhir melihat formulir yang mem-parsing respons pencarian geocode ke dalam 4 kotak teks. Jika parsing alamat salah (yang jarang terjadi kecuali sumber data buruk) itu bukan masalah besar - pengguna dapat memverifikasi dan memperbaikinya! (Tetapi untuk solusi otomatis dapat membuang / mengabaikan atau menandai kesalahan sehingga dev dapat mendukung format baru atau memperbaiki data sumber.)

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

tidak perlu
sumber
disclaimer: klien saya memiliki data alamat mereka dan menjalankan server Esri mereka sendiri. Jika Anda mengambil data dari google, OSM, ArcGisOnline, atau di mana pun, pastikan itu adalah OK untuk menyimpan dan menggunakannya (banyak layanan memiliki batasan mengenai bagaimana Anda dapat menyimpan, dan untuk berapa lama)
nothingisnecessary
Jawaban pertama di atas membuat alasan kuat bahwa masalah ini tidak dapat diselesaikan dengan regex jika Anda berurusan dengan daftar alamat global. 200 negara memiliki terlalu banyak pengecualian. Dalam pengujian saya, Anda dapat menentukan negara dari string dengan cukup andal, lalu mencari regex khusus untuk setiap negara - yang mungkin merupakan cara kerja API yang lebih baik.
Marc Maxmeister
2

Jika Anda ingin mengandalkan data libpostal OSM sangat kuat dan menangani banyak peringatan paling umum dengan input alamat.

Vitor Magalhães
sumber
Saya pikir jawaban Anda adalah duplikat dari posting ini. Tapi saran yang bagus.
Michael
2

Pilihan lain untuk alamat yang berbasis di AS adalah YAddress (dibuat oleh perusahaan tempat saya bekerja).

Banyak jawaban untuk pertanyaan ini menyarankan alat geocoding sebagai solusi. Penting untuk tidak membingungkan parsing alamat dan geocoding; mereka tidak sama. Sementara geocoder dapat memecah alamat menjadi komponen sebagai manfaat sampingan, mereka biasanya mengandalkan set alamat non-standar. Ini berarti bahwa alamat parsing geocoder mungkin tidak sama dengan alamat resmi. Misalnya, apa yang disebut Google geocoding API "6th Ave" di Manhattan, USPS menyebut "Avenue of the Americas".

Michael Diomin
sumber
2

Untuk Parsing Alamat AS,

Saya lebih suka menggunakan paket usaddress yang tersedia di pip untuk usaddress saja

python3 -m pip install usaddress

Dokumentasi
Pypi

Ini bekerja dengan baik bagi saya untuk alamat AS.

#!/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)))

Menjalankan 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"}
theBuzzyCoder
sumber
0

Di salah satu proyek kami, kami telah menggunakan parser alamat berikut. Ini mem-parsing alamat untuk sebagian besar negara di dunia dengan akurasi yang baik.

http://address-parser.net/

Ini tersedia sebagai perpustakaan yang berdiri sendiri atau sebagai API langsung.

Waqas Anwar
sumber
1
Tapi ini adalah produk berbayar.
Jeremy Thompson
0

Saya terlambat ke pesta, ini skrip Excel VBA yang saya tulis bertahun-tahun yang lalu untuk Australia. Itu dapat dengan mudah dimodifikasi untuk mendukung Negara lain. Saya telah membuat repositori GitHub dari kode C # di sini. Saya telah meng-host-nya di situs saya dan Anda dapat mengunduhnya di sini: http://jeremythompson.net/rocks/ParseAddress.xlsm

Strategi

Untuk negara mana pun dengan Kode Pos yang numerik atau dapat dicocokkan dengan RegEx, strategi saya berfungsi dengan sangat baik:

  1. Pertama, kami mendeteksi Nama Depan dan Nama Belakang yang dianggap sebagai baris teratas. Mudah untuk melewatkan nama dan mulai dengan alamat dengan menghapus centang pada kotak centang (disebut 'Nama adalah baris atas' seperti yang ditunjukkan di bawah ini).

  2. Selanjutnya aman untuk mengharapkan Alamat yang terdiri dari Street dan Number datang sebelum Suburb dan St, Pde, Ave, Av, Rd, Cres, loop, dll adalah pemisah.

  3. Mendeteksi Pinggiran Kota vs Negara dan bahkan Negara dapat menipu parser yang paling canggih karena mungkin ada konflik. Untuk mengatasinya saya menggunakan PostCode look up berdasarkan fakta bahwa setelah menelanjangi nomor Street dan Apartment / Unit serta PoBox, Ph, Fax , Mobile dll, hanya nomor PostCode yang akan tetap ada. Ini mudah dicocokkan dengan regEx untuk kemudian mencari di pinggiran kota dan negara.

Layanan Kantor Pos Nasional Anda akan memberikan daftar kode pos dengan Suburbs dan States secara gratis yang dapat Anda simpan dalam excel sheet, tabel db, file teks / json / xml, dll.

  1. Akhirnya, karena beberapa Kode Pos memiliki beberapa Suburbs, kami memeriksa suburb mana yang muncul di Alamat.

Contoh

masukkan deskripsi gambar di sini

Kode VBA

DISCLAIMER, saya tahu kode ini tidak sempurna, atau bahkan ditulis dengan baik namun sangat mudah untuk dikonversi ke bahasa pemrograman apa pun dan berjalan di semua jenis aplikasi. Strategi adalah jawabannya tergantung pada negara dan aturan Anda, ambil kode ini sebagai contoh :

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
Jeremy Thompson
sumber