30년 동안 크레이그리스트는 모든 종류의 거래를 위한 대표적인 시장이었습니다. 매우 단순한 1990년대 디자인에도 불구하고, 크레이그리스트는 “개인 판매” 거래를 쇼핑하기에 세계 최고의 장소일 수 있습니다.
오늘은Python 스크레이퍼를 사용해 크레이그리스트에서 자동차 데이터를 추출하는 방법을 알아보겠습니다. 따라하다 보면 금방 전문가처럼 크레이그리스트를 스크래핑할 수 있을 거예요. 대규모 작업이 필요하신가요? 최고의 스크래핑 도구 비교를 확인해 보세요.
크레이그리스트에서 추출할 항목
HTML을 파헤치는 어려운 방법
웹 스크래핑에서 가장 중요한 기술은 어디를 살펴봐야 하는지 아는 것입니다. HTML 코드에서 개별 항목을 추출하는 지나치게 복잡한 파서를 작성할 수도 있습니다.
아래 이미지의 트럭을 보면, 해당 데이터는 cl-gallery 클래스의 div 요소 안에 중첩되어 있습니다. 어려운 방법을 선택한다면, 이 태그를 찾아내고 거기서부터 요소를 추가로 파싱할 수 있습니다.

JSON 찾기: 소중한 시간 절약하기
하지만 더 나은 방법이 있습니다. 크레이그리스트를 비롯한 많은 사이트는 내장된 JSON 데이터를 사용해 전체 페이지를 구성합니다. 이 JSON을 찾을 수 있다면 파싱 작업을 거의 제로로 줄일 수 있습니다.
크레이그리스트 페이지에는 우리가 원하는 모든 데이터를 담고 있는 script 객체가 있습니다. 이 하나의 요소를 추출하면 페이지 전체의 데이터를 얻을 수 있습니다. 확인해보면 id는 ld_searchpage_results입니다. CSS 선택자 script[id='ld_searchpage_results']로 이 요소를 찾을 수 있습니다.

