最近发现了一个很有意思的开源项目:Real US Address Generator。它可以生成包含真实街道、邮编、电话号码和对应地图定位的美国/加拿大身份信息。
很多做跨境电商、海淘或需要注册海外服务的朋友,经常需要寻找美国真实地址生成器。市面上的工具大多广告满天飞,或者需要复杂的服务器部署。
这个开源项目 原本基于 Cloudflare Workers,经过修改后,现在可以直接嵌入到 Blogger、Hexo 或 Hugo 等静态博客中,完全免费且无广告。
| 美国和加拿大真实地址生成器效果图 |
通常这类工具需要服务器支持,原项目也是设计部署在 Cloudflare Workers 上的。但我平时主要使用 Blogger,能不能把它“移植”进我的博客页面里,直接用纯前端运行呢?
经过一番折腾和代码重构,我成功实现了!今天就把这个过程和可以直接使用的代码分享给大家。
项目原理解析
原项目地址:chatgptuk/real-us-address-generator
原项目的逻辑主要在 worker.js 中。它的工作原理是:
-
坐标数据:内置了美国各州和加拿大各省的经纬度范围。
-
API 查询:通过 OpenStreetMap 的 Nominatim API,根据坐标反向查询真实的街道地址。
-
数据生成:随机生成匹配的电话区号、姓名和性别。
遇到的问题:
原代码是为服务端(Server-side)设计的,使用了 addEventListener(‘fetch’) 等浏览器无法识别的指令,且依赖服务端请求来避免跨域问题。如果直接把代码复制到 Blogger,是无法运行的。
解决方案:
我将服务端代码完全重写为浏览器可运行的 JavaScript。
-
移除了 Cloudflare 特有的监听器。
-
将 API 请求改为浏览器原生的
fetch。 -
将所有坐标数据和电话区号数据直接内嵌到 HTML 中,无需加载外部文件。
-
增加了兜底逻辑:如果 API 请求频繁受限,会自动降级显示大致坐标地址,保证页面不报错。
部署步骤
只需要简单三步,你就能拥有一个属于自己的地址生成器页面。
第一步:新建页面
登录 Blogger 后台,点击左侧菜单的 “页面” (Pages) -> “新建页面” (New Page)。给页面起个标题,比如“真实地址生成器”。
| Blogger 后台新建页面设置界面 |
第二步:切换 HTML 视图
在编辑器左上角找到铅笔图标,点击下拉菜单选择 “HTML 视图” (HTML view)。清空里面原有的所有内容。
第三步:粘贴代码
将下方我修改适配后的完整代码复制并粘贴进去。
我已经添加好了。
(注:这段代码包含了 CSS 样式、HTML 结构和核心 JS 逻辑,直接复制即可使用)
HTML
<style>
.gen-container {
font-family: Arial, sans-serif;
text-align: center;
background: white;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
margin: 20px auto;
box-sizing: border-box;
border-radius: 8px;
position: relative;
}
.gen-item {
font-size: 1.2em;
margin-bottom: 12px;
padding: 10px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
word-break: break-all;
}
.gen-item:hover {
background-color: #e2e6ea;
}
.gen-item strong {
display: block;
font-size: 0.8em;
color: #666;
margin-bottom: 4px;
}
.refresh-btn {
padding: 12px 24px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
margin-bottom: 20px;
width: 100%;
}
.refresh-btn:hover {
background-color: #0056b3;
}
.refresh-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.state-select {
margin-bottom: 20px;
width: 100%;
padding: 10px;
font-size: 1em;
}
.map-frame {
width: 100%;
height: 300px;
border: 0;
margin-top: 15px;
border-radius: 4px;
}
.copied-toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #28a745;
color: white;
padding: 10px 20px;
border-radius: 20px;
display: none;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
margin-right: 10px;
vertical-align: middle;
display: none;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
<div class="gen-container">
<div id="copy-toast" class="copied-toast">已复制!</div>
<h2>美国/加拿大真实地址生成器</h2>
<select id="state-select" class="state-select" onchange="manualGenerate()">
<option value="">随机州/省 (Random State)</option>
</select>
<div class="gen-item" onclick="copyText(this)">
<strong>姓名 (Name)</strong>
<span id="res-name">Loading...</span>
</div>
<div class="gen-item" onclick="copyText(this)">
<strong>性别 (Gender)</strong>
<span id="res-gender">Loading...</span>
</div>
<div class="gen-item" onclick="copyText(this)">
<strong>电话 (Phone)</strong>
<span id="res-phone">Loading...</span>
</div>
<div class="gen-item" onclick="copyText(this)">
<strong>地址 (Address)</strong>
<span id="res-address">Loading...</span>
</div>
<button id="gen-btn" class="refresh-btn" onclick="manualGenerate()">
<span class="loading-spinner" id="spinner"></span>
生成新地址 (Generate New)
</button>
<iframe id="map-frame" class="map-frame" src=""></iframe>
</div>
<script>
// 此处省略长数据代码,请复制上文提供的完整 Script 代码块
// ... (包含 stateCoordinates, areaCodesUS 等数据的完整 JS)
// 务必使用我在对话中提供的完整版 Script,确保数据完整性
const stateCoordinates = {
// US
"AL":[{lat:32.377716,lng:-86.300568},{lat:33.520661,lng:-86.802490}],"AK":[{lat:61.216583,lng:-149.899597},{lat:58.301598,lng:-134.419998}],"AZ":[{lat:33.448376,lng:-112.074036},{lat:34.048927,lng:-111.093735}],"AR":[{lat:34.746483,lng:-92.289597},{lat:36.082157,lng:-94.171852}],"CA":[{lat:36.778259,lng:-119.417931},{lat:34.052235,lng:-118.243683}],"CO":[{lat:39.739235,lng:-104.990250},{lat:38.833881,lng:-104.821365}],"CT":[{lat:41.763710,lng:-72.685097},{lat:41.308273,lng:-72.927887}],"DE":[{lat:39.739072,lng:-75.539787},{lat:38.774055,lng:-75.139351}],"FL":[{lat:30.332184,lng:-81.655647},{lat:25.761681,lng:-80.191788}],"GA":[{lat:33.749001,lng:-84.387985},{lat:32.083541,lng:-81.099831}],"HI":[{lat:21.306944,lng:-157.858337},{lat:19.896767,lng:-155.582779}],"ID":[{lat:43.615021,lng:-116.202316},{lat:47.677683,lng:-116.780466}],"IL":[{lat:41.878113,lng:-87.629799},{lat:40.633125,lng:-89.398529}],"IN":[{lat:39.768402,lng:-86.158066},{lat:41.593369,lng:-87.346427}],"IA":[{lat:41.586834,lng:-93.625000},{lat:42.500000,lng:-94.166672}],"KS":[{lat:39.099728,lng:-94.578568},{lat:37.687176,lng:-97.330055}],"KY":[{lat:38.252666,lng:-85.758453},{lat:37.839333,lng:-84.270020}],"LA":[{lat:30.695366,lng:-91.187393},{lat:29.951065,lng:-90.071533}],"ME":[{lat:44.310623,lng:-69.779490},{lat:43.661471,lng:-70.255325}],"MD":[{lat:38.978447,lng:-76.492180},{lat:39.290386,lng:-76.612190}],"MA":[{lat:42.360081,lng:-71.058884},{lat:42.313373,lng:-71.057083}],"MI":[{lat:42.732536,lng:-84.555534},{lat:42.331429,lng:-83.045753}],"MN":[{lat:44.953703,lng:-93.089958},{lat:44.977753,lng:-93.265015}],"MS":[{lat:32.298756,lng:-90.184807},{lat:32.366806,lng:-88.703705}],"MO":[{lat:38.576702,lng:-92.173516},{lat:38.627003,lng:-90.199402}],"MT":[{lat:46.878717,lng:-113.996586},{lat:45.783287,lng:-108.500690}],"NE":[{lat:41.256538,lng:-95.934502},{lat:40.813618,lng:-96.702595}],"NV":[{lat:39.163914,lng:-119.767403},{lat:36.114647,lng:-115.172813}],"NH":[{lat:43.208137,lng:-71.538063},{lat:42.995640,lng:-71.454789}],"NJ":[{lat:40.058323,lng:-74.405663},{lat:39.364285,lng:-74.422928}],"NM":[{lat:35.084385,lng:-106.650421},{lat:32.319939,lng:-106.763653}],"NY":[{lat:40.712776,lng:-74.005974},{lat:43.299427,lng:-74.217933}],"NC":[{lat:35.779591,lng:-78.638176},{lat:35.227085,lng:-80.843124}],"ND":[{lat:46.825905,lng:-100.778275},{lat:46.877186,lng:-96.789803}],"OH":[{lat:39.961178,lng:-82.998795},{lat:41.499321,lng:-81.694359}],"OK":[{lat:35.467560,lng:-97.516426},{lat:36.153980,lng:-95.992775}],"OR":[{lat:44.046236,lng:-123.022029},{lat:45.505917,lng:-122.675049}],"PA":[{lat:40.273191,lng:-76.886701},{lat:39.952583,lng:-75.165222}],"RI":[{lat:41.824009,lng:-71.412834},{lat:41.580095,lng:-71.477429}],"SC":[{lat:34.000710,lng:-81.034814},{lat:32.776474,lng:-79.931051}],"SD":[{lat:44.366787,lng:-100.353760},{lat:43.544595,lng:-96.731103}],"TN":[{lat:36.162663,lng:-86.781601},{lat:35.149532,lng:-90.048981}],"TX":[{lat:30.267153,lng:-97.743057},{lat:29.760427,lng:-95.369804}],"UT":[{lat:40.760780,lng:-111.891045},{lat:37.774929,lng:-111.920414}],"VT":[{lat:44.260059,lng:-72.575386},{lat:44.475883,lng:-73.212074}],"VA":[{lat:37.540726,lng:-77.436050},{lat:36.852924,lng:-75.977982}],"WA":[{lat:47.606209,lng:-122.332069},{lat:47.252876,lng:-122.444290}],"WV":[{lat:38.349820,lng:-81.632622},{lat:39.629527,lng:-79.955896}],"WI":[{lat:43.073051,lng:-89.401230},{lat:43.038902,lng:-87.906471}],"WY":[{lat:41.140259,lng:-104.820236},{lat:44.276569,lng:-105.507391}],
// Canada
"AB":[{lat:51.044733,lng:-114.071883},{lat:53.546124,lng:-113.493823}],"BC":[{lat:49.282729,lng:-123.120738},{lat:48.428421,lng:-123.365644}],"MB":[{lat:49.895137,lng:-97.138374},{lat:50.445211,lng:-96.823611}],"NB":[{lat:45.963589,lng:-66.643115},{lat:46.510712,lng:-67.255044}],"NL":[{lat:53.135509,lng:-57.660435},{lat:50.445211,lng:-57.100000}],"NS":[{lat:44.648862,lng:-63.575320},{lat:45.010474,lng:-63.416817}],"ON":[{lat:51.253775,lng:-85.323214},{lat:43.653225,lng:-79.383186}],"PE":[{lat:46.238240,lng:-63.131074},{lat:46.492424,lng:-63.793013}],"QC":[{lat:46.813878,lng:-71.207980},{lat:45.501689,lng:-73.567256}],"SK":[{lat:52.939915,lng:-106.450863},{lat:50.445211,lng:-104.618896}],"NT":[{lat:64.825544,lng:-115.825340},{lat:61.251955,lng:-114.352482}],"NU":[{lat:64.282327,lng:-76.614813},{lat:70.299598,lng:-83.107562}],"YT":[{lat:64.282327,lng:-135.000000},{lat:64.000000,lng:-138.000000}]
};
const areaCodesUS = {"AL":["205","251","256","334","938"],"AK":["907"],"AZ":["480","520","602","623","928"],"AR":["479","501","870"],"CA":["209","213","310","323","408","415","424","510","530","559","562","619","626","650","661","707","714","760","805","818","831","858","909","916","925","949"],"CO":["303","719","720","970"],"CT":["203","475","860","959"],"DE":["302"],"FL":["239","305","321","352","386","407","561","727","754","772","786","813","850","863","904","941","954"],"GA":["229","404","470","478","678","706","762","770","912"],"HI":["808"],"ID":["208","986"],"IL":["217","224","309","312","331","618","630","708","773","779","815","847","872"],"IN":["219","260","317","463","574","765","812","930"],"IA":["319","515","563","641","712"],"KS":["316","620","785","913"],"KY":["270","364","502","606","859"],"LA":["225","318","337","504","985"],"ME":["207"],"MD":["240","301","410","443","667"],"MA":["339","351","413","508","617","774","781","857","978"],"MI":["231","248","269","313","517","586","616","734","810","906","947","989"],"MN":["218","320","507","612","651","763","952"],"MS":["228","601","662","769"],"MO":["314","417","573","636","660","816","975"],"MT":["406"],"NE":["308","402","531"],"NV":["702","725","775"],"NH":["603"],"NJ":["201","551","609","732","848","856","862","908","973"],"NM":["505","575"],"NY":["212","315","332","347","516","518","585","607","631","646","680","716","718","838","845","914","917","929","934"],"NC":["252","336","704","743","828","910","919","980","984"],"ND":["701"],"OH":["216","234","283","330","380","419","440","513","567","614","740","937"],"OK":["405","539","580","918"],"OR":["458","503","541","971"],"PA":["215","267","272","412","484","570","610","717","724","814","835","878"],"RI":["401"],"SC":["803","839","843","854","864"],"SD":["605"],"TN":["423","615","629","731","865","901","931"],"TX":["210","214","254","281","325","346","409","430","432","469","512","682","713","737","806","817","830","832","903","915","936","940","956","972","979"],"UT":["385","435","801"],"VT":["802"],"VA":["276","434","540","571","703","757","804"],"WA":["206","253","360","425","509"],"WV":["304","681"],"WI":["262","414","534","608","715","920"],"WY":["307"]};
const areaCodesCanada = {"AB":["403","587","825"],"BC":["236","250","604","672","778"],"MB":["204","431"],"NB":["506"],"NL":["709"],"NS":["782","902"],"ON":["226","249","289","343","365","416","437","519","548","613","639","647","705","807","905"],"PE":["902"],"QC":["418","438","450","514","579","581","819","873"],"SK":["306","639"],"NT":["867"],"NU":["867"],"YT":["867"]};
function getRandomState() {
const states = Object.keys(stateCoordinates);
return states[Math.floor(Math.random() * states.length)];
}
function getRandomLocationInState(state) {
const coordsArray = stateCoordinates[state];
if (!coordsArray) return { lat: 39.8283, lng: -98.5795 };
const randomCity = coordsArray[Math.floor(Math.random() * coordsArray.length)];
const lat = randomCity.lat + (Math.random() - 0.5) * 0.05;
const lng = randomCity.lng + (Math.random() - 0.5) * 0.05;
return { lat, lng };
}
function getRandomPhoneNumber(country, state) {
let areaCodeList = [];
if (country === 'US') areaCodeList = areaCodesUS[state] || ["000"];
else areaCodeList = areaCodesCanada[state] || ["000"];
const areaCode = areaCodeList[Math.floor(Math.random() * areaCodeList.length)];
const exchangeCode = Math.floor(200 + Math.random() * 700).toString().padStart(3, '0');
const lineNumber = Math.floor(1000 + Math.random() * 9000).toString().padStart(4, '0');
return `(${areaCode}) ${exchangeCode}-${lineNumber}`;
}
function getRandomName() {
const firstNames = ["James", "Mary", "John", "Patricia", "Robert", "Jennifer", "Michael", "Linda", "William", "Elizabeth", "David", "Barbara", "Richard", "Susan", "Joseph", "Jessica", "Thomas", "Sarah", "Charles", "Karen"];
const lastNames = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin"];
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
const gender = ["Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara", "Susan", "Jessica", "Sarah", "Karen"].includes(firstName) ? "Female" : "Male";
return { name: `${firstName} ${lastName}`, gender: gender };
}
function initSelect() {
const select = document.getElementById('state-select');
const allKeys = Object.keys(stateCoordinates).sort();
let html = '<option value="">随机州/省 (Random)</option>';
allKeys.forEach(key => {
const isUS = areaCodesUS[key] !== undefined;
html += `<option value="${key}">${key} - ${isUS ? 'USA' : 'Canada'}</option>`;
});
select.innerHTML = html;
}
async function manualGenerate() {
const btn = document.getElementById('gen-btn');
const spinner = document.getElementById('spinner');
const select = document.getElementById('state-select');
btn.disabled = true;
spinner.style.display = 'inline-block';
let state = select.value || getRandomState();
let location = getRandomLocationInState(state);
let addressData = null;
let country = areaCodesUS[state] ? 'US' : 'CA';
for(let i=0; i<3; i++) {
try {
const apiUrl = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${location.lat}&lon=${location.lng}&zoom=18&addressdetails=1`;
const resp = await fetch(apiUrl);
const data = await resp.json();
if(data && data.address) {
const add = data.address;
const road = add.road || add.pedestrian || add.street;
const house = add.house_number || Math.floor(Math.random() * 2000) + 1;
const city = add.city || add.town || add.village || add.county;
const postcode = add.postcode || "00000";
if(road && city) {
addressData = `${house} ${road}, ${city}, ${state} ${postcode}, ${country === 'US' ? 'United States' : 'Canada'}`;
break;
}
}
} catch(e) {
console.log("Fetch error", e);
}
location = getRandomLocationInState(state);
}
if(!addressData) {
addressData = `Near Coordinates ${location.lat.toFixed(4)}, ${location.lng.toFixed(4)}, ${state}, ${country === 'US' ? 'USA' : 'Canada'}`;
}
const person = getRandomName();
const phone = getRandomPhoneNumber(country, state);
document.getElementById('res-name').innerText = person.name;
document.getElementById('res-gender').innerText = person.gender;
document.getElementById('res-phone').innerText = phone;
document.getElementById('res-address').innerText = addressData;
const mapUrl = `https://www.google.com/maps?q=${encodeURIComponent(addressData)}&output=embed`;
document.getElementById('map-frame').src = mapUrl;
btn.disabled = false;
spinner.style.display = 'none';
}
function copyText(element) {
const text = element.querySelector('span').innerText;
navigator.clipboard.writeText(text).then(() => {
const toast = document.getElementById('copy-toast');
toast.style.display = 'block';
setTimeout(() => toast.style.display = 'none', 2000);
});
}
initSelect();
manualGenerate();
</script>
实现效果
点击发布后,访问你的页面,你应该能看到如下效果:美国和加拿大真实地址生成器
| 美国地址生成器最终效果演示 - 包含地图和详细信息 |
-
功能展示:支持下拉选择特定的州(如 NY, CA),点击“生成新地址”按钮即可获取最新信息。
-
交互体验:点击具体的姓名、地址、电话等信息,会自动复制到剪贴板,使用非常方便。
-
地图集成:下方内嵌了 Google Maps,直观展示生成的地址位置。
总结
通过这次改造,我们将原本依赖 Cloudflare Workers 的服务转换成了纯静态页面。这样做的好处是显而易见的:
-
零成本:不需要额外部署服务器或配置 Workers 路由。
-
速度快:直接利用浏览器进行 API 请求。
-
易维护:所有代码都在一个 HTML 块中,随用随改。
如果你也在寻找类似的解决方案,不妨试试上面的代码!有任何问题欢迎留言讨论。
注意:免责声明,生成器仅供学习和研究使用,切勿用于非法用途。