Làm cách nào để phân tích địa chỉ đường phố / bưu chính miễn phí khỏi văn bản và thành các thành phần


136

Chúng tôi kinh doanh phần lớn ở Hoa Kỳ và đang cố gắng cải thiện trải nghiệm người dùng bằng cách kết hợp tất cả các trường địa chỉ vào một vùng văn bản. Nhưng có một vài vấn đề:

  • Địa chỉ các loại người dùng có thể không chính xác hoặc ở định dạng chuẩn
  • Địa chỉ phải được tách thành các phần (đường phố, thành phố, tiểu bang, v.v.) để xử lý thanh toán bằng thẻ tín dụng
  • Người dùng có thể nhập nhiều hơn chỉ địa chỉ của họ (như tên hoặc công ty của họ với nó)
  • Google có thể làm điều này nhưng Điều khoản dịch vụ và giới hạn truy vấn bị cấm, đặc biệt là với ngân sách eo hẹp

Rõ ràng, đây là một câu hỏi phổ biến:

Có cách nào để tách một địa chỉ khỏi văn bản xung quanh nó và chia nó thành từng mảnh không? Có một biểu thức thường xuyên để phân tích địa chỉ?


Các câu trả lời dưới đây hữu ích hơn vì chúng không bỏ qua vấn đề toàn cầu - địa chỉ đó không phù hợp với một mô hình chung.
Marc Maxmeister

Câu trả lời:


289

Tôi thấy câu hỏi này rất nhiều khi tôi làm việc cho một công ty xác minh địa chỉ. Tôi đang đăng câu trả lời ở đây để làm cho nó dễ tiếp cận hơn với các lập trình viên đang tìm kiếm xung quanh với cùng một câu hỏi. Công ty tôi đã xử lý hàng tỷ địa chỉ và chúng tôi đã học được rất nhiều trong quá trình này.

Đầu tiên, chúng ta cần hiểu một vài điều về địa chỉ.

Địa chỉ không thường xuyên

Điều này có nghĩa là các biểu thức thông thường được đưa ra. Tôi đã thấy tất cả, từ các biểu thức chính quy đơn giản khớp địa chỉ theo một định dạng rất cụ thể, đến đây:

/ \ 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}) (tòa án | ct | đường phố | st | drive | dr | làn đường | ln | đường | đường | 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 |, |.] +) / tôi

... đến đây , nơi một tệp hơn 900 dòng tạo ra một biểu thức chính quy siêu lớn khi đang di chuyển để phù hợp hơn nữa. Tôi không đề xuất những điều này (ví dụ, đây là một câu đố về regex ở trên, điều đó gây ra nhiều sai lầm ). Không có một công thức ma thuật dễ dàng nào để làm việc này. Về lý thuyết và theo lý thuyết, không thể kết hợp các địa chỉ với một biểu thức chính quy.

Ấn phẩm USPS 28 ghi lại nhiều định dạng của các địa chỉ có thể, với tất cả các từ khóa và variatons của chúng. Tệ nhất của tất cả, địa chỉ thường mơ hồ. Các từ có thể có nghĩa nhiều hơn một thứ ("St" có thể là "Saint" hoặc "Street") và có những từ mà tôi khá chắc chắn rằng chúng đã phát minh ra. (Ai biết rằng "Stravenue" là hậu tố đường phố?)

Bạn sẽ cần một số mã thực sự hiểu địa chỉ và nếu mã đó tồn tại, đó là một bí mật thương mại. Nhưng bạn có thể có thể tự lăn nếu bạn thực sự thích điều đó.

Địa chỉ có hình dạng và kích thước bất ngờ

Dưới đây là một số địa chỉ (nhưng đầy đủ):

1)  102 main street
    Anytown, state

2)  400n 600e #2, 52173

3)  p.o. #104 60203

Ngay cả những điều này có thể hợp lệ:

4)  829 LKSDFJlkjsdflkjsdljf Bkpw 12345

5)  205 1105 14 90210

