Cách tôi scrape Craigslist bằng Python mà không bị chặn

Cập nhật lần cuối vào April 16, 2026
Cách tôi scrape Craigslist bằng Python mà không bị chặn

Craigslist hiện vẫn thu hút khoảng 108 triệu lượt truy cập mỗi tháng trên khoảng 700 website địa phương — nhưng vẫn chưa có API công khai. Nếu bạn muốn lấy dữ liệu có cấu trúc từ tin thuê căn hộ, xe cũ, bài đăng việc làm hay tin rao dịch vụ, thì scraping gần như là lựa chọn duy nhất.

Tuy nhiên, hệ thống chống bot tự phát triển của Craigslist khá gắt. Nó không dùng Cloudflare hay DataDome — mà vận hành bộ giới hạn tốc độ riêng dựa trên nginx, được tinh chỉnh suốt hơn một thập kỷ. Chạm sai một phát là bạn sẽ ăn ngay lỗi 403 trước khi kịp uống xong ly cà phê buổi sáng. Tôi đã dành rất nhiều thời gian thử nghiệm các cách tiếp cận khác nhau để vượt qua cơ chế bảo vệ của Craigslist, và bài viết này là kết quả: một hướng dẫn Python cập nhật đến năm 2025, áp dụng cho mọi danh mục, có phần trích xuất JSON-LD (cải tiến lớn nhất so với các hướng dẫn cũ), chiến lược chống chặn thực tế, góc nhìn pháp lý, và cả một lựa chọn không cần code cho ai chỉ muốn lấy dữ liệu mà không muốn viết một dòng lệnh nào.

Scrape Craigslist bằng Python nghĩa là gì?

Web scraping Craigslist là dùng script Python để truy cập các trang Craigslist theo cách tự động, trích xuất dữ liệu có cấu trúc mà bạn cần — tiêu đề, giá, mô tả, hình ảnh, vị trí, ngày đăng — rồi lưu vào bảng tính, cơ sở dữ liệu hoặc file JSON.

Python là ngôn ngữ được chọn nhiều nhất cho việc này nhờ hệ sinh thái thư viện rất mạnh. Với requests, BeautifulSoup, lxmlcurl_cffi, bạn có thể xây dựng một scraper Craigslist hoạt động được chỉ trong chưa đến 100 dòng code. Cộng đồng Python cũng rất lớn, nên khi Craigslist thay đổi gì đó (và họ có thay đổi thật), thường đã có người tìm ra cách xử lý.

Điều quan trọng cần biết: Craigslist không cung cấp public read API. Giao diện lập trình chính thức duy nhất của họ là Bulk Posting Interface (BAPI), nhưng nó chỉ cho phép ghi dữ liệu — tức là những tài khoản đăng bài được phê duyệt và trả phí có thể gửi tin, chứ không thể lấy dữ liệu ra. Bất kỳ sản phẩm “Craigslist API” nào bạn thấy trên nền tảng bên thứ ba đều là scraper không chính thức, không phải endpoint được Craigslist công nhận. Nếu bạn muốn dữ liệu hàng loạt, bạn phải scrape.

Vì sao nên scrape Craigslist? Các ứng dụng thực tế

Craigslist không chỉ là nơi tìm ghế sofa cũ. Đây là một tập dữ liệu khổng lồ, liên tục được cập nhật trên hàng chục nhóm nội dung. Đây là những nhóm người thực sự hưởng lợi từ việc scrape nó:

Ứng dụngAi hưởng lợiBạn trích xuất gì
Theo dõi giá căn hộ & nhà thuêMôi giới bất động sản, người thuê, công ty PropTechGiá, diện tích, số phòng ngủ, khu vực, tọa độ
Phân tích thị trường xe cũĐại lý xe, ứng dụng tiêu dùng, nhà nghiên cứuGiá, hãng, mẫu xe, năm, số km, tình trạng
Nghiên cứu thị trường việc làmNhà tuyển dụng, nhà kinh tế lao động, chuyên gia nhân sựTiêu đề, mức lương, loại hình việc làm, ngày đăng
Tạo leadĐội sales, nhà cung cấp dịch vụThông tin liên hệ, tên doanh nghiệp, khu vực phục vụ
So sánh giá cạnh tranhNhà cung cấp dịch vụ địa phương, vận hành ecommerceBảng giá dịch vụ, mô tả, khu vực phục vụ

Ví dụ học thuật được nhắc đến nhiều nhất là bộ dữ liệu Kaggle "Used Cars Dataset" — khoảng 500.000 tin xe cũ tại Mỹ với 26 biến, và đây đã là nền tảng cho hàng chục nghiên cứu, bao gồm cả một nghiên cứu trên ResearchGate năm 2024 về động lực thị trường xe cũ ở Mỹ. Các quỹ hedge fund còn mua dữ liệu thuê nhà tổng hợp từ Craigslist để nghiên cứu xu hướng giá thuê. Và các đội sales thì thường xuyên scrape mục services và gigs để tạo lead.

