Tutorials

Xây dựng Trình tổng hợp danh sách việc làm với CaptchaAI

Quét danh sách công việc từ nhiều bảng, xử lý các thách thức CAPTCHA bằng CaptchaAI, chuẩn hóa dữ liệu và lưu trữ dữ liệu để tìm kiếm và phân tích.


Kiến trúc

[Job Board A] ──┐
[Job Board B] ──┼──> Scraper + CAPTCHA Solver ──> Normalizer ──> SQLite DB
[Job Board C] ──┘

Mô hình dữ liệu công việc

# models.py
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import sqlite3
import json

@dataclass
class JobListing:
    title: str
    company: str
    location: str
    url: str
    source: str
    salary_min: Optional[float] = None
    salary_max: Optional[float] = None
    posted_date: Optional[str] = None
    description: str = ""
    tags: list = field(default_factory=list)
    scraped_at: str = field(default_factory=lambda: datetime.now().isoformat())

class JobDatabase:
    def __init__(self, db_path="jobs.db"):
        self.conn = sqlite3.connect(db_path)
        self._create_table()

    def _create_table(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS jobs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                company TEXT NOT NULL,
                location TEXT,
                url TEXT UNIQUE,
                source TEXT,
                salary_min REAL,
                salary_max REAL,
                posted_date TEXT,
                description TEXT,
                tags TEXT,
                scraped_at TEXT
            )
        """)
        self.conn.commit()

    def insert(self, job: JobListing):
        try:
            self.conn.execute(
                """INSERT OR IGNORE INTO jobs
                   (title, company, location, url, source,
                    salary_min, salary_max, posted_date,
                    description, tags, scraped_at)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                (job.title, job.company, job.location, job.url,
                 job.source, job.salary_min, job.salary_max,
                 job.posted_date, job.description,
                 json.dumps(job.tags), job.scraped_at),
            )
            self.conn.commit()
        except sqlite3.IntegrityError:
            pass  # Duplicate URL

    def search(self, keyword, location=None):
        query = "SELECT * FROM jobs WHERE title LIKE ?"
        params = [f"%{keyword}%"]
        if location:
            query += " AND location LIKE ?"
            params.append(f"%{location}%")
        query += " ORDER BY scraped_at DESC"
        cursor = self.conn.execute(query, params)
        return cursor.fetchall()

Cơ sở quét CAPTCHA-Aware

# scraper_base.py
import requests
import re
import time
import os

class BaseScraper:
    API_KEY = os.environ["CAPTCHAAI_API_KEY"]

    def __init__(self, source_name):
        self.source = source_name
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                          "AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36",
        })

    def fetch(self, url):
        resp = self.session.get(url, timeout=20)

        if self._has_captcha(resp.text):
            token = self._solve_captcha(url, resp.text)
            resp = self.session.post(url, data={
                "g-recaptcha-response": token,
            }, timeout=30)

        return resp.text

    def _has_captcha(self, html):
        return "data-sitekey" in html or "g-recaptcha" in html

    def _solve_captcha(self, url, html):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if not match:
            raise ValueError("No sitekey found")

        sitekey = match.group(1)

        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.API_KEY,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": url,
            "json": 1,
        }, timeout=30)
        task_id = resp.json()["request"]
        time.sleep(15)

        for _ in range(24):
            resp = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.API_KEY, "action": "get",
                "id": task_id, "json": 1,
            }, timeout=15)
            data = resp.json()
            if data.get("status") == 1:
                return data["request"]
            if data["request"] != "CAPCHA_NOT_READY":
                raise RuntimeError(data["request"])
            time.sleep(5)

        raise TimeoutError("CAPTCHA solve timeout")

Máy cạo bảng công việc

# scrapers.py
from bs4 import BeautifulSoup
from scraper_base import BaseScraper
from models import JobListing
import re

class GenericJobScraper(BaseScraper):
    """Scrape a job board search results page."""

    def __init__(self, source_name, base_url, selectors):
        super().__init__(source_name)
        self.base_url = base_url
        self.selectors = selectors

    def scrape_search(self, keyword, location="", max_pages=3):
        jobs = []

        for page in range(1, max_pages + 1):
            url = self.base_url.format(
                keyword=keyword.replace(" ", "+"),
                location=location.replace(" ", "+"),
                page=page,
            )
            html = self.fetch(url)
            page_jobs = self._parse_listings(html)

            if not page_jobs:
                break
            jobs.extend(page_jobs)

        return jobs

    def _parse_listings(self, html):
        soup = BeautifulSoup(html, "html.parser")
        cards = soup.select(self.selectors["card"])
        jobs = []

        for card in cards:
            title_el = card.select_one(self.selectors["title"])
            company_el = card.select_one(self.selectors["company"])
            location_el = card.select_one(self.selectors.get("location", ".location"))
            link_el = card.select_one(self.selectors.get("link", "a"))

            if not title_el or not company_el:
                continue

            salary = self._extract_salary(card.get_text())

            jobs.append(JobListing(
                title=title_el.get_text(strip=True),
                company=company_el.get_text(strip=True),
                location=location_el.get_text(strip=True) if location_el else "",
                url=link_el["href"] if link_el else "",
                source=self.source,
                salary_min=salary[0],
                salary_max=salary[1],
            ))

        return jobs

    def _extract_salary(self, text):
        match = re.search(
            r'\$?([\d,]+)\s*[-–to]+\s*\$?([\d,]+)', text
        )
        if match:
            return (
                float(match.group(1).replace(",", "")),
                float(match.group(2).replace(",", "")),
            )
        return (None, None)

