Craigslist остаётся крупнейшей в США площадкой для объявлений с конца 1990-х годов и по-прежнему содержит миллионы публичных объявлений в категориях недвижимости, товаров, услуг и местных новостей. Страницы объявлений представляют собой ценный источник публичных рыночных сигналов: динамика арендных ставок по районам, цены на подержанные товары в метрополии, нехватка предложения в отдельных сегментах. Каждый результат поиска находится на странице в предсказуемой серверно-отрендеренной структуре, что делает общедоступные поля удобными для чтения.
Это руководство показывает, как парсить Craigslist с помощью JavaScript и Node.js с использованием Cheerio. Вы создадите небольшой работающий парсер, который получает страницу результатов публичного поиска Craigslist через Crawling API, извлекает название, цену, местоположение, дату публикации и ссылку для каждого объявления, а затем экспортирует результат в форматах JSON и CSV. Всё руководство ограничено публичными, неперсональными данными объявлений. Контактные данные продавца и свободный текст публикации являются персональными данными, а раздел о законности в конце объясняет, почему этот парсер намеренно их исключает, поэтому прочитайте его перед тем, как запускать скрипт на реальных объёмах.
Что вы создадите
Скрипт на Node.js, который принимает публичный URL поиска Craigslist, получает HTML через Crawling API и извлекает структурированную запись для каждого объявления на странице результатов. В качестве основного примера используется поиск недвижимости, и для каждого объявления извлекаются следующие поля:
- Title заголовок объявления, отображаемый на карточке результата, например «2 bedroom trailer for rent».
- Price запрашиваемая цена в том виде, в каком она отображается, например «$675»; хранится как строка, поскольку цены на Craigslist содержат символы валюты и разделители тысяч.
- Location подсказка о районе или территории, которую Craigslist отображает рядом с объявлением.
- Post date дата публикации объявления, если карточка результата её содержит.
- Link абсолютный URL на страницу отдельного объявления.
Почему обычный запрос может не работать на Craigslist
Одиночный HTTP-запрос к URL поиска Craigslist может сработать, но он не выдержит нагрузки при парсинге сколько-нибудь значительного объёма. Craigslist отслеживает автоматический трафик и принимает меры против него: IP-адреса дата-центров и паттерны запросов, не похожие на реальный браузер, подвергаются ограничению скорости, получают CAPTCHA или блокируются полностью. Запустите жёсткий цикл с одного адреса, и вы довольно быстро увидите ответы, отличные от 200, и страницы с проверкой вместо объявлений.
Поэтому устойчивый парсер Craigslist нуждается в IP-адресе, который платформа воспринимает как реального посетителя, и должен вести себя вежливо. Можно собрать это самостоятельно с пулом ротирующихся резидентных прокси, но поддержание этого пула в рабочем состоянии и без блокировок составляет основную часть работы. Crawling API объединяет всё это в один вызов: вы передаёте ему URL, он получает страницу через доверенный IP со встроенной обработкой CAPTCHA и возвращает HTML для парсинга с помощью Cheerio.
Всё в этом руководстве читает поля, которые любой посетитель видит на публичной странице результатов поиска: название, цену, подсказку о местоположении, дату публикации и ссылку. Руководство не открывает отдельные объявления для сбора номера телефона или электронной почты продавца и не создаёт профили размещающих объявления лиц. Эта граница намеренная, и раздел о законности ниже её объясняет.
Предварительные требования
Прежде чем писать код, необходимо подготовить несколько вещей. Ни одна из них не занимает много времени.
Базовые знания JavaScript и Node.js. Вы должны уметь писать и запускать Node-скрипты, а также устанавливать пакеты с помощью npm. Если Node.js для вас нов, официальная документация или любой вводный курс выведут вас на уровень, который предполагает этот учебник. Для более полного погружения наше руководство по созданию веб-парсера на Node.js охватывает основы.
Node.js версии 16 или выше. Проверьте свою версию командой node --version. Если его нет, установите с сайта Node.js или через менеджер версий, например nvm.
Аккаунт Crawlbase и токен. Зарегистрируйтесь, откройте панель управления и скопируйте токен со страницы документации аккаунта. Бесплатный уровень даёт до 20 000 бесплатных запросов без необходимости вводить данные карты, и вы платите только за успешные запросы. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.
Настройка проекта
Создайте папку проекта, инициализируйте её и установите две библиотеки, необходимые парсеру.
node --version mkdir craigslist-scraper && cd craigslist-scraper npm init -y npm install crawlbase cheerio
Две зависимости выполняют основную работу: crawlbase является официальным Node-клиентом для Crawling API, а cheerio парсит возвращаемый HTML с jQuery-подобным API, позволяя извлекать отдельные поля по CSS-селектору. Исходная версия этого учебника использовала jsdom для парсинга сохранённого HTML; Cheerio выполняет ту же работу с более лёгким и быстрым API, лучше подходящим для конвейера парсинга. Создайте файл с именем scraper.js в этой папке и добавьте код из приведённых ниже шагов.
Шаг 1: получение страницы результатов поиска
Начните с получения HTML страницы. Импортируйте класс CrawlingAPI, инициализируйте его с помощью токена и запросите публичный URL поиска Craigslist. Выберите страницу с листингом поиска, которую хотите парсить, например поиск недвижимости для продажи в галерейном режиме, и проверяйте код статуса перед парсингом, чтобы сбои были заметны, а не тихими.
const { CrawlingAPI } = require('crawlbase'); const fs = require('fs'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); const craigslistPageURL = 'https://chicago.craigslist.org/search/rea?hasPic=1'; api .get(craigslistPageURL) .then((response) => { if (response.statusCode === 200) { fs.writeFileSync('response.html', response.body); console.log('HTML saved to response.html'); } else { console.error(`Request failed: ${response.statusCode}`); } }) .catch((error) => console.error('API request error:', error));
Запустите скрипт командой node scraper.js. В случае успеха он записывает страницу в response.html, что позволяет изучить разметку и разрабатывать селекторы против стабильной копии, не обращаясь к сети при каждом изменении. Crawling API получает страницу через доверенный IP, поэтому в полученном HTML вы видите объявления, а не страницу блокировки.
Этот первый запрос вернул реальную страницу результатов Craigslist без пула прокси и решения CAPTCHA с вашей стороны. Crawling API получает страницу через ротирующиеся резидентные IP на стороне сервера и справляется с проверками, которые Craigslist применяет против парсеров, поэтому вы получаете пригодный HTML из одного вызова. Сначала направьте его на публичный поиск в рамках бесплатного уровня, затем добавьте свой парсер.
Шаг 2: парсинг каждого объявления с помощью Cheerio
Имея сохранённый HTML, загрузите его в Cheerio и пройдитесь по объявлениям. Craigslist отрендеривает статические результаты поиска внутри списка ol.cl-static-search-results, каждое объявление находится в собственном элементе li.cl-static-search-result, поэтому вы выбираете каждый элемент и читаете название, цену, местоположение, дату публикации и ссылку изнутри. Защитное чтение каждого поля предотвращает сбой всего прогона из-за одного отсутствующего значения.
const cheerio = require('cheerio'); function parseListings(html) { const $ = cheerio.load(html); const listings = []; $('ol.cl-static-search-results li.cl-static-search-result').each((_, el) => { const item = $(el); const title = item.find('.title').text().trim(); const price = item.find('.price').text().trim(); const location = item.find('.location').text().trim(); const postDate = item.find('.meta time').attr('datetime') || ''; const link = item.find('a').attr('href') || ''; if (title) { listings.push({ title, price: price || 'N/A', location: location || 'N/A', postDate, url: link, }); } }); return listings; }
Селекторы напрямую соответствуют странице. Название каждого объявления берётся из .title, запрашиваемая цена из .price, подсказка о районе из .location, а ссылка из атрибута href якоря элемента. Дата публикации читается из атрибута datetime элемента time в строке .meta объявления, что даёт чистую машиночитаемую дату, а не относительный текст. Цена намеренно остаётся строкой, поскольку значения на Craigslist включают символ валюты и разделители тысяч; при необходимости преобразуйте в число позже, если этого требует ваш анализ.
Craigslist время от времени изменяет свою разметку, и отдельные поддомены городов могут незначительно различаться. Рассматривайте эти селекторы как начальный шаблон, а не как контракт. Когда поле возвращается пустым, откройте response.html или живую страницу в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов нормально для любого рабочего парсера, это не признак поломки.
Шаг 3: сборка полного скрипта с экспортом в JSON и CSV
Теперь объедините получение и парсинг в один запускаемый скрипт, а затем запишите записи на диск в форматах JSON и CSV.
const fs = require('fs'); const { CrawlingAPI } = require('crawlbase'); const cheerio = require('cheerio'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); async function crawl(pageUrl) { const response = await api.get(pageUrl); if (response.statusCode === 200) return response.body; console.error(`Request failed: ${response.statusCode}`); return null; } function toCsv(rows) { const headers = ['title', 'price', 'location', 'postDate', 'url']; const escape = (value) => `"${String(value).replace(/"/g, '""')}"`; const lines = [headers.join(',')]; for (const row of rows) { lines.push(headers.map((h) => escape(row[h])).join(',')); } return lines.join('\n'); } async function main() { const url = 'https://chicago.craigslist.org/search/rea?hasPic=1'; const html = await crawl(url); if (!html) return; const listings = parseListings(html); fs.writeFileSync('listings.json', JSON.stringify(listings, null, 2)); fs.writeFileSync('listings.csv', toCsv(listings)); console.log(`Saved ${listings.length} listings to JSON and CSV`); } main();
Вставьте функцию parseListings из шага 2 в тот же файл, чтобы main могла её вызвать. Запустите командой node scraper.js, и вы получите два файла: listings.json с полными структурированными записями и listings.csv, готовый к открытию в таблице. Вспомогательная функция toCsv берёт каждое поле в кавычки и удваивает все встроенные кавычки, что важно здесь, поскольку заголовки объявлений часто содержат запятые.
Как выглядит вывод
Файл JSON содержит один объект на каждое объявление с заголовком, ценой, местоположением, датой публикации и ссылкой. Приведённые ниже значения иллюстративны и взяты из поиска недвижимости.
[ { "title": "2 bedroom trailer for rent", "price": "$675", "location": "165th & Kennedy", "postDate": "2024-04-05 09:12", "url": "https://chicago.craigslist.org/nwi/reo/d/hammond-bedroom-trailer-for-rent/7732856568.html" }, { "title": "Barrington Village Home", "price": "$439,000", "location": "northwest suburbs", "postDate": "2024-04-04 16:48", "url": "https://chicago.craigslist.org/nwc/reo/d/barrington-barrington-village-home/7734168844.html" } ]
CSV отражает те же строки с заголовком, поэтому он сразу же открывается в Excel, Google Sheets или любом конвейере данных, читающем файлы с разделителями.
title,price,location,postDate,url "2 bedroom trailer for rent","$675","165th & Kennedy","2024-04-05 09:12","https://chicago.craigslist.org/nwi/reo/d/hammond-bedroom-trailer-for-rent/7732856568.html" "Barrington Village Home","$439,000","northwest suburbs","2024-04-04 16:48","https://chicago.craigslist.org/nwc/reo/d/barrington-barrington-village-home/7734168844.html"
Обработка пагинации
Одна страница поиска подходит для демонстрации; реальная задача предполагает получение каждой страницы результатов. Craigslist разбивает URL поиска на страницы с числовым смещением, продвигаясь по 120 результатов за раз, поэтому можно циклически перебирать смещения, получать каждую страницу через Crawling API, парсить её с помощью той же функции и останавливаться, когда страница не возвращает объявлений. Поскольку все страницы результатов имеют одинаковую структуру элементов, уже написанный парсер работает для них всех без изменений.
async function scrapeAllPages(baseUrl, maxPages) { const all = []; for (let page = 0; page < maxPages; page++) { // Craigslist pages search results in steps of 120 const offset = page * 120; const pageUrl = `${baseUrl}&s=${offset}`; const html = await crawl(pageUrl); if (!html) break; const listings = parseListings(html); if (listings.length === 0) break; // no more results all.push(...listings); console.log(`Page ${page + 1}: ${listings.length} listings`); // Pace requests so you stay under the rate limit await new Promise((r) => setTimeout(r, 2000)); } return all; }
Точный параметр пагинации может меняться, поэтому проверьте несколько реальных ссылок «следующая страница» в браузере и сопоставьте паттерн. Важные привычки применимы к любой цели: перебирайте до исчерпания результатов и делайте короткую паузу между запросами, чтобы не перегружать сайт. Подробнее об этом стиле работы читайте в нашем руководстве по парсингу JavaScript-сайтов, а если вы отслеживаете цены во времени, наши заметки о веб-скрейпинге для отслеживания цен будут полезны.
Как оставаться незаблокированным
Craigslist противодействует парсерам, поэтому несколько привычек помогают сохранить работоспособность прогона. Они применимы к любой сложной цели.
- Соблюдайте темп запросов. Вводите задержку между получением страниц, а не гоните поиск в жёстком цикле. Распределение запросов по времени является главным фактором, удерживающим вас в пределах ограничений скорости Craigslist.
- Используйте ротацию. Пул резидентных IP распределяет запросы по множеству адресов реальных пользователей, чтобы ни один из них не превысил лимит или не получил CAPTCHA. Crawling API делает это за вас; если вы строите собственный стек, это та часть, которую нужно сделать правильно.
- Следите за кодами статуса. Прогон, начавший возвращать проверки или ответы, отличные от 200, сигнализирует, что текущего темпа или уровня IP уже недостаточно. Воспринимайте это как сигнал отступить, а не как шум, который можно игнорировать.
Для более широкой стратегии смотрите, как парсить сайты без блокировок. Если вам нужны аналогичные данные объявлений с других сайтов объявлений и аренды, тот же паттерн «получить, затем парсить» переносится на парсинг Apartments.com.
Законно ли парсить Craigslist?
Допустимость парсинга Craigslist зависит от условий использования Craigslist, вашей юрисдикции и того, что вы делаете с данными. На Craigslist это важнее, чем на большинстве сайтов: Craigslist активно противодействует автоматическому доступу и имеет длинную историю судебного преследования парсеров. Условия использования запрещают автоматический сбор данных, поэтому парсинг может нарушать эти условия независимо от тщательности вашего инструментария. Ни один из кодов здесь не меняет этого: он просто делает техническую часть работающей. Прочитайте Условия использования Craigslist и его robots.txt, соблюдайте подразумеваемые ограничения скорости и рассматривайте и то, и другое как границу для того, что вы собираете.
Это руководство намеренно ограничено публичными, неперсональными данными объявлений: заголовком, ценой, подсказкой о местоположении, датой публикации и ссылкой, которые любой видит на странице результатов поиска без входа в систему. Это отличается от персональных данных на платформе. Имя, номер телефона, электронная почта продавца или свободный текст, который он написал в своём объявлении, являются персональными данными. Не собирайте контактные данные продавцов, не составляйте профили размещающих объявления лиц и не публикуйте объявление, связанное с идентифицируемым лицом. Как только проект касается идентифицируемых лиц, применяется законодательство о конфиденциальности, такое как GDPR и CCPA, что выходит за рамки данной темы. Агрегированные факты, например «арендная плата за двухкомнатное жильё в этом районе составляет около X», вполне допустимы; список того, кто что продаёт, с контактными данными, нет.
Craigslist не публикует API общего назначения, хотя некоторые категории предлагают RSS-ленты для ограниченного санкционированного доступа. Там, где существует лента или явное соглашение об использовании данных, предпочтите их: санкционированный маршрут поставляется с чёткими условиями использования, а не с юридическим и техническим риском парсинга сайта, который ему противодействует. Если вы не уверены, допустимо ли то или иное использование, получите разрешение или соглашение об использовании данных, а не считайте молчание согласием, и сохраняйте как объём, так и область того, что вы собираете, соразмерными законной неперсональной исследовательской цели.
Ключевые выводы
- Craigslist противодействует парсерам. Жёсткий цикл с IP-адреса дата-центра подвергается ограничению скорости, проверке или блокировке, поэтому получайте страницу через доверенный ротирующийся IP и соблюдайте темп запросов.
- Crawling API берёт на себя сложную часть за один вызов. Он получает страницу через резидентные IP и обрабатывает CAPTCHA на стороне сервера, возвращая HTML для парсинга с помощью Cheerio.
-
Cheerio извлекает поля. Выбирайте каждый элемент
li.cl-static-search-resultвнутриol.cl-static-search-results, затем читайте заголовок, цену, местоположение, дату публикации и ссылку, ожидая дрейфа разметки по городам и со временем. - Пагинация и экспорт. Перебирайте параметр смещения Craigslist до исчерпания результатов, соблюдайте темп запросов и записывайте структурированные записи в форматы JSON и CSV.
- Оставайтесь в рамках публичных неперсональных данных. Собирайте только поля объявлений, никогда не трогайте контактные данные продавца или тела публикаций, связанных с конкретным лицом, соблюдайте ToS и robots.txt и помните, что GDPR и CCPA применяются, как только задействованы персональные данные.
Часто задаваемые вопросы
Есть ли у Craigslist официальный API?
Craigslist не предоставляет API общего назначения для доступа к своим данным. Некоторые разделы предлагают RSS-ленты для ограниченного доступа, но комплексного API нет. Там, где для ваших нужд существует санкционированная лента или соглашение об использовании данных, используйте их в предпочтение парсингу, поскольку они поставляются с чёткими условиями разрешённого использования.
Могу ли я создать парсер Craigslist на языке, отличном от JavaScript?
Да. В этом руководстве используется JavaScript с Cheerio, но тот же подход работает на любом языке. Crawling API имеет библиотеки и SDK для нескольких языков, поэтому вы получаете HTML одинаковым способом и парсите его любым HTML-парсером, который предпочитает ваш стек, например BeautifulSoup на Python. Селекторы и поля остаются теми же; меняется только синтаксис парсинга.
Мои селекторы возвращают пустые значения. Что изменилось?
Почти наверняка разметка Craigslist или различие между поддоменами городов. Откройте сохранённый response.html или живую страницу в инструментах разработчика браузера, убедитесь, что контейнер объявлений по-прежнему ol.cl-static-search-results с элементами li.cl-static-search-result, и обновите внутренние селекторы в parseListings. Периодическое обслуживание селекторов нормально для любого рабочего парсера.
Могу ли я получить блокировку при парсинге Craigslist?
Да, особенно на Craigslist, если вы отправляете слишком много запросов слишком быстро с одного адреса. Crawling API снижает этот риск, ротируя резидентные IP и обрабатывая CAPTCHA за вас, но вы всё равно должны соблюдать темп запросов, добавлять задержки между страницами и следить за кодами статуса, чтобы откатиться при появлении проверок.
Могу ли я парсить номера телефонов продавцов и контактные данные из объявлений?
Нет, и этот парсер специально создан так, чтобы этого не делать. Имя, номер телефона, электронная почта продавца и свободный текст, который он написал, являются персональными данными. Их сбор, создание профилей размещающих объявления лиц или публикация объявления, связанного с конкретным человеком, подпадают под законодательство о конфиденциальности, такое как GDPR и CCPA, и нарушают условия Craigslist. Ограничивайте сбор публичными неперсональными полями объявлений, рассмотренными здесь.
Для чего полезны данные Craigslist?
Публичные данные объявлений поддерживают рыночные исследования и анализ цен: отслеживание динамики арендных ставок и цен на подержанные товары по районам и мегаполисам, выявление нехватки предложения и изучение местного спроса во времени. Ценность заключается в агрегированном неперсональном сигнале по многим объявлениям, а не в личности или контактных данных какого-либо отдельного объявляющего лица.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