Bài toán rất đơn giản: 8 giờ copy-paste thủ công so với khoảng 10 phút bằng một scraper được xây dựng tốt.

craigslist_stats_55285c3a34.png

Scrape Craigslist bằng Python: tất cả danh mục, không chỉ xe

Hầu hết các hướng dẫn scrape Craigslist tôi thấy chỉ nói về cars-for-sale — giống như viết hướng dẫn Google mà chỉ đề cập đến tìm kiếm hình ảnh. Craigslist có hàng chục danh mục, và mỗi danh mục lại có cấu trúc URL khác nhau.

Cấu trúc luôn là: https://{city}.craigslist.org/search/{category_slug}

Chỉ cần đổi subdomain của thành phố và slug, bạn sẽ scrape sang một mảng nội dung hoàn toàn khác. Đây là bảng tham chiếu các danh mục phổ biến nhất (đã xác minh vào tháng 4/2025):

Danh mụcURL SlugCác trường thường trích xuất
Căn hộ / nhà ở/search/apaGiá, diện tích, số phòng ngủ, vị trí, quy định thú cưng
Ô tô & xe tải/search/ctaGiá, hãng, mẫu, năm, số km
Việc làm/search/jjjTiêu đề, công ty, lương, loại hình việc làm
Dịch vụ/search/bbbTiêu đề, mô tả, số điện thoại, khu vực
Gigs/search/gggTiêu đề, mức thù lao, ngày, danh mục
Đồ rao bán (chung)/search/sssTiêu đề, giá, tình trạng, vị trí

Bạn cũng có thể kết hợp tham số truy vấn để lọc dữ liệu:

Tham sốMục đíchVí dụ
queryTừ khóa tìm kiếm toàn văn?query=studio
min_price / max_priceKhoảng giá&min_price=1500&max_price=3000
hasPicChỉ hiển thị bài có hình ảnh&hasPic=1
postedTodayTrong 24 giờ gần nhất&postedToday=1
sortCách sắp xếp&sort=priceasc
sOffset phân trang (120 kết quả mỗi trang)?s=120

Vì vậy, một URL như https://newyork.craigslist.org/search/apa?min_price=1500&max_price=3000&hasPic=1 sẽ cho bạn danh sách căn hộ ở New York từ 1.500 đến 3.000 USD kèm ảnh. Mọi scraper Python trong bài này đều áp dụng được cho các danh mục đó — bạn chỉ cần đổi slug.

CSS selector của Craigslist năm 2025: cũ vs. mới (và lối tắt JSON)

Lý do số một khiến scraper Craigslist bị hỏng là vì HTML thay đổi. Nếu bạn đang làm theo một bài hướng dẫn từ năm 2022 bảo target .result-row hay .result-info, thì scraper của bạn coi như đã “chết” rồi.

Craigslist đã viết lại markup của trang kết quả tìm kiếm vào giai đoạn 2023–2024. Các class cũ vẫn tồn tại nhưng nằm sâu trong wrapper mới; nếu target trực tiếp từ đầu cây DOM thì bạn sẽ nhận về danh sách rỗng. Đây là những gì đã thay đổi:

Phần tửSelector cũ (trước 2024)Selector hiện tại (2025)
Khung tin.result-info.cl-search-result
Link tiêu đề.result-title.posting-title a
Giá.result-price.priceinfo
Metadata (khu vực).result-hood.meta

Nhưng đây mới là điểm mấu chốt — và cũng là thứ phân biệt một scraper cập nhật năm 2025 với phần còn lại: bạn không cần parse HTML ở trang kết quả tìm kiếm.

Craigslist giờ nhúng toàn bộ danh sách hiển thị vào thẻ <script id="ld_searchpage_results"> dưới dạng dữ liệu JSON-LD có cấu trúc. Chỉ một lệnh requests.get() là đã lấy được schema.org ItemList đầy đủ của cả trang — tiêu đề, giá, tiền tệ, vị trí, URL ảnh, link chi tiết. Không cần render JavaScript. Không sợ selector CSS bị đổi.

Cách làm với JSON-LD nhanh hơn, ổn định hơn, và ít bị hỏng khi Craigslist chỉnh giao diện. Đây cũng là phương pháp mà các repo GitHub còn được bảo trì tích cực đang dùng, và là cách tôi sẽ dùng trong phần hướng dẫn bên dưới.

Một lưu ý: khối JSON-LD này thường có ở các danh mục có giá — căn hộ (apa), đồ rao bán (sss), xe (cta), nhà ở (hhh). Nó thường không có hoặc rất sơ sài ở jobs (jjj), gigs (ggg), community (ccc) và services (bbb) vì các tin này không có schema.org/Offer pricing. Với các danh mục đó, hãy quay về đường HTML bằng .cl-search-result.