파이썬으로 크레이그리스트 스크래핑하기
이제 우리가 찾으려는 대상이 무엇인지 알았으니, 크레이그리스트 스크래핑이 훨씬 쉬워질 것입니다. 다음 몇 섹션에서는 개별 코드를 살펴본 후 이를 모두 결합하여 작동하는 스크래퍼를 만들 것입니다.
페이지 파싱
def scrape_listings(location, keyword):
url = f"https://{location}.craigslist.org/search/cta?query={keyword}"
scraped_data = []
success = False
while not success:
try:
response = requests.get(url)
# 잘못된 상태 코드가 수신되면 에러 발생
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
embedded_json_string = soup.select_one("script[id='ld_searchpage_results']")
json_data = json.loads(embedded_json_string.text).get("itemListElement")
for dirty_item in json_data:
item = dirty_item.get("item")
offers = item.get("offers")
location_info = item.get("offers").get("availableAtOrFrom")
images = item.get("image")
image = None
if len(images) > 0:
image = images[0]
clean_item = {
"name": item.get("name"),
"image": image,
"price": item.get("offers").get("price"),
"currency": item.get("offers").get("priceCurrency"),
"city": location_info.get("address").get("addressLocality"),
"region": location_info.get("address").get("addressRegion"),
"country": location_info.get("address").get("addressCountry")
}
scraped_data.append(clean_item)
# 모든 리스팅을 처리했으므로 success = True로 설정하고 루프를 종료
success = True
except Exception as e:
print(f"{url}에서 리스팅 스크래핑 실패, {e}")
return scraped_data
- 먼저,
url,scraped_data,success변수를 생성합니다.url: 수행하려는 검색의 정확한 URL입니다.scraped_data: 검색 결과를 모두 저장할 변수입니다.success: 이 스크레이퍼가 지속적(persistent)이 되도록 합니다.while루프와 함께 사용하면 작업이 완료되고 success를True로 설정할 때까지 스크레이퍼가 종료되지 않습니다.
- 그런 다음 페이지를 가져오고 잘못된 응답이 발생하면 에러를 발생시킵니다.
soup = BeautifulSoup(response.text, "html.parser")는 페이지를 파싱하는 데 사용할 수 있는BeautifulSoup객체를 생성합니다.embedded_json_string = soup.select_one("script[id='ld_searchpage_results']")를 사용하여 임베디드 JSON을 찾습니다.- 그런 다음
json.loads()를 사용하여딕셔너리로변환합니다. - 다음으로, 모든 항목을 반복 처리하며 데이터를 정리합니다.
clean_item은scraped_data에추가됩니다. - 마지막으로
success를True로 설정하고 스크랩된 목록 배열을 반환합니다.
데이터 저장
웹 스크래핑에서 가장 흔히 사용되는 저장 방식은 CSV와 JSON입니다. 두 형식으로 목록을 저장하는 방법을 살펴보겠습니다.
JSON 파일로 저장하기
이 기본 코드 조각은 JSON 저장 로직을 담고 있습니다. 파일을 열고 데이터를 json.dump() 에 전달합니다. indent=4를 사용하여 JSON 파일을 가독성 있게 만듭니다.
with open(f"{QUERY}-{LOCATION}.json", "w") as file:
try:
json.dump(listings, file, indent=4)
except Exception as e:
print(f"결과 저장 실패: {e}")
CSV 파일로 저장하기
CSV로 저장하려면 조금 더 작업이 필요합니다. CSV는 배열을 잘 처리하지 못합니다. 그래서 데이터 정리 시 하나의 이미지만 추출한 것입니다.
목록이 없으면 함수가 종료됩니다. 목록이 있으면 배열의 첫 번째 항목에서 keys() 를 사용하여 CSV 헤더를 작성합니다. 그런 다음 csv.DictWriter() 를 사용하여 헤더와 목록을 작성합니다.
def write_listings_to_csv(listings, filename):
if not listings:
print("목록을 찾을 수 없습니다. CSV 쓰기를 건너뜁니다.")
return
# CSV 열 헤더 정의
fieldnames = listings[0].keys()
# CSV에 데이터 쓰기
with open(filename, "w", newline="", encoding="utf-8") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(listings)
모든 것을 합치기
이제 이 모든 조각들을 하나로 모을 수 있습니다. 이 코드는 완전히 작동하는 스크레이퍼를 포함합니다.
import requests
from bs4 import BeautifulSoup
import json
import csv
def write_listings_to_csv(listings, filename):
if not listings:
print("목록을 찾을 수 없습니다. CSV 쓰기를 건너뜁니다.")
return
# CSV 열 헤더 정의
fieldnames = listings[0].keys()
# CSV에 데이터 쓰기
with open(filename, "w", newline="", encoding="utf-8") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(listings)
def scrape_listings(location, keyword):
url = f"https://{location}.craigslist.org/search/cta?query={keyword}"
scraped_data = []
success = False
while not success:
try:
response = requests.get(url)
# 상태 코드가 잘못된 경우 오류 발생
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
embedded_json_string = soup.select_one("script[id='ld_searchpage_results']")
json_data = json.loads(embedded_json_string.text).get("itemListElement")
for dirty_item in json_data:
item = dirty_item.get("item")
offers = item.get("offers")
location_info = item.get("offers").get("availableAtOrFrom")
images = item.get("image")
image = None
if len(images) > 0:
image = images[0]
clean_item = {
"name": item.get("name"),
"image": image,
"price": item.get("offers").get("price"),
"currency": item.get("offers").get("priceCurrency"),
"city": location_info.get("address").get("addressLocality"),
"region": location_info.get("address").get("addressRegion"),
"country": location_info.get("address").get("addressCountry")
}
scraped_data.append(clean_item)
# 모든 리스팅을 처리했으므로 success = True로 설정하고 루프를 종료
success = True
except Exception as e:
print(f"{url}에서 리스팅 스크래핑 실패: {e}")
return scraped_data
if __name__ == "__main__":
LOCATION = "detroit"
QUERY = "cars"
OUTPUT = "csv"
listings = scrape_listings(LOCATION, QUERY)
if OUTPUT == "json":
with open(f"{QUERY}-{LOCATION}.json", "w") as file:
try:
json.dump(listings, file, indent=4)
except Exception as e:
print(f"결과 저장 실패: {e}")
elif OUTPUT == "csv":
try:
write_listings_to_csv(listings, f"{QUERY}-{LOCATION}.csv")
print(f"{len(listings)}개의 목록을 {QUERY}-{LOCATION}.csv에 저장했습니다")
except Exception as e:
print(f"CSV 출력 작성 실패: {e}")
else:
print("출력 방법이 지원되지 않습니다")
메인 블록 내부에서 OUTPUT 변수를 통해 저장 방식을 처리할 수 있습니다. JSON 파일로 저장하려면 json으로 설정하세요. CSV를 원한다면 이 변수를 csv로 설정하세요. 데이터 수집 시에는 이 두 저장 방식을 항상 함께 사용할 것입니다.
JSON 출력
아래 이미지에서 볼 수 있듯이, 각 차량은 깔끔하고 명확한 구조의 가독성 높은 JSON 객체로 표현됩니다.

