Wir sind hauptsächlich in den USA tätig und versuchen die Benutzererfahrung zu verbessern, indem wir alle Adressfelder in einem einzigen Textbereich zusammenfassen. Es gibt jedoch einige Probleme:
Anscheinend ist dies eine häufige Frage:
Gibt es eine Möglichkeit, eine Adresse vom Text um sie herum zu isolieren und in Stücke zu zerlegen? Gibt es einen regulären Ausdruck, um Adressen zu analysieren?
Ich habe diese Frage oft gesehen, als ich für eine Adressprüfungsfirma gearbeitet habe. Ich poste die Antwort hier, um sie Programmierern zugänglicher zu machen, die mit der gleichen Frage suchen. Die Firma, in der ich gearbeitet habe, hat Milliarden von Adressen verarbeitet, und wir haben dabei viel gelernt.
Zunächst müssen wir ein paar Dinge über Adressen verstehen.
Dies bedeutet, dass reguläre Ausdrücke nicht vorhanden sind. Ich habe alles gesehen, von einfachen regulären Ausdrücken, die Adressen in einem ganz bestimmten Format zuordnen:
/\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}) (Gericht | ct | Straße | st | Antrieb | dr | lane | 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 | ODER | PA | RI | SC | SD | TN | TX | UT | VA | VI | VT | WA | WI | WV | WY) ([\ s | \, |.] +)? (\ S +\d {5})? ([\ S | \, |.] +)/i
... bis this wobei eine Datei mit mehr als 900 Zeilen einen supermassiven regulären Ausdruck generiert, der noch besser passt. Ich empfehle diese nicht (zum Beispiel hier ist eine Fiedel der obigen Regex, die viele Fehler macht ). Es gibt keine einfache Zauberformel, um dies zum Laufen zu bringen. In der Theorie und durch Theorie ist es nicht möglich, Adressen mit einem regulären Ausdruck abzugleichen.
SPS Publication 28 dokumentiert die vielen möglichen Adressformate mit all ihren Schlüsselwörtern und Variationen. Am schlimmsten ist, dass Adressen häufig nicht eindeutig sind. Wörter können mehr als eine Sache bedeuten ("St" kann "Saint" oder "Street" sein) und es gibt Wörter, von denen ich mir ziemlich sicher bin, dass sie erfunden wurden. (Wer wusste, dass "Stravenue" ein Straßensuffix war?)
Sie benötigen einen Code, der Adressen wirklich versteht. Wenn dieser Code existiert, ist er ein Geschäftsgeheimnis. Aber du könntest wahrscheinlich deine eigene Rolle spielen, wenn du wirklich darauf stehst.
Hier sind einige erfundene (aber vollständige) Adressen:
1) 102 main street
Anytown, state
2) 400n 600e #2, 52173
3) p.o. #104 60203
Auch diese sind möglicherweise gültig:
4) 829 LKSDFJlkjsdflkjsdljf Bkpw 12345
5) 205 1105 14 90210
Offensichtlich sind diese nicht standardisiert. Satzzeichen und Zeilenumbrüche sind nicht garantiert. Folgendes ist los:
Nummer 1 ist vollständig, da sie eine Straße, eine Stadt und ein Bundesland enthält. Mit diesen Informationen gibt es genug, um die Adresse zu identifizieren, und sie kann als "lieferbar" angesehen werden (mit einer gewissen Standardisierung).
Nummer 2 ist vollständig, da sie auch eine Straße (mit Sekundär-/Einheitennummer) und eine 5-stellige Postleitzahl enthält, die zur Identifizierung einer Straße ausreicht Adresse.
Nummer 3 ist ein vollständiges Postfachformat, da es eine Postleitzahl enthält.
Nummer 4 ist ebenfalls vollständig, da die Postleitzahl ist eindeutig bedeutet, dass eine private Einheit oder ein Unternehmen diesen Adressraum gekauft hat. Eine eindeutige Postleitzahl steht für großvolumige oder konzentrierte Zustellräume. Alles, was an die Postleitzahl 12345 adressiert ist, geht an General Electric in Schenectady, NY. Dieses Beispiel wird niemanden erreichen, aber der USPS wäre immer noch in der Lage, es zu liefern.
Nummer 5 ist auch vollständig, ob Sie es glauben oder nicht. Mit genau diesen Zahlen kann die vollständige Adresse ermittelt werden, wenn sie anhand einer Datenbank mit allen möglichen Adressen analysiert wird. Das Ausfüllen der fehlenden Richtungsangaben, des sekundären Bezeichners und der Postleitzahl + 4 ist trivial, wenn Sie jede Nummer als Komponente sehen. So sieht es aus, vollständig erweitert und standardisiert:
205 N 1105 W Fewo 14
Beverly Hills CA 90210-5221
In den meisten Ländern, in denen lizenzierte Anbieter offizielle Adressdaten erhalten, gehören die Adressdaten selbst der zuständigen Behörde. In den USA besitzt der USPS die Adressen. Das Gleiche gilt für Canada Post, Royal Mail und andere, obwohl jedes Land das Eigentum ein wenig anders durchsetzt oder definiert. Es ist wichtig zu wissen, dass die Adressdatenbank normalerweise nicht rückentwickelt werden darf. Sie müssen vorsichtig sein, wie Sie Daten erfassen, speichern und verwenden.
Google Maps ist eine häufige Adresse für schnelle Adresskorrekturen, aber das TOS ist ziemlich unerschwinglich. Beispielsweise können Sie ihre Daten oder APIs nicht ohne Anzeige einer Google Map und nur für nichtkommerzielle Zwecke verwenden (es sei denn, Sie zahlen dafür), und Sie können die Daten nicht speichern (außer für temporäres Caching). Macht Sinn. Die Daten von Google gehören zu den besten der Welt. Google Maps überprüft die Adresse jedoch nicht . Wenn eine Adresse nicht vorhanden ist, wird weiterhin angezeigt, wo sich die Adresse befinden würde , wenn sie vorhanden wäre (versuchen Sie es mit Ihrer eigene Straße; verwenden Sie eine Hausnummer, von der Sie wissen, dass sie nicht existiert). Das ist manchmal nützlich, aber sei dir dessen bewusst.
Die Verwendungsrichtlinie von Nominatim ist in ähnlicher Weise einschränkend, insbesondere für den großvolumigen und kommerziellen Gebrauch, und die Daten stammen größtenteils aus freien Quellen, weshalb sie nicht so gut gepflegt sind (wie dies bei offenen Projekten der Fall ist). - Dies entspricht jedoch möglicherweise noch Ihren Anforderungen. Es wird von einer tollen Community unterstützt.
Der USPS selbst hat eine API, aber es geht viel runter und kommt ohne Garantien oder Unterstützung. Es könnte auch schwer zu bedienen sein. Einige Leute setzen es sparsam und ohne Probleme ein. Es ist jedoch leicht zu übersehen, dass der USPS verlangt, dass Sie die API nur zur Bestätigung der Adressen verwenden, um über diese zu versenden.
Leider haben wir unsere Gesellschaft darauf konditioniert, dass Adressen kompliziert werden. Es gibt Dutzende guter UX-Artikel über das Internet, aber wenn Sie ein Adressformular mit einzelnen Feldern haben, erwarten dies die Benutzer, auch wenn es für Edge-Case-Adressen schwieriger ist, die nicht in das Adressbuch passen Format, das das Formular erwartet, oder möglicherweise erfordert das Formular ein Feld, das es nicht sollte. Oder Benutzer wissen nicht, wo sie einen bestimmten Teil ihrer Adresse platzieren sollen.
Ich könnte mich heutzutage immer wieder mit der schlechten Benutzeroberfläche von Checkout-Formularen befassen, aber stattdessen sage ich nur, dass das Kombinieren der Adressen in einem einzigen Feld eine willkommene Änderung sein wird - Die Benutzer können ihre Adresse so eingeben, wie sie es für richtig halten, anstatt zu versuchen, Ihr langwieriges Formular herauszufinden. Diese Änderung ist jedoch unerwartet und wird den Benutzern möglicherweise zunächst etwas unangenehm erscheinen. Sei dir dessen einfach bewusst.
Ein Teil dieser Schmerzen kann gelindert werden, indem Sie das Feld vor der Adresse nach vorne stellen. Wenn sie das Feld "Land" zuerst ausfüllen, wissen Sie, wie Sie Ihr Formular anzeigen. Möglicherweise haben Sie eine gute Möglichkeit, mit US-Adressen in Einzelfeldern umzugehen. Wenn Sie also "USA" auswählen, können Sie Ihr Formular auf ein einziges Feld reduzieren, andernfalls werden die Komponentenfelder angezeigt. Nur Dinge zum Nachdenken!
Der USPS lizenziert Anbieter über einen Prozess namens CASS ™ -Zertifizierung, um Kunden überprüfte Adressen bereitzustellen. Diese Anbieter haben Zugriff auf die USPS-Datenbank, die monatlich aktualisiert wird. Ihre Software muss strengen Standards entsprechen, um zertifiziert zu werden, und sie erfordern nicht oft die Zustimmung zu den oben beschriebenen einschränkenden Bedingungen.
Es gibt viele CASS-zertifizierte Unternehmen, die Listen verarbeiten können oder über APIs verfügen: Melissa Data, Experian QAS und SmartyStreets, um nur einige zu nennen.
(Da ich nach "Werbung" Ausschau gehalten habe, habe ich meine Antwort an dieser Stelle abgeschnitten. Es liegt an Ihnen, eine Lösung zu finden, die für Sie funktioniert.)
Die Wahrheit: Wirklich, Leute, ich arbeite bei keiner dieser Firmen. Es ist keine Werbung.
libpostal: eine Open-Source-Bibliothek zum Parsen von Adressen, Training mit Daten aus OpenStreetMap, OpenAddresses und OpenCage.
https://github.com/openvenues/libpostal ( weitere Informationen dazu )
Andere Tools/Dienste:
http://www.gisgraphy.com Kostenlose, Open Source und sofort einsatzbereite Geocoder- und Geolokalisierungs-Webservices, die OpenStreetMap, GeoNames und Quattroshapes integrieren.
https://github.com/kodapan/osm-common Bibliothek für den Zugriff auf OpenStreetMap-Dienste, das Analysieren und Verarbeiten von Daten.
Es gibt viele Straßenadressen-Parser. Es gibt zwei grundlegende Varianten: eine mit Datenbanken von Ortsnamen und Straßennamen und solche, die dies nicht tun.
Ein Straßenadressen-Parser für reguläre Ausdrücke kann ohne großen Aufwand eine Erfolgsquote von etwa 95% erreichen. Dann schlagen Sie die ungewöhnlichen Fälle an. Das Perl One in CPAN, "Geo :: StreetAddress :: US", geht es darum. Es gibt Python- und Javascript-Ports, alles Open Source. Ich habe eine verbesserte Version in Python, die die Erfolgsquote durch die Behandlung von mehr Fällen etwas erhöht. Um die letzten 3% richtig zu machen, benötigen Sie jedoch Datenbanken, die bei der Disambiguierung helfen.
Eine Datenbank mit 3-stelligen Postleitzahlen und Namen und Abkürzungen von US-Bundesstaaten ist eine große Hilfe. Wenn ein Parser eine konsistente Postleitzahl und einen Statusnamen sieht, kann er sich an das Format anschließen. Dies funktioniert sehr gut für die USA und Großbritannien.
Die korrekte Straßenadressenanalyse beginnt am Ende und arbeitet rückwärts. So machen es die USPS-Systeme. Am Ende sind die Adressen am wenigsten mehrdeutig, wobei Ländernamen, Städtenamen und Postleitzahlen relativ leicht zu erkennen sind. Straßennamen können normalerweise isoliert werden. Orte auf der Straße sind am komplexesten zu analysieren; dort trifft man auf Dinge wie "Fifth Floor" und "Staples Pavillion". Dann ist eine Datenbank eine große Hilfe.
UPDATE: Geocode.xyz funktioniert jetzt weltweit. Beispiele finden Sie unter https://geocode.xyz
Für USA, Mexiko und Kanada siehe geocoder.ca .
Zum Beispiel:
Eingabe: etwas in der Nähe der Kreuzung von Main und Arthur Kill New York
Ausgabe:
<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>
Sie können die Ergebnisse auch in der Weboberfläche überprüfen oder als Json oder Jsonp ausgeben. z.B. Ich suche Restaurants in der Nähe von 123 Main Street, New York
Für das Analysieren von US-Adressen
Ich bevorzuge die Verwendung des usaddress-Pakets, das in pip nur für usaddress verfügbar ist
python3 -m pip install usaddress
Dies funktionierte gut für mich für US-Adresse.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# address_parser.py
import sys
from usaddress import tag
from json import dumps, loads
if __== '__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)))
Ausführen der Datei 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"}
Kein Code? Zum Schämen!
Hier ist ein einfacher JavaScript-Adressenparser. Es ist ziemlich schrecklich für jeden einzelnen Grund, den Matt in seiner obigen Dissertation vorgibt (womit ich fast zu 100% einverstanden bin: Adressen sind komplexe Typen, und Menschen machen Fehler; besser auslagern und automatisieren - wenn Sie es sich leisten können).
Aber anstatt zu weinen, entschied ich mich zu versuchen:
Dieser Code funktioniert in Ordnung für die Analyse der meisten Esri-Ergebnisse für findAddressCandidate
und auch für andere (umgekehrte) Geocodierer, die einzeilige Adressen zurückgeben, bei denen Straße/Stadt/Bundesstaat durch Kommas getrennt werden. Sie können erweitern, wenn Sie länderspezifische Parser wollen oder schreiben möchten. Oder nutzen Sie einfach diese Fallstudie, wie schwierig diese Übung sein kann oder wie schlecht ich bei JavaScript bin. Ich gebe zu, dass ich nur etwa dreißig Minuten dafür aufgewendet habe (zukünftige Iterationen könnten Caches, Zip-Validierung und Status-Lookups sowie den Kontext des Benutzerstandorts hinzufügen), aber es funktionierte für meinen Anwendungsfall: Der Endbenutzer sieht eine Form, in der die Antwort auf Geocodesuche in 4 analysiert wird Textfelder. Wenn die Adressanalyse falsch ist (was selten ist, wenn die Quelldaten schlecht waren), ist dies keine große Sache - der Benutzer kann dies überprüfen und beheben! (Aber für automatisierte Lösungen kann entweder verworfen/ignoriert oder als Fehler markiert werden, sodass dev entweder das neue Format unterstützen oder Quelldaten korrigieren kann.)
/*
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 & 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>
Eine weitere Option für in den USA ansässige Adressen ist YAddress (von der Firma, für die ich arbeite).
Viele Antworten auf diese Frage schlagen Geokodierungswerkzeuge als Lösung vor. Es ist wichtig, Adressanalyse und Geokodierung nicht zu verwechseln. Sie sind nicht gleich. Während Geocoder eine Adresse als Nebeneffekt in Komponenten aufteilen können, stützen sie sich normalerweise auf nicht standardmäßige Adressensätze. Dies bedeutet, dass eine vom Geocodierer analysierte Adresse möglicherweise nicht mit der offiziellen Adresse übereinstimmt. Zum Beispiel, was Google Geocoding API "6th Ave" in Manhattan nennt, nennt USPS "Avenue of the Americas".
Ich bin zu spät zur Party, hier ist ein Excel VBA-Skript, das ich vor Jahren für Australien geschrieben habe. Es kann leicht geändert werden, um andere Länder zu unterstützen. Ich habe hier ein GitHub-Repository des C # -Codes erstellt. Ich habe es auf meiner Site gehostet und Sie können es hier herunterladen: http://jeremythompson.net/rocks/ParseAddress.xlsm
Für jedes Land mit einem Postcode, der numerisch ist oder mit einem RegEx abgeglichen werden kann, funktioniert meine Strategie sehr gut:
Zuerst erkennen wir den Vor- und Nachnamen, von dem angenommen wird, dass er die oberste Zeile ist. Es ist einfach, den Namen zu überspringen und mit der Adresse zu beginnen, indem Sie das Kontrollkästchen deaktivieren ("Name ist oberste Zeile", wie unten gezeigt).
Als nächstes ist es sicher zu erwarten, dass die Adresse, die aus der Straße und der Hausnummer besteht, vor dem Vorort steht und dass St, Pde, Ave, Av, Rd, Cres, Schleife usw. ein Trennzeichen ist.
Das Erkennen des Vororts gegen den Staat und sogar das Land kann die ausgefeiltesten Parser austricksen, da es Konflikte geben kann. Um dies zu überwinden benutze ich einen PostCode-Look, der darauf basiert, dass nach dem Strippen von Straßen- und Apartment-/Unit-Nummern sowie der PoBox, Ph, Fax , Mobile etc, nur der PostCode Nummer wird bleiben. Dies ist einfach mit einem regEx abzugleichen, um dann die Vororte und das Land nachzuschlagen.
Ihr Nationaler Postdienst stellt Ihnen kostenlos eine Liste der Postleitzahlen mit Vororten und Bundesstaaten zur Verfügung, die Sie in einer Excel-Tabelle, einer DB-Tabelle, einer Text-/JSON-/XML-Datei usw. speichern können.
HAFTUNGSAUSSCHLUSS, ich weiß, dass dieser Code nicht perfekt oder sogar gut geschrieben ist, aber er ist sehr einfach in jede Programmiersprache zu konvertieren und in jeder Art von Anwendung auszuführen. Die Strategie ist die Antwort, abhängig von Ihrem Land und Ihren Regeln. Nehmen Sie diesen Code als Beispiel :
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
Wenn Sie sich auf OSM-Daten verlassen wollen, ist libpostal sehr leistungsfähig und behandelt die häufigsten Vorbehalte mit Adresseingaben.
In einem unserer Projekte haben wir den folgenden Adressparser verwendet. Es analysiert Adressen für die meisten Länder der Welt mit guter Genauigkeit.
Es ist als eigenständige Bibliothek oder als Live-API verfügbar.