حالات الاستخدام

التعامل مع اختبار CAPTCHA لنظم المعلومات الجغرافية واستخراج بيانات الخرائط

تعمل بوابات نظم المعلومات الجغرافية الحكومية وأنظمة تقييم المقاطعات ومنصات رسم الخرائط على حماية الاستعلامات الجغرافية المكانية باستخدام الصور والتعرف الضوئي على الحروف 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 محافظ يبدأ بطلبات فردية موثقة ثم يتوسع تدريجيًا إلى دفعات أكبر من دون التضحية بجلسات البوابة أو دقة النتائج.


الخطوات التالية

أدلة ذات صلة

التعليقات غير مفعّلة لهذا المقال.