Trường Hợp Sử Dụng

Xử lý CAPTCHA để trích xuất dữ liệu bản đồ và GIS

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

Os comentários estão desativados para este artigo.