تعمل بوابات نظم المعلومات الجغرافية الحكومية وأنظمة تقييم المقاطعات ومنصات رسم الخرائط على حماية الاستعلامات الجغرافية المكانية باستخدام الصور والتعرف الضوئي على الحروف CAPTCHA. تخدم هذه البوابات حدود قطع الأراضي، وتخصيص المناطق، ومناطق الفيضانات، وتقييمات الممتلكات - وهي بيانات ذات قيمة لتحليل العقارات، والتخطيط الحضري، والبحوث البيئية. وإليك كيفية التعامل مع اختبارات CAPTCHA.
عمليًا، تختلف وتيرة الاستخراج بين بوابة استعلام فردية وأرشيف خرائط أو بوابة تنزيل بيانات. لذلك من الأفضل أن تبدأ بحالات استخدام واضحة مثل: lookup لقطعة أرض واحدة، أو دفعات صغيرة لعناوين محددة، أو مسح جغرافي تدريجي مع حدود معدل محافظة حتى لا تتحول عملية الجمع نفسها إلى سبب دائم لظهور CAPTCHA.
أنماط CAPTCHA على بوابات نظم المعلومات الجغرافية
| نوع البوابة | نوع التحقق | المحفّز |
|---|---|---|
| مقاطعة GIS/assessor | صورة نص CAPTCHA | استعلامات البحث عن الطرود |
| البوابات الجغرافية المكانية للدولة | اختبار CAPTCHA المخصص | طلبات تنزيل البيانات |
| بوابات بيانات هيئة المسح الجيولوجي الأمريكية | reCAPTCHA v2 | الوصول إلى البيانات بالجملة |
| خرائط تقسيم البلديات | صورة التحقق | عمليات البحث المتكررة عن الممتلكات |
| قواعد البيانات البيئية | الرياضيات كابتشا | توليد التقرير |
| بحث منطقة الفيضانات | صورة نص CAPTCHA | استعلامات العنوان |
مستخرج بيانات نظم المعلومات الجغرافية
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_of(*selectors):
for selector in selectors:
element = soup.select_one(selector)
if element:
return element.get_text(strip=True)
return ""
return {
"parcel_id": text_of(".parcel-id", "#parcelId"),
"owner": text_of(".owner", ".owner-name"),
"address": text_of(".address", ".situs"),
"zoning": text_of(".zoning", ".zone-code"),
"acreage": text_of(".acreage", ".area"),
"assessed_value": text_of(".assessed", ".value"),
"land_use": text_of(".land-use", ".use-code"),
}
def _parse_search_results(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
results = []
for row in soup.select(".result-row, tr.parcel"):
def row_text(*selectors):
for selector in selectors:
element = row.select_one(selector)
if element:
return element.get_text(strip=True)
return ""
results.append({
"parcel_id": row_text(".parcel-id"),
"address": row_text(".address"),
"owner": row_text(".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"]
)
الاستخراج القائم على الإحداثيات (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
});
معلمات CAPTCHA لبوابات GIS
| المعلمة | القيمة | حالة الاستخدام |
|---|---|---|
method |
base64 |
الصورة القياسية CAPTCHA |
numeric |
1 |
اختبارات CAPTCHA الرقمية فقط |
min_len |
4 |
عند معرفة عدد الأحرف |
max_len |
6 |
عند معرفة عدد الأحرف |
language |
0 |
أحرف /Latin الإنجليزية |
textinstructions |
مخصص | Math CAPTCHAs أو الرموز المنسقة |
استكشاف الأخطاء وإصلاحها
| المشكلة | السبب | الإجراء |
|---|---|---|
| تحميل صورة CAPTCHA معطل | مطلوب ملف تعريف الارتباط للجلسة | قم بتحميل صفحة البحث أولاً |
| تم رفض النص الذي تم حله | حساسية الحالة | إضافة معلمة case_sensitive=1 |
| تقوم البوابة بإرجاع اختبار CAPTCHA مختلفًا | اختبار CAPTCHA الخاص بالجلسة | قم بالتنزيل والحل في نفس الجلسة |
| لا توجد بيانات الطرود بعد اختبار CAPTCHA | حقول النموذج المخفية مفقودة | قم باستخراج جميع المدخلات المخفية قبل الإرسال |
الأسئلة الشائعة
لماذا تستخدم بوابات نظم المعلومات الجغرافية اختبارات CAPTCHA للصور ذات النمط القديم؟
غالبًا ما يتم إنشاء أنظمة نظم المعلومات الجغرافية الحكومية على منصات قديمة تسبق خدمات CAPTCHA الحديثة. قيود الميزانية ودورات الشراء الطويلة تعني استمرار اختبارات CAPTCHA القديمة.
كيف يمكنني التعامل مع تنسيقات اختبار CAPTCHA الخاصة بالمقاطعة؟
يجوز لكل مقاطعة استخدام تطبيقات CAPTCHA مختلفة. استخدم معلمة CaptchaAI textinstructions لوصف التنسيق المحدد - على سبيل المثال، "5 أحرف كبيرة" أو "حل المعادلة الرياضية".
هل يمكنني استخراج بيانات الشكل أو GeoJSON خلف اختبارات CAPTCHA؟
إذا كانت البوابة توفر بيانات مكانية قابلة للتنزيل خلف اختبار CAPTCHA، فقم بحل اختبار CAPTCHA للوصول إلى رابط التنزيل. CaptchaAI يتعامل مع اختبار CAPTCHA؛ ثم قم بتحميل الملف بشكل عادي
يمكنك استخدام CaptchaAI لبناء مسار جمع GIS محافظ يبدأ بطلبات فردية موثقة ثم يتوسع تدريجيًا إلى دفعات أكبر من دون التضحية بجلسات البوابة أو دقة النتائج.
الخطوات التالية
- البدء السريع مع CaptchaAI: حلّ أول كابتشا في 5 دقائق
- كيفية حلّ reCAPTCHA v2 عبر الـ API: دليل خطوة بخطوة
- كيفية حل Cloudflare Turnstile باستخدام واجهة API
- كيفية حل GeeTest v3 باستخدام API