Á quân

# main.py
import time
from models import JobDatabase
from scrapers import GenericJobScraper

BOARDS = [
    {
        "name": "Board A",
        "base_url": "https://board-a.example.com/search?q={keyword}&l={location}&p={page}",
        "selectors": {
            "card": ".job-card",
            "title": ".job-title",
            "company": ".company-name",
            "location": ".job-location",
            "link": "a.job-link",
        },
    },
]

def main():
    db = JobDatabase()
    keywords = ["python developer", "data engineer"]

    for board in BOARDS:
        scraper = GenericJobScraper(board["name"], board["base_url"], board["selectors"])

        for keyword in keywords:
            print(f"Scraping {board['name']} for '{keyword}'...")
            jobs = scraper.scrape_search(keyword, location="Remote")

            for job in jobs:
                db.insert(job)
                print(f"  {job.title} at {job.company}")

            time.sleep(5)

    # Search example
    results = db.search("python", "Remote")
    print(f"\nFound {len(results)} matching jobs")

if __name__ == "__main__":
    main()

Khắc phục sự cố

Vấn đề Nguyên nhân Cách xử lý
Danh sách trùng lặp Cùng một công việc trên nhiều trang Khấu trừ dựa trên URL thông qua ràng buộc UNIQUE
Trích xuất lương không thành công Định dạng không chuẩn Tùy chỉnh biểu thức chính quy _extract_salary trên mỗi bảng
CAPTCHA trên mỗi trang Phiên không được duy trì Tái sử dụng self.session theo yêu cầu
Danh sách trống sau khi giải quyết Biểu mẫu CAPTCHA cần JS Chuyển sang Selenium + CaptchaAI

Câu hỏi thường gặp

Làm cách nào để xử lý phân trang?

Scraper lặp qua các trang 1 tới max_pages. Nếu một trang không trả về thẻ công việc, nó sẽ dừng sớm.

Tôi có thể thêm bảng công việc mới một cách dễ dàng không?

Vâng. Thêm mục nhập mới vào BOARDS bằng mẫu URL và bộ chọn CSS của bảng.

Làm cách nào để tránh bị chặn?

Yêu cầu giới hạn tỷ lệ với time.sleep(), xoay vòng tác nhân người dùng và giữ cho các phiên nhất quán.


Hướng dẫn liên quan


Dữ liệu công việc tổng hợp —bắt đầu với CaptchaAI.

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

Postagens relacionadas

Troubleshooting CAPTCHA tự động hóa trình duyệt không thành công nhưng API vẫn hoạt động: Hướng dẫn gỡ lỗi
Hướng dẫn thực hành về trình duyệt tự động hóa CAPTCHA không thành công nhưng API vẫn hoạt động: Hướng dẫn gỡ lỗi, với các nguyên nhân phổ biến, các bước chẩn đ...

Hướng dẫn thực hành về trình duyệt tự động hóa CAPTCHA không thành công nhưng API vẫn hoạt động: Hướng dẫn gỡ...

Apr 29, 2026
Tutorials Xây dựng quy trình thử nghiệm tự động với CaptchaAI
Hướng dẫn từng bước để xây dựng quy trình thử nghiệm tự động với Captcha AI, với các ví dụ có thể sử dụng lại trực tiếp và quy trình làm việc Captcha AI rõ ràng...

Hướng dẫn từng bước để xây dựng quy trình thử nghiệm tự động với Captcha AI, với các ví dụ có thể sử dụng lại...

Apr 30, 2026
Reference Tính bền vững phiên trình duyệt cho luồng QA CAPTCHA của bạn
Duy trì phiên trình duyệt qua nhiều bước trong kiểm thử QA CAPTCHA trên staging của bạn để giảm gián đoạn và tăng độ tái lập.

Duy trì phiên trình duyệt qua nhiều bước trong kiểm thử QA CAPTCHA trên staging của bạn để giảm gián đoạn và t...

Apr 30, 2026