Rõ ràng, những điều này không được tiêu chuẩn hóa. Dấu câu và ngắt dòng không được đảm bảo. Đây là những gì đang xảy ra:

  1. Số 1 hoàn thành vì nó chứa địa chỉ đường phố và thành phố và tiểu bang. Với thông tin đó, có đủ xác định địa chỉ và nó có thể được coi là "có thể giao được" (với một số tiêu chuẩn hóa).

  2. Số 2 hoàn tất vì nó cũng chứa một địa chỉ đường phố (với số thứ cấp / đơn vị) và mã ZIP gồm 5 chữ số, đủ để xác định một địa chỉ.

  3. Số 3 là một định dạng hộp thư bưu điện hoàn chỉnh, vì nó chứa mã ZIP.

  4. Số 4 cũng hoàn thành vì mã ZIP là duy nhất , có nghĩa là một thực thể tư nhân hoặc công ty đã mua không gian địa chỉ đó. Mã ZIP duy nhất dành cho không gian phân phối có khối lượng lớn hoặc tập trung. Bất cứ điều gì được gửi đến mã ZIP 12345 đều đến General Electric ở Schenectady, NY. Ví dụ này sẽ không đến được với bất kỳ ai, nhưng USPS vẫn có thể cung cấp nó.

  5. Số 5 cũng đã hoàn thành, tin hay không. Chỉ với những con số đó, địa chỉ đầy đủ có thể được phát hiện khi phân tích cú pháp dựa trên cơ sở dữ liệu của tất cả các địa chỉ có thể. Điền vào các hướng bị thiếu, chỉ định phụ và mã ZIP + 4 là không đáng kể khi bạn xem mỗi số là một thành phần. Đây là những gì nó trông giống như, hoàn toàn mở rộng và tiêu chuẩn hóa:

205 N 1105 W Apt 14

Đồi tuyết CA 90210-5221

Dữ liệu địa chỉ không phải của riêng bạn

Ở hầu hết các quốc gia cung cấp dữ liệu địa chỉ chính thức cho các nhà cung cấp được cấp phép, dữ liệu địa chỉ thuộc về cơ quan chủ quản. Ở Mỹ, USPS sở hữu các địa chỉ. Điều tương tự cũng đúng với Canada Post, Royal Mail và các quốc gia khác, mặc dù mỗi quốc gia thực thi hoặc định nghĩa quyền sở hữu khác nhau một chút. Biết điều này rất quan trọng, vì nó thường cấm kỹ thuật đảo ngược cơ sở dữ liệu địa chỉ. Bạn phải cẩn thận làm thế nào để có được, lưu trữ và sử dụng dữ liệu.

Google Maps là một cách phổ biến để sửa lỗi địa chỉ nhanh chóng, nhưng ĐKDV khá nghiêm cấm; ví dụ: bạn không thể sử dụng dữ liệu hoặc API của họ mà không hiển thị Google Map và chỉ cho các mục đích phi thương mại (trừ khi bạn trả tiền) và bạn không thể lưu trữ dữ liệu (ngoại trừ bộ nhớ đệm tạm thời). Có ý nghĩa. Dữ liệu của Google là một số tốt nhất trên thế giới. Tuy nhiên, Google Maps không xác minh địa chỉ. Nếu một địa chỉ không tồn tại, nó vẫn sẽ cho bạn thấy nơi địa chỉ sẽ được nếu nó đã tồn tại (thử nó trên đường phố của riêng bạn, sử dụng một số nhà mà bạn biết không tồn tại). Điều này đôi khi hữu ích, nhưng hãy lưu ý về điều đó.

Chính sách sử dụng của Nominatim cũng hạn chế tương tự, đặc biệt là sử dụng với số lượng lớn và thương mại và dữ liệu chủ yếu được lấy từ các nguồn miễn phí, do đó, nó không được duy trì tốt (như bản chất của các dự án mở) - tuy nhiên, điều này vẫn có thể phù hợp bạn cần. Nó được hỗ trợ bởi một cộng đồng lớn.

Bản thân USPS có API, nhưng nó đi xuống rất nhiều và không có sự đảm bảo cũng như không hỗ trợ. Nó cũng có thể khó sử dụng. Một số người sử dụng nó một cách tiết kiệm mà không có vấn đề. Nhưng thật dễ để bỏ lỡ rằng USPS yêu cầu bạn chỉ sử dụng API của họ để xác nhận địa chỉ để gửi qua chúng.

Mọi người mong đợi địa chỉ sẽ khó khăn