CSV 출력
CSV 출력도 매우 유사합니다. 모든 목록을 담고 있는 깔끔한 스프레드시트를 얻을 수 있습니다.

웹 언락커로 크레이그리스트 보호 기능 우회하기
크레이그리스트 스크래핑 작업을 확장할수록 필연적으로 장애물에 부딪히게 됩니다: CAPTCHA, IP 차단, 그리고 스크래퍼를 완전히 막을 수 있는 봇 방지 시스템 등이 있습니다.
Bright Data의Web Unlocker는대규모 데이터 수집을 위해 특별히 설계된 엔터프라이즈급 인프라를 통해 이러한 문제를 자동으로 해결합니다.
자동 CAPTCHA 해결
수동으로 CAPTCHA를 해결하거나 차단된 요청으로 소중한 데이터를 잃는 대신, 웹 언락커가 처리해 드립니다:
- ✅ reCAPTCHA, hCaptcha 등 다양한CAPTCHA 자동 해결
- ✅ 탐지 회피를 위한실시간 지문 무작위화
- ✅ 각 사이트의 보호 메커니즘에 적응하는스마트 재시도 로직
- ✅ 강력한 보호 페이지에서도99.9% 성공률
당사의CAPTCHA 해결 기능에 대해 자세히 알아보세요.
간단한 통합
import requests
# Web Unlocker 엔드포인트
WEB_UNLOCKER_URL = 'https://brd.superproxy.io:33335'
AUTH = 'brd-customer-<CUSTOMER_ID>-zone-web_unlocker:<ZONE_PASSWORD>'
def scrape_with_unlocker(location, keyword):
url = f"https://{location}.craigslist.org/search/cta?query={keyword}"
response = requests.get(
url,
proxies={
'http': f'http://{AUTH}@{WEB_UNLOCKER_URL}',
'https': f'http://{AUTH}@{WEB_UNLOCKER_URL}'
},
verify=False
)
return response.text
# 차단이나 CAPTCHA 걱정 없이 스크래핑
listings = scrape_with_unlocker("detroit", "cars")
Web Unlocker를 사용하면 다음과 같은 이점이 있습니다:
- 수동 CAPTCHA 해결 불필요
- 번거로운 프록시 관리 없음
- IP 로테이션 설정 불필요
- 대규모로 깔끔하고 안정적인 데이터 수집만 가능
스크래핑 브라우저 활용
Scraping Browser를 사용하면 프록시 통합이 가능한 Playwright 인스턴스를 실행할 수 있습니다. Python 스크립트에서 전체 브라우저를 운영함으로써 스크래핑을 한 단계 업그레이드할 수 있습니다. Playwright와 프록시 통합에 관심이 있다면
아래 코드에서 파싱 메서드는 대부분 동일하지만, async_playwright와 함께 asyncio를 사용하여 헤드리스 브라우저를 열고 이 브라우저로 실제로 페이지를 가져옵니다. BeautifulSoup 대신 CSS 선택자를 Playwright의 query_selector() 메서드에 전달합니다.
import asyncio
from playwright.async_api import async_playwright
import json
AUTH = 'brd-customer-<YOUR-USERNAME>-zone-<YOUR-ZONE-NAME>:<YOUR-PASSWORD>'
SBR_WS_CDP = f'wss://{AUTH}@brd.superproxy.io:9222'
async def scrape_listings(keyword, location):
print('스크래핑 브라우저에 연결 중...')
url = f"https://{location}.craigslist.org/search/cta?query={keyword}"
scraped_data = []
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(SBR_WS_CDP)
context = await browser.new_context()
page = await context.new_page()
try:
print('연결됨! 웹페이지로 이동 중...')
await page.goto(url)
embedded_json_string = await page.query_selector("script[id='ld_searchpage_results']")
json_data = json.loads(await embedded_json_string.text_content())["itemListElement"]
for dirty_item in json_data:
item = dirty_item.get("item")
offers = item.get("offers")
location_info = item.get("offers").get("availableAtOrFrom")
images = item.get("image")
image = None
if len(images) > 0:
image = images[0]
clean_item = {
"name": item.get("name"),
"image": image,
"price": item.get("offers").get("price"),
"currency": item.get("offers").get("priceCurrency"),
"city": location_info.get("address").get("addressLocality"),
"region": location_info.get("address").get("addressRegion"),
"country": location_info.get("address").get("addressCountry")
}
scraped_data.append(clean_item)
except Exception as e:
print(f"데이터 스크래핑 실패: {e}")
finally:
await browser.close()
return scraped_data
async def main():
QUERY = "cars"
LOCATION = "detroit"
listings = await scrape_listings(QUERY, LOCATION)
try:
with open(f"{QUERY}-scraping-browser.json", "w") as file:
json.dump(listings, file, indent=4)
except Exception as e:
print(f"결과 저장 실패 {e}")
if __name__ == '__main__':
asyncio.run(main())
사용자 정의 노코드 스크레이퍼 활용
Bright Data에서는 코드 없이 사용할 수 있는 크레이그리스트 스크레이퍼도 제공합니다. 이 스크레이퍼를 사용하면 스크레이핑할 데이터와 페이지를 지정하기만 하면 됩니다. 그러면 저희가 스크레이퍼를 생성하여 배포해 드립니다!
“내 스크레이퍼” 섹션에서 “새로 만들기”를 클릭하고 “맞춤형 스크레이퍼 요청”을 선택하세요.