Chọn stack Python: Requests + BS4, Selenium hay Playwright?

Đây là câu hỏi xuất hiện ở mọi diễn đàn scraping: “Nên dùng thư viện nào?” Với Craigslist, câu trả lời rõ ràng hơn nhiều website khác.

Tiêu chírequests + BeautifulSoupSeleniumPlaywright
Tốc độ5–15 trang/giây (bị giới hạn bởi mạng)0.3–1 trang/giây0.5–2 trang/giây
Nội dung render bằng JSKhông
Bộ nhớ~30–60 MB~400–700 MB~300–500 MB
Độ phức tạp thiết lậpThấpTrung bìnhTrung bình
Khả năng chống botThấp (cần headers/proxy)Trung bình (trình duyệt thật)Trung bình-cao
Trường hợp dùng tốt nhất cho CraigslistKết quả tìm kiếm (JSON-LD)Trang chi tiết có nội dung độngScraping async quy mô lớn
Độ dễ họcDễ cho người mớiTrung bìnhTrung bình

Trang Craigslist được render phía server. Khối JSON-LD nằm ngay trong HTML ban đầu. Không có thử thách JavaScript nào ở luồng đọc dữ liệu. Mọi Craigslist scraper trên GitHub còn duy trì đều dùng requests + BeautifulSoup hoặc Scrapy. Không có Selenium hay Playwright. Điều đó không phải ngẫu nhiên — automation bằng trình duyệt sẽ ngốn thêm hàng trăm MB RAM, chậm hơn 10–100 lần, và để lại dấu vết dễ bị phát hiện hơn mà chẳng mang lại lợi ích gì.

Khuyến nghị của tôi:

  • requests + BS4: Hãy bắt đầu từ đây. Nó khớp hoàn hảo với cách trích JSON-LD và xử lý được 95% nhu cầu scrape Craigslist.
  • Selenium: Chỉ dùng nếu bạn cần tương tác với nội dung động trên một số trang chi tiết cụ thể (trường hợp này hiếm trên Craigslist).
  • Playwright: Dùng khi bạn phải mở rộng lên hàng nghìn trang với concurrency async — nhưng thực ra, nút thắt của Craigslist là rate limiter, không phải tốc độ thư viện.

Nếu muốn xem phân tích đầy đủ, chúng tôi đã có bài so sánh Playwright vs. Puppeteer và bài tổng hợp các công cụ web scraping Python tốt nhất.

Scrape Craigslist mà không cần viết Python Get Started Free

Giải pháp không cần code: scrape Craigslist mà không viết Python

Trước khi vào phần code, mình rẽ nhẹ một chút — phần này dành cho những ai không phải developer. Môi giới bất động sản, đội sales, quản lý vận hành — nếu bạn chỉ cần dữ liệu chứ không muốn viết Python, thì có một cách nhanh hơn.

Thunderbit là một AI web scraper hoạt động dưới dạng tiện ích Chrome extension. Nó có thể scrape Craigslist chỉ trong khoảng 2 cú click, không cần code. Quy trình như sau:

  1. Mở bất kỳ trang kết quả tìm kiếm nào của Craigslist (căn hộ, xe, việc làm — bất kỳ danh mục nào).
  2. Bấm "AI Suggest Fields" trên thanh bên của Thunderbit. AI sẽ đọc trang và tự nhận diện các cột như tiêu đề tin, giá, vị trí và link.
  3. Bấm "Scrape" — dữ liệu sẽ được trích ra trong vài giây.
  4. Dùng Subpage Scraping để vào từng trang chi tiết của tin và làm giàu dữ liệu với mô tả đầy đủ, số điện thoại, hình ảnh và thuộc tính.
  5. Xuất thẳng sang Google Sheets, Excel, Airtable hoặc Notion — hoàn toàn miễn phí.

Nếu nhu cầu của bạn lặp lại đều đặn — ví dụ theo dõi giá căn hộ mỗi ngày hoặc chụp snapshot tin việc làm mỗi tuần — thì Scheduled Scraper của Thunderbit cho phép bạn mô tả lịch chạy bằng ngôn ngữ tự nhiên và hệ thống sẽ tự động chạy. Không cần cron job, không cần dựng server.

Thunderbit cũng xử lý các biện pháp chống bot bằng chế độ Cloud Scraping, nên bạn không phải lo xoay proxy hay tự tạo header. Nếu muốn thử, hãy cài Thunderbit Chrome Extension và tự trải nghiệm.

Nếu bạn muốn kiểm soát và tùy biến hoàn toàn, hãy tiếp tục xem phần hướng dẫn Python từng bước bên dưới.