Thật không may, chúng tôi đã tạo điều kiện cho xã hội của chúng tôi hy vọng địa chỉ sẽ phức tạp. Có hàng tá bài viết về UX hay trên Internet về vấn đề này, nhưng thực tế là, nếu bạn có một mẫu địa chỉ với các trường riêng lẻ, đó là những gì người dùng mong đợi, mặc dù điều đó làm cho các địa chỉ trường hợp cạnh không phù hợp với định dạng biểu mẫu đang mong đợi hoặc có thể biểu mẫu yêu cầu trường không nên. Hoặc người dùng không biết nơi để đặt một phần nhất định trong địa chỉ của họ.

Tôi có thể tiếp tục về các hình thức thanh toán UX tồi tệ hiện nay, nhưng thay vào đó tôi sẽ chỉ nói rằng việc kết hợp các địa chỉ vào một trường sẽ là một thay đổi đáng hoan nghênh - mọi người sẽ có thể nhập địa chỉ của họ theo cách họ thấy phù hợp , thay vì cố gắng tìm ra hình thức dài của bạn. Tuy nhiên, thay đổi này sẽ bất ngờ và người dùng có thể thấy nó hơi chói tai lúc đầu. Chỉ cần nhận thức được điều đó.

Một phần của nỗi đau này có thể được giảm bớt bằng cách đưa lĩnh vực quốc gia ra phía trước, trước địa chỉ. Khi họ điền vào trường quốc gia đầu tiên, bạn biết cách làm cho biểu mẫu của bạn xuất hiện. Có thể bạn có một cách tốt để xử lý các địa chỉ một trường ở Hoa Kỳ, vì vậy nếu họ chọn Hoa Kỳ, bạn có thể giảm biểu mẫu của mình thành một trường duy nhất, nếu không thì hiển thị các trường thành phần. Chỉ là những điều cần suy nghĩ!

Bây giờ chúng tôi biết tại sao nó khó; Bạn có thể làm gì về nó?

USPS cấp phép cho các nhà cung cấp thông qua quy trình gọi là Chứng nhận CASS ™ để cung cấp địa chỉ được xác minh cho khách hàng. Các nhà cung cấp này có quyền truy cập vào cơ sở dữ liệu USPS, được cập nhật hàng tháng. Phần mềm của họ phải tuân thủ các tiêu chuẩn khắt khe để được chứng nhận và họ thường không yêu cầu phải đồng ý với các điều khoản giới hạn như đã thảo luận ở trên.

Có nhiều công ty được chứng nhận CASS có thể xử lý danh sách hoặc có API: Dữ liệu Melissa, QAS Experian và SmartyStreets để đặt tên cho một số.

(Do nhận được thông báo "quảng cáo", tôi đã cắt câu trả lời của mình vào thời điểm này. Tùy thuộc vào bạn để tìm giải pháp phù hợp với bạn.)

Sự thật: Thực sự, mọi người, tôi không làm việc tại bất kỳ công ty nào trong số này. Đây không phải là một quảng cáo.


1
Địa chỉ Nam Mỹ (Uruguay) thì sao? : D
Bart Calix đến

11
@Brian - Có lẽ vì người dùng đã cung cấp rất nhiều thông tin hữu ích cho những người đọc câu hỏi và câu trả lời, bất kể họ có chọn sử dụng sản phẩm của công ty mình hay không.
Zarepheth

7
@Brian Những trang web đó là nội dung phế liệu. Họ đang lẩn tránh nội dung để có được thứ hạng SERP. Tôi chưa bao giờ nhìn thấy chúng trước đây. Tôi chưa bao giờ đăng nội dung này trước hoặc sau bất cứ nơi nào khác.
Matt

2
@khuderm Tôi nhận thấy ngay khi tôi đọc bình luận của bạn rằng tất cả các bình luận không đồng tình đã biến mất; không chắc chắn như thế nào / khi điều đó xảy ra. Nhưng dù sao, hãy xem lịch sử chỉnh sửa câu trả lời của tôi và bạn sẽ tìm thấy tài liệu tham khảo trực tiếp đến trình trích xuất địa chỉ Hoa Kỳ có thể giúp bạn. Tôi đã xây dựng nó khi tôi làm việc ở công việc cuối cùng của mình, nhưng đó là mã độc quyền để tôi không thể chia sẻ nó ... nhưng chúng tồn tại. Hy vọng là hữu ích.
Matt