다음으로 사이트 레이아웃이 포함된 URL을 입력하라는 메시지가 표시됩니다. 아래 이미지에서는 디트로이트 자동차 검색 페이지의 URL을 전달했습니다. 다른 도시의 URL을 추가할 수도 있습니다.

자동화된 프로세스를 통해 해당 사이트를 스크래핑하고 검토할 스키마를 생성합니다.

스키마가 생성되면 검토해야 합니다.

다음은 맞춤형 크레이그리스트 스크레이퍼 스키마의 샘플 JSON 데이터입니다. 몇 분 안에 작동하는 프로토타입이 생성됩니다.
{
"type": "object",
"fields": {
"listings": {
"type": "array",
"active": true,
"items": {
"type": "object",
"fields": {
"title": {
"type": "text",
"active": true,
"sample_value": "$208/mo - 2014 Ford F150 F 150 F-150 XL"
},
"url": {
"type": "url",
"active": true,
"sample_value": "https://annarbor.craigslist.org/ctd/d/ann-arbor-208-mo-ford-f150-150-150-xl/7826116555.html"
},
"price": {
"type": "price",
"active": true,
"sample_value": "$10,250"
},
"location": {
"type": "text",
"active": true,
"sample_value": "2892 Jackson Avenue Ann Arbor, MI 48103"
}
}
}
},
"url": {
"type": "url",
"required": true,
"active": true,
"sample_value": "https://detroit.craigslist.org/search/cta?query=cars"
}
}
}
다음으로 수집 범위를 설정합니다. 크레이그리스트 전체를 스크래핑할 필요도, 특정 섹션만 할 필요도 없으므로 URL을 입력해 스크래핑을 시작하도록 합니다.

마지막으로, 배포를 위해 당사 전문가와의 상담 일정을 잡으라는 안내가 표시됩니다. 월 300달러의 유지보수 비용을 지불하거나, 일회성 배포 비용 1,000달러를 선택할 수 있습니다.

결론
크레이그리스트를 스크래핑할 때 이제 파이썬을 활용해 빠르고 효율적인 데이터 처리가 가능합니다. 데이터를 파싱하고 정리하는 방법을 알게 되었으며, CSV와 JSON을 사용해 저장하는 방법도 배웠습니다. 완전한 브라우저 기능이 필요하다면, 완전한 프록시 통합으로 이러한 요구를 충족시키는 Scraping Browser를 활용할 수 있습니다. 스크래핑 프로세스를 완전히 자동화하려는 경우, 이제 저희의 No-Code Scraper를 다루는 방법도 알게 되었습니다.
또한 스크래핑 과정을 완전히 생략하고 싶다면, Bright Data에서 바로 사용 가능한 크레이그리스트 데이터셋을 제공합니다. 지금 가입하고 무료 체험을 시작하세요!