Từng bước: Cách scrape Craigslist bằng Python (hướng dẫn đầy đủ)

  • Độ khó: Trung bình
  • Thời gian cần thiết: ~30 phút (thiết lập + scrape lần đầu)
  • Bạn cần: Python 3.8+, trình duyệt Chrome (để kiểm tra trang), một terminal

Bước 1: Thiết lập môi trường Python

Cài các thư viện cần thiết:

pip install requests beautifulsoup4 lxml

lxml là tùy chọn nhưng giúp BeautifulSoup parse nhanh hơn đáng kể. Nếu sau này bạn gặp vấn đề TLS fingerprinting (mình sẽ nói kỹ hơn ở phần chống chặn), bạn cũng có thể cài curl_cffi:

pip install curl_cffi

Phần import của bạn:

import requests
from bs4 import BeautifulSoup
import json
import csv
import time
import random

Giờ bạn đã có một môi trường Python sạch với đầy đủ dependencies.

Bước 2: Tạo URL Craigslist cho mọi danh mục

Tạo URL mục tiêu động bằng city + category slug + bộ lọc tùy chọn:

from urllib.parse import urlencode

BASE = "https://{city}.craigslist.org/search/{slug}"

def build_url(city, slug, **params):
    return f"{BASE.format(city=city, slug=slug)}?{urlencode(params)}"

# Ví dụ: căn hộ ở New York, giá 1500-3000, có ảnh
url = build_url("newyork", "apa", min_price=1500, max_price=3000, hasPic=1)
print(url)
# https://newyork.craigslist.org/search/apa?min_price=1500&max_price=3000&hasPic=1

Đổi "apa" thành "cta" (xe), "jjj" (việc làm), "bbb" (dịch vụ), hoặc bất kỳ slug nào từ bảng danh mục ở trên. Đổi "newyork" thành "sfbay", "chicago", "losangeles", v.v.

Bước 3: Gửi request và trích JSON nhúng

Gửi GET request với header phù hợp, rồi parse khối JSON-LD:

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/124.0.0.0 Safari/537.36"
    ),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Referer": "https://www.craigslist.org/",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "same-origin",
    "Upgrade-Insecure-Requests": "1",
}

session = requests.Session()
r = session.get(url, headers=HEADERS, timeout=20)
r.raise_for_status()

soup = BeautifulSoup(r.text, "html.parser")
tag = soup.select_one("script#ld_searchpage_results")
data = json.loads(tag.text) if tag else {"itemListElement": []}

Nếu tagNone, nghĩa là khối JSON-LD không có ở danh mục đó — hãy chuyển sang parse HTML (xem bảng selector ở trên). Với căn hộ, xe và các danh mục đồ rao bán, khối JSON-LD gần như luôn xuất hiện.

Bước 4: Parse dữ liệu tin đăng thành bản ghi có cấu trúc

Lặp qua các item JSON và trích các trường cần thiết:

listings = []
for entry in data["itemListElement"]:
    item = entry["item"]
    offers = item.get("offers", {}) or {}
    addr = (offers.get("availableAtOrFrom") or {}).get("address", {})
    listings.append({
        "name":     item.get("name"),
        "url":      offers.get("url"),
        "price":    offers.get("price"),
        "currency": offers.get("priceCurrency"),
        "locality": addr.get("addressLocality"),
        "region":   addr.get("addressRegion"),
        "image":    item.get("image"),
    })

print(f"Found {len(listings)} listings")

Bạn sẽ thấy kiểu như “Found 120 listings” (Craigslist hiển thị 120 kết quả mỗi trang). Một số tin có thể có None ở trường giá nếu người đăng không nhập giá — hãy xử lý tình huống đó mềm dẻo trong logic phía sau.

Bước 5: scrape trang chi tiết để lấy dữ liệu đầy đủ hơn

Trang kết quả chỉ cho bạn thông tin tóm tắt. Để lấy mô tả đầy đủ, thuộc tính (số phòng ngủ, diện tích, quy định thú cưng), tọa độ lat/long và hình ảnh, bạn cần vào từng URL chi tiết của tin.

def fetch_detail(url, session):
    r = session.get(url, headers=HEADERS, timeout=20)
    r.raise_for_status()
    s = BeautifulSoup(r.text, "html.parser")
    body = s.select_one("#postingbody")
    mp = s.select_one("#map")
    return {
        "description": body.get_text("\n", strip=True) if body else None,
        "attributes":  [x.get_text(" ", strip=True) 
                        for x in s.select("p.attrgroup span, div.attrgroup .attr")],
        "lat": mp.get("data-latitude") if mp else None,
        "lng": mp.get("data-longitude") if mp else None,
        "images": [img["src"] for img in s.select("div.gallery img")],
    }

for item in listings:
    item.update(fetch_detail(item["url"], session))
    time.sleep(random.uniform(3, 6))  # cực kỳ quan trọng: jitter chống chặn