2
Giáo sư. Xin lỗi @Matt. Vâng, tôi đã bắt đầu theo dõi bạn thông qua các câu hỏi của bạn và Github. Khá ấn tượng bạn là.
Sayka

27

libpostal: một thư viện mã nguồn mở để phân tích địa chỉ, đào tạo với dữ liệu từ OpenStreetMap, OpenAddresses và OpenCage.

https://github.com/openvenues/libpostal ( thông tin thêm về nó )

Các công cụ / dịch vụ khác:


13

Có nhiều trình phân tích địa chỉ đường phố. Chúng có hai hương vị cơ bản - những loại có cơ sở dữ liệu về tên địa danh và tên đường phố, và những loại không có.

Trình phân tích cú pháp địa chỉ đường phố biểu thức chính quy có thể đạt tỷ lệ thành công lên tới 95% mà không gặp nhiều rắc rối. Sau đó, bạn bắt đầu đánh những trường hợp bất thường. Một Perl trong CPAN, "Geo :: StreetAddress :: US", là về điều đó tốt. Có các cổng Python và Javascript, tất cả đều là nguồn mở. Tôi có một phiên bản cải tiến trong Python giúp tăng tỷ lệ thành công lên một chút bằng cách xử lý nhiều trường hợp hơn. Tuy nhiên, để có được 3% quyền cuối cùng, bạn cần có cơ sở dữ liệu để giúp định hướng.

Một cơ sở dữ liệu với mã ZIP gồm 3 chữ số và tên và chữ viết tắt của tiểu bang Hoa Kỳ là một trợ giúp lớn. Khi một trình phân tích cú pháp nhìn thấy một mã bưu chính và tên trạng thái nhất quán, nó có thể bắt đầu khóa vào định dạng. Điều này hoạt động rất tốt cho Hoa Kỳ và Vương quốc Anh.

Phân tích địa chỉ đường phố thích hợp bắt đầu từ cuối và hoạt động ngược. Đó là cách các hệ thống USPS làm điều đó. Địa chỉ ít mơ hồ ở cuối, trong đó tên quốc gia, tên thành phố và mã bưu chính tương đối dễ nhận biết. Tên đường thường có thể được phân lập. Vị trí trên đường phố là phức tạp nhất để phân tích; ở đó bạn bắt gặp những thứ như "Tầng thứ năm" và "Staples Pavillion". Đó là khi một cơ sở dữ liệu là một trợ giúp lớn.


Ngoài ra còn có mô-đun CPAN Lingua: EN :: addressPude. Mặc dù chậm hơn "Geo :: StreetAddress :: US, nhưng nó mang lại tỷ lệ thành công cao hơn.
Kim Ryan

8

CẬP NHẬT: Geocode.xyz hiện hoạt động trên toàn thế giới. Ví dụ, xem https://geocode.xyz

Đối với Hoa Kỳ, Mexico và Canada, xem geocoder.ca .

Ví dụ:

Đầu vào: một cái gì đó đang diễn ra gần giao điểm của chính và arthur giết chết new york

Đầu ra:

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

Bạn cũng có thể kiểm tra kết quả trong giao diện web hoặc nhận đầu ra là Json hoặc Jsonp. ví dụ. Tôi đang tìm nhà hàng quanh 123 Main Street, New York


Làm thế nào bạn thực hiện hệ thống phân tích địa chỉ bằng openaddress? Bạn đang sử dụng chiến lược vũ phu?
Nithin K Anil

1
"Lực lượng vũ phu" nghĩa là gì? Việc chia văn bản thành tất cả các kết hợp có thể có của chuỗi địa chỉ có thể và so sánh từng chuỗi với cơ sở dữ liệu địa chỉ là không thực tế và sẽ mất nhiều thời gian hơn để cung cấp câu trả lời so với hệ thống này. Openaddresses là một trong những nguồn dữ liệu để xây dựng 'tập huấn luyện' các định dạng địa chỉ cho thuật toán. Nó sử dụng thông tin này để phân tích địa chỉ ra khỏi văn bản phi cấu trúc.
Ervin Ruci

2
Một hệ thống tương tự khác là Geo :: libpostal ( perltricks.com/article/announcing-geo--libpostal ) Họ cũng sử dụng openstreetmap và openaddresses, để xây dựng các mẫu địa chỉ một cách nhanh chóng
Ervin Ruci

