Cổng GIS của chính phủ, hệ thống đánh giá quận và nền tảng lập bản đồ bảo vệ các truy vấn không gian địa lý bằng CAPTCHA hình ảnh và OCR. Các cổng này phục vụ ranh giới lô đất, chỉ định phân vùng, vùng lũ lụt và đánh giá tài sản — dữ liệu có giá trị cho phân tích bất động sản, quy hoạch đô thị và nghiên cứu môi trường. Dưới đây là cách xử lý CAPTCHA.
Các mẫu CAPTCHA trên Cổng thông tin GIS
| Loại cổng thông tin | loại CAPTCHA | Trình kích hoạt |
|---|---|---|
| Quận GIS/assessor | Văn bản hình ảnh CAPTCHA | Truy vấn tìm kiếm bưu kiện |
| Cổng thông tin không gian địa lý nhà nước | CAPTCHA tùy chỉnh | Yêu cầu tải xuống dữ liệu |
| Cổng dữ liệu USGS | reCAPTCHA v2 | Truy cập dữ liệu hàng loạt |
| Bản đồ phân vùng thành phố | CAPTCHA hình ảnh | Tra cứu thuộc tính lặp đi lặp lại |
| Cơ sở dữ liệu môi trường | CAPTCHA toán học | Tạo báo cáo |
| Tra cứu vùng lũ lụt | Văn bản hình ảnh CAPTCHA | Truy vấn địa chỉ |
Trình trích xuất dữ liệu GIS
import requests
import base64
import time
import re
class GISDataExtractor:
def __init__(self, api_key):
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
def lookup_parcel(self, portal_url, parcel_id):
"""Look up parcel data by ID, solving CAPTCHAs as needed."""
response = self.session.get(
f"{portal_url}/parcel", params={"id": parcel_id}
)
if self._has_image_captcha(response.text):
captcha_url = self._extract_captcha_url(response.text, portal_url)
captcha_text = self._solve_captcha(captcha_url)
# Re-submit with solved CAPTCHA
response = self.session.post(f"{portal_url}/parcel", data={
"id": parcel_id,
"captcha": captcha_text,
**self._extract_hidden_fields(response.text)
})
return self._parse_parcel_data(response.text)
def search_by_address(self, portal_url, address):
"""Search GIS records by street address."""
response = self.session.get(
f"{portal_url}/search", params={"address": address}
)
if self._has_image_captcha(response.text):
captcha_url = self._extract_captcha_url(response.text, portal_url)
captcha_text = self._solve_captcha(captcha_url)
response = self.session.post(f"{portal_url}/search", data={
"address": address,
"captcha": captcha_text,
**self._extract_hidden_fields(response.text)
})
return self._parse_search_results(response.text)
def bulk_extract(self, portal_url, parcel_ids, delay=3):
"""Extract data for multiple parcels with rate limiting."""
results = {}
for parcel_id in parcel_ids:
try:
results[parcel_id] = self.lookup_parcel(portal_url, parcel_id)
except Exception as e:
results[parcel_id] = {"error": str(e)}
time.sleep(delay)
return results
def _has_image_captcha(self, html):
return bool(re.search(
r'captcha|verification.?image|security.?code',
html, re.IGNORECASE
))
def _extract_captcha_url(self, html, base_url):
from bs4 import BeautifulSoup
from urllib.parse import urljoin
soup = BeautifulSoup(html, "html.parser")
img = (
soup.find("img", attrs={"src": lambda s: s and "captcha" in s.lower()}) or
soup.find("img", {"id": re.compile(r"captcha", re.I)}) or
soup.find("img", {"class": re.compile(r"captcha", re.I)})
)
if img and img.get("src"):
return urljoin(base_url, img["src"])
raise ValueError("CAPTCHA image not found")
def _solve_captcha(self, captcha_url):
"""Download and solve image CAPTCHA."""
img_response = self.session.get(captcha_url)
img_base64 = base64.b64encode(img_response.content).decode("utf-8")
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": self.api_key,
"method": "base64",
"body": img_base64,
"json": 1
})
task_id = resp.json()["request"]
for _ in range(30):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"]
raise TimeoutError("CAPTCHA solve timed out")
def _extract_hidden_fields(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
fields = {}
for inp in soup.select("input[type='hidden']"):
name = inp.get("name")
if name:
fields[name] = inp.get("value", "")
return fields
def _parse_parcel_data(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
def text_or_none(node):
return node.text.strip() if node and node.text else None
return {
"parcel_id": text_or_none(soup.select_one(".parcel-id, #parcelId")),
"owner": text_or_none(soup.select_one(".owner, .owner-name")),
"address": text_or_none(soup.select_one(".address, .situs")),
"zoning": text_or_none(soup.select_one(".zoning, .zone-code")),
"acreage": text_or_none(soup.select_one(".acreage, .area")),
"assessed_value": text_or_none(soup.select_one(".assessed, .value")),
"land_use": text_or_none(soup.select_one(".land-use, .use-code"))
}
def _parse_search_results(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
def text_or_none(node):
return node.text.strip() if node and node.text else None
results = []
for row in soup.select(".result-row, tr.parcel"):
results.append({
"parcel_id": text_or_none(row.select_one(".parcel-id")),
"address": text_or_none(row.select_one(".address")),
"owner": text_or_none(row.select_one(".owner"))
})
return results
# Usage
extractor = GISDataExtractor("YOUR_API_KEY")
# Single parcel lookup
parcel = extractor.lookup_parcel(
"https://gis.county.example.gov",
"12-34-567-890"
)
print(f"Owner: {parcel['owner']}, Zoning: {parcel['zoning']}")
# Bulk extraction
parcels = extractor.bulk_extract(
"https://gis.county.example.gov",
["12-34-567-890", "12-34-567-891", "12-34-567-892"]
)
Trích xuất dựa trên tọa độ (JavaScript)
class GISExtractor {
constructor(apiKey) {
this.apiKey = apiKey;
}
async extractByCoordinates(portalUrl, lat, lng) {
const url = `${portalUrl}/identify?lat=${lat}&lng=${lng}`;
const response = await fetch(url);
const html = await response.text();
if (this.hasCaptcha(html)) {
return this.solveAndExtract(portalUrl, html, { lat, lng });
}
return this.parseGISData(html);
}
async extractRegion(portalUrl, bounds, gridSize = 0.01) {
const results = [];
const { north, south, east, west } = bounds;
for (let lat = south; lat <= north; lat += gridSize) {
for (let lng = west; lng <= east; lng += gridSize) {
try {
const data = await this.extractByCoordinates(portalUrl, lat, lng);
if (data.parcelId) results.push(data);
} catch (error) {
console.error(`Failed at ${lat},${lng}: ${error.message}`);
}
// Rate limit
await new Promise(r => setTimeout(r, 2000));
}
}
return results;
}
hasCaptcha(html) {
return /captcha|verification.?image|security.?code/i.test(html);
}
async solveAndExtract(portalUrl, html, params) {
const imgMatch = html.match(/src="([^"]*captcha[^"]*)"/i);
if (!imgMatch) throw new Error('CAPTCHA image not found');
const imgUrl = new URL(imgMatch[1], portalUrl).href;
const imgResp = await fetch(imgUrl);
const buffer = await imgResp.arrayBuffer();
const base64 = Buffer.from(buffer).toString('base64');
const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
method: 'POST',
body: new URLSearchParams({
key: this.apiKey,
method: 'base64',
body: base64,
json: '1'
})
});
const { request: taskId } = await submitResp.json();
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 3000));
const result = await fetch(
`https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
);
const data = await result.json();
if (data.status === 1) {
const response = await fetch(portalUrl, {
method: 'POST',
body: new URLSearchParams({
...params,
captcha: data.request
})
});
return this.parseGISData(await response.text());
}
}
throw new Error('CAPTCHA solve timed out');
}
parseGISData(html) {
return {
parcelId: html.match(/parcel.?id[^>]*>([^<]+)/i)?.[1]?.trim(),
zoning: html.match(/zon(?:e|ing)[^>]*>([^<]+)/i)?.[1]?.trim(),
acreage: html.match(/acreage|area[^>]*>([^<]+)/i)?.[1]?.trim(),
landUse: html.match(/land.?use[^>]*>([^<]+)/i)?.[1]?.trim()
};
}
}
// Usage
const gis = new GISExtractor('YOUR_API_KEY');
// Single coordinate lookup
const data = await gis.extractByCoordinates(
'https://gis.county.example.gov',
34.0522, -118.2437
);
// Extract entire region
const region = await gis.extractRegion('https://gis.county.example.gov', {
north: 34.10, south: 34.00, east: -118.20, west: -118.30
});
Tham số CAPTCHA cho Cổng thông tin GIS
| tham số | Giá trị | Trường hợp sử dụng |
|---|---|---|
method |
base64 |
CAPTCHA hình ảnh chuẩn |
numeric |
1 |
CAPTCHA chỉ có số |
min_len |
4 |
Khi biết số lượng ký tự |
max_len |
6 |
Khi biết số lượng ký tự |
language |
0 |
Tiếng Anh/Latin ký tự |
textinstructions |
tùy chỉnh | CAPTCHA toán học hoặc mã được định dạng |
Danh sách kiểm tra trích xuất trước đợt
- Xác minh chế độ xem bản đồ, bộ lọc vùng và điều khiển phân trang trước khi bắt đầu chạy bộ sưu tập lớn.
- Lưu trữ tải trọng tọa độ đã chuẩn hóa và phản hồi mục tiêu thô để các lỗi trích xuất vẫn có thể sửa được.
- Tạm dừng lô khi mật độ CAPTCHA tăng đột biến thay vì để các lần thử lại ẩn thay đổi hành vi phía mục tiêu.
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Tải hình ảnh CAPTCHA bị hỏng | Cần có cookie phiên | Tải trang tìm kiếm đầu tiên |
| Đã giải quyết văn bản bị từ chối | Phân biệt chữ hoa chữ thường | Thêm tham số case_sensitive=1 |
| Cổng trả về CAPTCHA khác nhau | CAPTCHA dành riêng cho phiên | Tải xuống và giải quyết trong cùng một phiên |
| Không có dữ liệu bưu kiện sau CAPTCHA | Thiếu trường biểu mẫu ẩn | Trích xuất tất cả các đầu vào bị ẩn trước khi gửi |
Câu hỏi thường gặp
Tại sao các cổng GIS sử dụng CAPTCHA hình ảnh kiểu cũ?
Các hệ thống GIS của chính phủ thường được xây dựng trên các nền tảng cũ có trước các dịch vụ CAPTCHA hiện đại. Hạn chế về ngân sách và chu kỳ mua sắm kéo dài có nghĩa là những CAPTCHA cũ này vẫn tồn tại.
Tôi nên xử lý các định dạng CAPTCHA cụ thể theo quận như thế nào?
Mỗi quận có thể sử dụng cách triển khai CAPTCHA khác nhau. Sử dụng tham số textinstructions của CaptchaAI để mô tả định dạng cụ thể — ví dụ: "5 chữ cái viết hoa" hoặc "giải phương trình toán học".
Tôi có thể trích xuất dữ liệu Shapefile hoặc GeoJSON đằng sau CAPTCHA không?
Nếu cổng cung cấp dữ liệu không gian có thể tải xuống đằng sau CAPTCHA, hãy giải CAPTCHA để truy cập liên kết tải xuống. CaptchaAI xử lý CAPTCHA; sau đó tải file bình thường.
Các bước tiếp theo
Trích xuất dữ liệu GIS một cách đáng tin cậy —lấy khóa API CaptchaAI của bạnvà tự động xử lý CAPTCHA của cổng thông tin chính phủ.
Bước tiếp theo
- CaptchaAI Quickstart: Lần Giải CAPTCHA Đầu Tiên Của Bạn Trong 5 Phút
- Cách Giải reCAPTCHA v2 Bằng API: Hướng Dẫn Từng Bước
- Cách giải Cloudflare Turnstile bằng API
- Cách giải quyết GeeTest v3 bằng API