time.sleep(random.uniform(3, 6)) là không được bỏ qua. Bỏ nó đi là bạn rất dễ ăn 403 chỉ sau vài chục request. Các trang chi tiết được render phía server với selector ổn định (#titletextonly, #postingbody, #map) và hầu như không đổi từ khoảng 2017 — một trong số ít điều đáng tin cậy trên Craigslist.

Bước 6: Xử lý phân trang để scrape toàn bộ kết quả

Craigslist dùng tham số offset ?s=120 cho phân trang. Mỗi trang có 120 kết quả, và offset tối đa thường là 2999.

def iter_all(city, slug, max_pages=25, **filters):
    for page in range(max_pages):
        offset = page * 120
        url = build_url(city, slug, s=offset, **filters)
        r = session.get(url, headers=HEADERS, timeout=20)
        r.raise_for_status()
        soup = BeautifulSoup(r.text, "html.parser")
        tag = soup.select_one("script#ld_searchpage_results")
        if not tag:
            break
        data = json.loads(tag.text)
        items = data.get("itemListElement", [])
        if not items:
            break
        for entry in items:
            item = entry["item"]
            offers = item.get("offers", {}) or {}
            yield {
                "name": item.get("name"),
                "url": offers.get("url"),
                "price": offers.get("price"),
            }
        time.sleep(random.uniform(2.5, 5.0))

Đừng cố scrape hàng nghìn trang liên tục trong thời gian ngắn. Rate limiter của Craigslist hoạt động theo IP, và thông lượng bền vững với một IP thường chỉ khoảng 0.3–0.5 request/giây bất kể bạn dùng thư viện nào. Giới hạn đó do Craigslist đặt ra, không phải do Python.

Bước 7: Xuất dữ liệu Craigslist sang CSV, JSON hoặc Google Sheets

Lưu kết quả của bạn:

# CSV
with open("craigslist.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=listings[0].keys())
    w.writeheader()
    w.writerows(listings)

# JSON
with open("craigslist.json", "w", encoding="utf-8") as f:
    json.dump(listings, f, indent=2, ensure_ascii=False)

Nếu bạn muốn bỏ qua luôn phần code xuất file, Thunderbit hỗ trợ xuất miễn phí trực tiếp sang Google Sheets, Excel, Airtable hoặc Notion ngay từ trình duyệt. Còn trong pipeline Python, CSV và JSON vẫn là định dạng tiêu chuẩn. Bạn cũng có thể đẩy dữ liệu thẳng vào pandas để phân tích hoặc vào database bằng sqlite3.

Cách tránh bị chặn khi scrape Craigslist bằng Python

Phần lớn các bài hướng dẫn thường nói lướt qua đoạn này. Hệ thống chống bot của Craigslist là tự xây, không phải hàng mua sẵn, và nó có vài đặc điểm khá riêng.

craigslist_antibot_ae9ddc3f54.png

Dùng header request thật giống người dùng thật

Craigslist kiểm tra thứ tự và độ đầy đủ của header. Một request thiếu Sec-Fetch-Dest hoặc dùng User-Agent cũ sẽ bị gắn cờ trước khi chạm vào nội dung. Bộ header đầy đủ kiểu Chrome 120+ (đã hiển thị ở Bước 3) là mức tối thiểu. Có thể xoay User-Agent theo từng session giữa 5–10 chuỗi Chrome/Firefox desktop mới — nhưng đừng đổi giữa chừng trong cùng một session, vì nhìn sẽ rất giả.

Thiếu các header Sec-Fetch-* là nguyên nhân phổ biến nhất khiến scraper mới bị chặn ngay lập tức.

Thêm độ trễ ngẫu nhiên giữa các request

Kinh nghiệm cộng đồng từ nhiều nguồn (ScrapingBee, Scraperly, Oxylabs, Multilogin) đều hội tụ về mức ngẫu nhiên 2–5 giây giữa các lần fetch trang kết quả và 3–6 giây giữa các trang chi tiết. Khoảng cách cố định trông rất “bot”. Hãy dùng time.sleep(random.uniform(2, 5)) — đừng dùng time.sleep(2).

Xoay proxy nếu scrape ở quy mô lớn

Craigslist chặn sẵn cả dải IP từ AWS, GCP và Azure. Proxy datacenter thường chết ngay từ request đầu tiên. Nếu vượt quá vài trăm trang, bạn sẽ cần residential rotating proxy, xoay mỗi 20–30 request. Proxy mobile có rủi ro bị phát hiện thấp nhất nhưng giá có thể từ $8–30/GB.

Loại proxyRủi ro bị phát hiện trên CraigslistChi phí (2025)
DatacenterRất cao — thường bị chặn ngay request đầu$0.50–2/GB
Residential rotatingThấp — được khuyến nghị$5–15/GB
MobileThấp nhất$8–30/GB

Chế độ Cloud Scraping của Thunderbit sẽ tự xử lý xoay proxy nếu bạn không muốn tự quản lý phần này.

Xử lý CAPTCHA một cách mềm dẻo

CAPTCHA trên Craigslist khá hiếm ở luồng đọc dữ liệu — chúng thường xuất hiện ở luồng đăng bài hoặc phản hồi. Nếu gặp CAPTCHA: dừng ít nhất 60 giây, đổi IP, xóa cookie và giảm tốc độ. CAPTCHA xuất hiện liên tục là dấu hiệu cho thấy nhịp truy cập của bạn quá gắt, chứ không phải một câu đố cần brute-force bằng solver.

Tôn trọng rate limit và triển khai backoff

Craigslist trả về 403 (không phải 429) khi bạn chạm rate limit. 403 nghĩa là IP hiện tại đã vào danh sách chặn — đừng retry mù quáng. Hãy đổi IP, đổi UA và chờ.

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

retry = Retry(
    total=5,
    backoff_factor=1.5,  # 1.5, 3, 6, 12, 24s
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods={"GET"},
    respect_retry_after_header=True,
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("https://", adapter)

Một mẹo nữa: các báo cáo từ cộng đồng thường chỉ ra khung giờ 2–6 giờ sáng theo giờ địa phương của thành phố mục tiêu là cửa sổ scrape an toàn nhất, với tỷ lệ bị chặn thấp hơn ban ngày khoảng 30–40%.

TLS fingerprinting — cái bẫy ẩn

Lớp bot của Craigslist còn kiểm tra TLS ClientHello. Thư viện requests của Python (dựa trên OpenSSL) có JA3 fingerprint không giống bất kỳ trình duyệt thật nào. Một User-Agent hoàn hảo nhưng đi kèm TLS fingerprint không giống browser là dấu hiệu dễ bị phát hiện. Cách khắc phục là dùng curl_cffi với impersonate="chrome124", mô phỏng bắt tay TLS của Chrome:

from curl_cffi import requests as cffi_requests

r = cffi_requests.get(url, headers=HEADERS, impersonate="chrome124")

Nếu bạn vẫn bị 403 không rõ lý do dù đang dùng IP residential sạch và header đúng, thì gần như chắc chắn thủ phạm là TLS fingerprinting.

robots.txt, Điều khoản sử dụng và scraping có trách nhiệm trên Craigslist

Nhiều hướng dẫn либо bỏ qua hoàn toàn phần này, либо nhét một dòng vào FAQ. Nhưng vì Craigslist đã từng thắng một bản án trị giá $60.5 triệu trong vụ tranh chấp với một scraper (RadPad, 2017), nên nó xứng đáng được nói kỹ hơn.

robots.txt của Craigslist thực sự nói gì?

File robots.txt của Craigslist khá ngắn. Nó chỉ có một block User-agent: * với 7 đường dẫn bị cấm:

Disallow: /reply
Disallow: /fb/
Disallow: /suggest
Disallow: /flag
Disallow: /mf
Disallow: /mailflag
Disallow: /eaf

Cả 7 đều là các endpoint tương tác/thay đổi trạng thái: trả lời, gắn cờ, đề xuất, email cho bạn bè. Các trang tin đăng (/search/..., URL từng bài riêng lẻ) không bị cấm. Không có chỉ thị Crawl-delay, dù Craigslist vẫn tự áp đặt giới hạn bằng cách chặn IP.

Các subdomain theo thành phố có public sitemap — ví dụ https://newyork.craigslist.org/sitemap/index.xml — đây là đường dẫn chính thức để khám phá tin đăng.

Tiền lệ pháp lý: những vụ đáng chú ý

Craigslist v. 3Taps (2013, dàn xếp 2015): 3Taps scrape tin Craigslist rồi bán lại. Khi Craigslist gửi yêu cầu chấm dứt và chặn IP, 3Taps tìm cách đi vòng qua chặn bằng proxy xoay. Tòa án kết luận rằng việc lách chặn IP sau khi đã bị thu hồi quyền truy cập rõ ràng là “không được ủy quyền” theo CFAA. 3Taps dàn xếp ở mức 1 triệu USD.

Meta v. Bright Data (2024): Một phán quyết gần đây hơn cho rằng điều khoản sử dụng của Meta không thể cấm việc scrape dữ liệu công khai khi người dùng đã đăng xuất. Tòa cho rằng scraper đang đăng xuất “đứng ở vị trí như một người truy cập thông thường.” Đây là phán quyết quan trọng nhất cho giới scraper giai đoạn 2024–2025 — nếu bạn không tạo tài khoản Craigslist, không đăng nhập, và chỉ truy cập các trang công khai, thì điều khoản sử dụng có thể không đủ sức ràng buộc bạn theo hợp đồng.

Kết luận thực tế: Rủi ro theo CFAA giảm đáng kể sau Van Buren (2021) và hiQ v. LinkedIn (2022) đối với các trang công khai. Nhưng các khiếu nại theo luật dân sự bang (như trespass-to-chattels, misappropriation) vẫn còn — đó là nền tảng dẫn tới cả vụ dàn xếp 3Taps và bản án 60.5 triệu USD với RadPad.

Đây chỉ là thông tin tham khảo, không phải tư vấn pháp lý. Nếu bạn scrape Craigslist cho mục đích thương mại, hãy trao đổi với luật sư.

Checklist thực hành cho scraping có đạo đức

  • ✅ Tôn trọng mọi mục Disallow trong robots.txt — đặc biệt là 7 endpoint hành động
  • ✅ Giữ dưới 1.000 trang trong mỗi 24 giờ cho mỗi IP (Điều khoản sử dụng của Craigslist áp mức $0.25 mỗi trang nếu vượt ngưỡng này như một khoản bồi thường ấn định)
  • ✅ Luôn ở trạng thái đăng xuất — không tạo tài khoản Craigslist chỉ để scrape
  • ✅ Không lách lệnh chặn IP bằng proxy sau khi đã bị chặn rõ ràng (đây là lý do 3Taps thất bại)
  • ✅ Thêm độ trễ giữa các request — tối thiểu 2–5 giây
  • ✅ Không scrape thông tin liên hệ cá nhân để spam
  • ✅ Không phân phối lại dữ liệu thô của Craigslist hoặc biến nó thành nền tảng của riêng bạn
  • ✅ Chỉ dùng dữ liệu cho mục đích nghiên cứu, phân tích hoặc sử dụng cá nhân hợp pháp
  • ✅ Ưu tiên sitemap đã công bố thay vì brute-force crawl khi có thể
  • ✅ Loại bỏ PII (email, số điện thoại) khi lưu dữ liệu

Chúng tôi cũng có một hướng dẫn sâu hơn về hệ quả pháp lý của web scraping nếu bạn muốn nhìn toàn cảnh hơn.

Python vs. no-code: cách nào phù hợp với bạn?

Tiêu chíPython (requests + BS4)Thunderbit (No-Code)
Thời gian thiết lập30–60 phút (cài đặt, viết code)2 phút (cài Chrome extension)
Kỹ năng kỹ thuật cần cóPython mức trung bìnhKhông cần
Tùy biếnToàn quyền kiểm soát logic, trường dữ liệu, luồng xử lýAI tự nhận diện trường; người dùng có thể chỉnh
Quy môKhông giới hạn (với proxy, lịch chạy)Có Scheduled Scraper cho tác vụ lặp lại
Xử lý chống chặnTự làm (headers, delays, proxy, TLS)Có sẵn (Cloud Scraping)
Tùy chọn xuất dữ liệuCSV, JSON (tự code)Google Sheets, Excel, Airtable, Notion — miễn phí
Phù hợp nhất choDeveloper, data scientist, pipeline tùy chỉnhĐội sales, môi giới BĐS, quản lý vận hành

Hãy dùng Python nếu bạn cần tùy biến sâu, muốn tích hợp vào một pipeline dữ liệu lớn hơn, hoặc muốn hiểu chính xác điều gì đang diễn ra bên trong. Hãy dùng Thunderbit nếu bạn muốn có kết quả nhanh mà không cần viết hay bảo trì code. Cả hai đều hợp lý. Tất cả phụ thuộc vào nhu cầu của bạn và việc bạn muốn dành thời gian cho terminal hay trình duyệt.

Kết luận

Craigslist là một nguồn dữ liệu phong phú, liên tục cập nhật ở các mảng nhà ở, xe cộ, việc làm, dịch vụ, gigs và nhiều hơn nữa — và vì không có API công khai, scraping là cách duy nhất để lấy dữ liệu có cấu trúc ở quy mô lớn. Cách làm hiệu quả trong năm 2025 là: trích JSON-LD nhúng từ trang kết quả tìm kiếm (thay vì selector CSS dễ gãy), dùng requests + BeautifulSoup (không phải Selenium), thêm header thật với các trường Sec-Fetch-*, random hóa độ trễ, và dùng proxy residential nếu bạn vượt quá vài trăm trang.

Phương pháp JSON-LD là cải tiến lớn nhất so với các hướng dẫn cũ. Nó nhanh hơn, bền hơn trước thay đổi giao diện, và không cần render JavaScript. Kết hợp với các chiến lược chống chặn ở trên, bạn sẽ tránh được các lỗi 403 mà phần lớn scraper hay gặp.

Nếu bạn muốn bỏ qua hoàn toàn phần code, Thunderbit Chrome Extension có thể scrape bất kỳ danh mục Craigslist nào chỉ với vài cú click và xuất thẳng sang bảng tính hoặc database bạn thích. Nếu muốn đào sâu hơn, các hướng dẫn của chúng tôi về cách web scrape bằng Pythonbest practices cho web scraping sẽ đi vào nền tảng chi tiết hơn.

Dùng thử Thunderbit cho việc scrape Craigslist

Dùng AI Web Scraper để lấy dữ liệu Craigslist Get Started Free

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

Scrape Craigslist có hợp pháp không?

Điều khoản sử dụng của Craigslist cấm scraping tự động và có điều khoản bồi thường ấn định ($0.25/trang nếu vượt 1.000 trang/ngày). Tuy nhiên, các phán quyết gần đây — đặc biệt là Meta v. Bright Data (2024) và hiQ v. LinkedIn (2022) — đã thu hẹp trách nhiệm theo CFAA đối với việc scrape dữ liệu công khai khi đã đăng xuất. Các khiếu nại theo luật dân sự bang (trespass-to-chattels) vẫn là rủi ro, nhất là nếu bạn tái phân phối dữ liệu cho mục đích thương mại. Hãy tôn trọng robots.txt, luôn đăng xuất, thêm độ trễ và không phân phối lại dữ liệu thô. Đây là thông tin chung, không phải tư vấn pháp lý.

Craigslist có API công khai không?

Không. Craigslist chỉ cung cấp Bulk Posting Interface (BAPI) dạng chỉ-ghi cho các tài khoản đăng bài được phê duyệt và trả phí. Không có public read API, không có developer portal, và cũng không có tầng giới hạn tốc độ để lấy dữ liệu. Mọi sản phẩm “Craigslist API” trên nền tảng bên thứ ba đều là scraper không chính thức.

Vì sao scraper Craigslist của tôi cứ bị hỏng?

Gần như luôn là do HTML thay đổi. Craigslist đã viết lại markup của trang kết quả tìm kiếm trong giai đoạn 2023–2024, và các hướng dẫn dùng selector cũ như .result-row hoặc .result-info giờ không còn hoạt động. Hãy chuyển sang cách trích JSON-LD nhúng (parse script#ld_searchpage_results) để có cách làm bền hơn nhiều. Đồng thời kiểm tra xem header của bạn có bao gồm các trường Sec-Fetch-* hay không — thiếu chúng là bị chặn ngay.

Tôi có thể scrape Craigslist mà không cần Python không?

Có. Tiện ích Chrome AI web scraper của Thunderbit hoạt động trên mọi trang Craigslist — căn hộ, xe, việc làm, dịch vụ. Chỉ cần bấm "AI Suggest Fields" để tự nhận diện cột, bấm "Scrape" để trích dữ liệu, rồi xuất sang Google Sheets, Excel, Airtable hoặc Notion miễn phí. Không cần code, không cần thiết lập, không cần quản lý proxy.

Tôi có thể scrape Craigslist thường xuyên đến mức nào mà không bị chặn?

Với một IP residential, thông lượng bền vững khoảng 0.3–0.5 request mỗi giây, kèm độ trễ ngẫu nhiên 2–5 giây giữa các trang. Nên giữ dưới 1.000 trang trong mỗi 24 giờ cho mỗi IP để tránh cả việc bị chặn lẫn ngưỡng bồi thường ấn định trong điều khoản Craigslist. Scrape vào giờ thấp điểm (2–6 giờ sáng theo giờ địa phương của thành phố mục tiêu) sẽ giảm tỷ lệ bị chặn khoảng 30–40%. Nếu cần khối lượng lớn hơn, hãy xoay residential proxy mỗi 20–30 request.

Tìm hiểu thêm

Ke
Ke
CTO tại Thunderbit | Chuyên gia Khoa học Dữ liệu cấp cao & ML Với gần một thập kỷ kinh nghiệm trong học máy và khoa học dữ liệu, Ke Shen là cựu sinh viên Đại học Columbia và từng là Chuyên gia Khoa học Dữ liệu cấp cao tại Walmart Labs. Sở hữu chuyên môn sâu về Python, R, Java và Thống kê, được đồng nghiệp công nhận, anh chia sẻ những góc nhìn thực chiến về cách đưa các thuật toán AI phức tạp từ lý thuyết vào kiến trúc sẵn sàng cho môi trường sản xuất.
Mục lục

Cào một trang web chỉ bằng cách hỏi

Nói bạn cần gì bằng tiếng Anh đơn giản. Hoặc tốt hơn, không cần nói gì cả.

Dùng Thunderbit ngay miễn phí
Trích xuất dữ liệu bằng AI
Dễ dàng chuyển dữ liệu sang Google Sheets, Airtable hoặc Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week