Tôi vừa thử nghiệm công cụ địa lý của geocode.xyz (gửi văn bản, lấy lại vị trí) trên hàng trăm địa chỉ thực tế. Cho một cánh cùng các API google bản đồ, và một bộ toàn cầu của địa chỉ, geocode.xyz's scantextphương pháp thất bại hầu hết thời gian. Nó luôn chọn "Geneva, US" thay vì "Geneva, Thụy Sĩ" và nói chung là thiên vị Mỹ.
Marc Maxmeister

Nó phụ thuộc vào ngữ cảnh. geocode.xyz/?scantext=Geneva,%20Switzerland sẽ sản xuất: Match Location Geneva, Thụy Sĩ, CH Confidence Điểm: 0.8 trong khi geocode.xyz/?scantext=Geneva,%20USA sẽ tạo Điểm phù hợp Geneva, US Confidence Điểm: 1.0 Ngoài ra, bạn có thể thiên vị khu vực như sau: geocode.xyz/?scantext=Geneva,%20USA®ion=CH
Ervin Ruci

4

Không có mã? Vì xấu hổ!

Đây là một trình phân tích cú pháp địa chỉ JavaScript đơn giản. Thật là khủng khiếp cho mọi lý do duy nhất mà Matt đưa ra trong luận án của mình ở trên (mà tôi gần như 100% đồng ý với: địa chỉ là loại phức tạp và con người mắc lỗi; tốt hơn là thuê ngoài và tự động hóa điều này - khi bạn có đủ khả năng).

Nhưng thay vì khóc, tôi quyết định thử:

Mã này hoạt động tốt để phân tích hầu hết các kết quả Esri chofindAddressCandidatevà cũng với một số trình điều khiển địa lý (đảo ngược) khác trả về địa chỉ một dòng trong đó đường / thành phố / tiểu bang được phân định bằng dấu phẩy. Bạn có thể mở rộng nếu bạn muốn hoặc viết các trình phân tích cú pháp cụ thể theo quốc gia. Hoặc chỉ sử dụng điều này như một trường hợp nghiên cứu về mức độ khó khăn của bài tập này hoặc mức độ tệ hại của tôi ở JavaScript. Tôi thừa nhận tôi chỉ dành khoảng ba mươi phút cho việc này (các lần lặp lại trong tương lai có thể thêm bộ nhớ cache, xác thực zip và tra cứu trạng thái cũng như bối cảnh vị trí người dùng), nhưng nó hoạt động cho trường hợp sử dụng của tôi: Người dùng cuối thấy biểu mẫu phân tích phản hồi tìm kiếm mã địa lý thành 4 hộp văn bản. Nếu phân tích địa chỉ sai (điều này hiếm khi trừ khi dữ liệu nguồn kém) thì đó không phải là vấn đề lớn - người dùng phải xác minh và sửa nó! (Nhưng đối với các giải pháp tự động có thể loại bỏ / bỏ qua hoặc gắn cờ là lỗi để nhà phát triển có thể hỗ trợ định dạng mới hoặc sửa dữ liệu nguồn.)

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


từ chối trách nhiệm: khách hàng của tôi sở hữu dữ liệu địa chỉ của họ và chạy máy chủ Esri của riêng họ. Nếu bạn lấy dữ liệu từ google, OSM, ArcGisOnline hoặc bất cứ nơi nào, hãy đảm bảo rằng bạn có thể lưu trữ và sử dụng nó (nhiều dịch vụ có những hạn chế về cách bạn có thể lưu trữ và trong bao lâu)
cần thiết vào

Câu trả lời đầu tiên ở trên làm cho một trường hợp hấp dẫn rằng vấn đề này không thể giải quyết được với các biểu thức chính quy nếu bạn đang xử lý danh sách địa chỉ toàn cầu. 200 quốc gia có quá nhiều ngoại lệ. Trong thử nghiệm của tôi, bạn có thể xác định quốc gia từ một chuỗi khá đáng tin cậy, sau đó tìm kiếm một biểu thức chính quy cụ thể cho từng quốc gia - đó có thể là cách các API hoạt động tốt hơn.
Marc Maxmeister


2

Một tùy chọn khác cho các địa chỉ có trụ sở tại Hoa Kỳ là YAddress (được thực hiện bởi công ty tôi làm việc).

Nhiều câu trả lời cho câu hỏi này đề xuất các công cụ mã hóa địa lý như một giải pháp. Điều quan trọng là không nhầm lẫn phân tích địa chỉ và mã hóa địa lý; Chúng không giống nhau. Mặc dù bộ mã hóa địa lý có thể chia một địa chỉ thành các thành phần như một lợi ích phụ, chúng thường dựa vào các bộ địa chỉ không chuẩn. Điều này có nghĩa là một địa chỉ được phân tích cú pháp địa lý có thể không giống với địa chỉ chính thức. Ví dụ: cái mà API mã hóa địa lý của Google gọi là "Đại lộ số 6" ở Manhattan, USPS gọi là "Đại lộ châu Mỹ".


2

Đối với phân tích địa chỉ Hoa Kỳ,

Tôi thích sử dụng gói usaddress chỉ có sẵn trong pip cho usaddress

python3 -m pip install usaddress

Tài liệu
PyPi

Điều này làm việc tốt cho tôi cho địa chỉ Hoa Kỳ.

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

Chạy 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

Trong một dự án của chúng tôi, chúng tôi đã sử dụng trình phân tích cú pháp địa chỉ sau đây. Nó phân tích địa chỉ cho hầu hết các quốc gia trên thế giới với độ chính xác tốt.

http://address-parser.net/

Nó có sẵn dưới dạng thư viện độc lập hoặc dưới dạng API trực tiếp.


1
Nhưng đó là một trả tiền cho sản phẩm.
Jeremy Thompson

0

Tôi đến bữa tiệc muộn, đây là một kịch bản VBA Excel tôi đã viết cách đây nhiều năm cho Úc. Nó có thể dễ dàng sửa đổi để hỗ trợ các quốc gia khác. Tôi đã tạo một kho lưu trữ GitHub của mã C # tại đây. Tôi đã lưu trữ nó trên trang web của tôi và bạn có thể tải xuống ở đây: http://jeremythndry.net/rocks/PudeAddress.xlsm

Chiến lược

Đối với bất kỳ quốc gia nào có Mã bưu điện là số hoặc có thể được khớp với RegEx, chiến lược của tôi hoạt động rất tốt:

  1. Đầu tiên, chúng tôi phát hiện Tên và Họ được coi là dòng trên cùng. Thật dễ dàng để bỏ qua tên và bắt đầu với địa chỉ bằng cách bỏ chọn hộp kiểm (được gọi là 'Tên là hàng trên cùng' như được hiển thị bên dưới).

  2. Tiếp theo, an toàn để mong đợi Địa chỉ bao gồm Đường và Số đến trước Vùng ngoại ô và St, Pde, Ave, Av, Rd, Cres, loop, v.v. là một dải phân cách.

  3. Phát hiện Suburb vs State và thậm chí cả Quốc gia có thể lừa những người phân tích cú pháp tinh vi nhất vì có thể có xung đột. Để khắc phục điều này, tôi sử dụng tra cứu PostCode dựa trên thực tế là sau khi tước số Đường và Căn hộ / Đơn vị cũng như PoBox, Ph, Fax , Mobile, v.v., chỉ còn lại số PostCode. Điều này rất dễ khớp với regEx để sau đó tra cứu vùng ngoại ô và quốc gia.

Dịch vụ Bưu điện Quốc gia của bạn sẽ cung cấp danh sách mã bưu điện với Vùng ngoại ô và Tiểu bang miễn phí mà bạn có thể lưu trữ trong một bảng excel, bảng db, tệp văn bản / json / xml, v.v.

  1. Cuối cùng, vì một số Mã bưu điện có nhiều Vùng ngoại ô, chúng tôi kiểm tra vùng ngoại ô nào xuất hiện trong Địa chỉ.

Thí dụ

nhập mô tả hình ảnh ở đây

Mã VBA

TUYÊN BỐ TỪ CHỐI, tôi biết mã này không hoàn hảo hoặc thậm chí được viết tốt tuy nhiên rất dễ chuyển đổi sang bất kỳ ngôn ngữ lập trình nào và chạy trong bất kỳ loại ứng dụng nào. Chiến lược là câu trả lời tùy thuộc vào quốc gia và quy tắc của bạn, lấy mã này làm ví dụ :

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.