Напиши парсер резюме со следующими данными с сайта...
Criado em: 25 de fevereiro de 2025
Criado em: 25 de fevereiro de 2025
Напиши парсер резюме со следующими данными с сайта avito в google sheets.
парсер должен содержать следующие данные:
ссылка на резюме
Сфера деятельности
Пол
Опыт работы
О себе
Парсер должен быть реализован таким образом, чтобы он мог искать только резюме с той сферой деятельности, которую я напишу в коде
Напиши парсер с объяснениями кода и как и что делать чтобы парсер работал
Ниже приведён пример того, как можно реализовать простой парсер резюме с сайта Avito в Google Sheets с помощью Google Apps Script. Данный пример носит ознакомительный характер, поскольку разметка Avito, а также политика сайта, могут меняться. Скрипт может перестать работать, если Avito обновит структуру страниц или введёт защиту от парсинга (например, Cloudflare). Кроме того, необходимо учитывать юридические аспекты и правила использования сайта Avito.
В редакторе Apps Script удалите всё содержимое (если там что-то есть) и вставьте следующий код:
javascript/** * Основная функция для парсинга резюме с Avito. * При запуске функция читает Google Sheets, очищает лист, * затем запрашивает страницу Avito по указанному запросу (сфере деятельности), * находит ссылки на резюме и по каждой ссылке вытягивает нужную информацию. */ function parseAvitoResumes() { // 1. Указываем лист, куда будем записывать данные var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); // 2. Очищаем лист от старых данных (при желании можно удалить эту строку, // если нужно накапливать результаты) sheet.clearContents(); // 3. Добавляем заголовки колонок sheet.appendRow(["Ссылка на резюме", "Сфера деятельности", "Пол", "Опыт работы", "О себе"]); // 4. Указываем сферу деятельности (поисковой запрос), // которую нужно искать на Avito. Например: "IT" var sphere = "IT"; // 5. Формируем URL для запроса резюме. Обратите внимание: // - Параметры поиска могут меняться // - Город (например, "moskva") тоже можно менять на любой другой // - p=1 (пагинация: первая страница) // - q=... ваш поисковой запрос // - Если нужна другая логика поиска (другие параметры), // меняйте URL согласно структуре Avito var url = "https://www.avito.ru/moskva/rezume?p=1&q=" + encodeURIComponent(sphere); // 6. Делаем запрос на Avito var response = UrlFetchApp.fetch(url, { "muteHttpExceptions": true, // Иногда помогает указать заголовок "User-Agent", чтобы не блокировалось Avito: "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" } }); // 7. Получаем HTML-код страницы var html = response.getContentText(); // 8. Находим все ссылки на конкретные объявления/резюме. // В данном случае – упрощённая регулярка, // которая ищет ссылки формата https://www.avito.ru/.../rezume/... // Важно понимать, что структура HTML может меняться. var resumeLinkRegex = /"(https:\/\/www\.avito\.ru\/[a-z0-9_]+\/rezume\/[^"]+)"/g; var links = []; var match; while ((match = resumeLinkRegex.exec(html)) !== null) { links.push(match[1]); } // 9. Удаляем дублирующиеся ссылки (Set избавляется от повторов) var uniqueLinks = Array.from(new Set(links)); // 10. Для каждой ссылки отдельно заходим на страницу объявления-резюме и извлекаем информацию uniqueLinks.forEach(function(link) { try { // a) Получаем HTML резюме var resumeResponse = UrlFetchApp.fetch(link, { "muteHttpExceptions": true, "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" } }); var resumeHtml = resumeResponse.getContentText(); // b) Извлекаем нужные данные с помощью регулярных выражений. // ВНИМАНИЕ: Avito часто меняет структуру HTML, поэтому селекторы ниже могут не работать. // Вам придётся актуализировать регулярки под реальные HTML-теги. var sphereValue = extractText(resumeHtml, /Сфера деятельности:\s*<[^>]*>(.*?)<\/[^>]*>/); var genderValue = extractText(resumeHtml, /Пол:\s*<[^>]*>(.*?)<\/[^>]*>/); var experienceValue = extractText(resumeHtml, /Опыт работы:\s*<[^>]*>(.*?)<\/[^>]*>/); var aboutMeValue = extractText(resumeHtml, /О себе:\s*<[^>]*>(.*?)<\/[^>]*>/); // c) Записываем данные в Google Sheets: одна строка = одно резюме sheet.appendRow([link, sphereValue, genderValue, experienceValue, aboutMeValue]); } catch (error) { // Если произошла ошибка при парсинге - можно её залогировать Logger.log("Ошибка при обработке ссылки: " + link + "; " + error); } }); } /** * Вспомогательная функция: извлекает текст по регулярному выражению (первый найденный фрагмент). * @param {string} source - строка HTML, где идёт поиск * @param {RegExp} regex - регулярное выражение для извлечения * @return {string} - найденное значение или пустая строка */ function extractText(source, regex) { var match = regex.exec(source); return match ? match[1].trim() : ""; }
parseAvitoResumes()
sphere
).UrlFetchApp.fetch(...)
headers
.Регулярные выражения
RegExp
. Он иногда ломается, если HTML-структура сложная.extractText(source, regex)
match[1].trim()
каждый раз.sphere = "IT"
Avito может блокировать запросы:
Регулярные выражения не находят нужные данные:
Мало результатов или их нет:
p=
, и т. д.).Ограничения Google Apps Script:
Данный пример демонстрирует общий подход к парсингу Avito (или любых других сайтов) с помощью Google Apps Script:
UrlFetchApp.fetch()
.При необходимости код можно расширять и дополнять:
Успешного парсинга!
Скрипт который ты написал не работает, строки пустые в Google sheets
Ниже приведён более актуальный пример, как можно настроить парсер резюме Avito в Google Sheets. Однако важно понимать:
Тем не менее, ниже показана базовая структура кода, которая на сегодняшний день чаще всего работает, если Avito не вернул вам вместо страницы капчу. Вам нужно будет регулярно актуализировать регулярные выражения под текущую HTML-разметку Avito.
Удалите всё содержимое и вставьте следующий скрипт. После вставки сохраните скрипт (Ctrl+S).
javascript/** * Основная функция парсинга. * 1) Очищает текущий лист и ставит заголовки. * 2) Ищет резюме Avito по заданной "сфере деятельности" (поисковому запросу). * 3) Находит ссылки объявлений (резюме). * 4) По каждой ссылке парсит нужные данные: Сфера деятельности, Пол, Опыт работы, О себе. */ function parseAvitoResumes() { var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); // Очищаем лист от предыдущих данных sheet.clearContents(); // Добавляем заголовки sheet.appendRow(["Ссылка на резюме", "Сфера деятельности", "Пол", "Опыт работы", "О себе"]); // Указываем сферу деятельности (строка для поиска). Пример: "IT" // Можно написать "строительство", "менеджер", "программист" и т.д. var sphere = "IT"; // Формируем URL для поиска. Пример для Москвы: // p=1 (первая страница), q=... (поисковой запрос). // Можно менять регион (moskva → sankt-peterburg или убрать вовсе) и подставлять другие параметры. var url = "https://www.avito.ru/moskva/rezume?p=1&q=" + encodeURIComponent(sphere); // Пробуем получить HTML-код первой страницы var html = fetchPage(url); if (!html) { Logger.log("Не удалось получить HTML с Avito. Возможно, сработала защита или нет результатов"); return; } // Шаг 1: Ищем ссылки на резюме в результатах поиска. // Для этого нужно посмотреть, как выглядят ссылки в выдаче. // Как правило, это что-то вроде <a href="/moskva/rezume/XXXXXXXXX" ...class="iva-item-titleStep-...". // Ниже – примерное регулярное выражение, вы можете скорректировать: var linkRegex = /<a[^>]*class="[^"]*iva-item-titleStep[^"]*"[^>]*href="([^"]+)"[^>]*>/g; var links = []; var match; while ((match = linkRegex.exec(html)) !== null) { // Ссылки на странице идут относительные: "/moskva/rezume/..." // Превращаем в абсолютные: "https://www.avito.ru + /moskva/rezume/..." var relativeLink = match[1]; // Защита от случайных ссылок на "javascript:void(0)" и т.п. if (relativeLink.startsWith("/")) { links.push("https://www.avito.ru" + relativeLink); } } // Убираем дубли, если есть var uniqueLinks = Array.from(new Set(links)); // Шаг 2: Для каждой найденной ссылки вытаскиваем нужные параметры: Сфера, Пол, Опыт, О себе uniqueLinks.forEach(function(link) { var resumeHtml = fetchPage(link); if (!resumeHtml) { // Если не удалось загрузить страницу, пропускаем Logger.log("Не удалось загрузить резюме: " + link); return; } // Извлекаем данные. Структура Avito может отличаться, // поэтому регулярки нужно уточнять под реальный HTML. // Обычно нужные поля лежат внутри: // <span class="params-paramsList__paramTitle-...">Сфера деятельности</span> // <span class="params-paramsList__paramValue-...">IT</span> // и т.п. var sphereValue = parseParamValue(resumeHtml, "Сфера деятельности"); var genderValue = parseParamValue(resumeHtml, "Пол"); var experienceValue = parseParamValue(resumeHtml, "Опыт работы"); var aboutMeValue = parseParamValue(resumeHtml, "О себе"); // Добавляем строку в Google Sheets sheet.appendRow([link, sphereValue, genderValue, experienceValue, aboutMeValue]); }); } /** * Вспомогательная функция для загрузки страницы Avito с подстановкой нужных заголовков. * Avito может блокировать без нормального User-Agent. * Возвращает текст HTML или пустую строку, если ошибка. */ function fetchPage(url) { try { var response = UrlFetchApp.fetch(url, { "muteHttpExceptions": true, "headers": { // User-Agent часто важен, чтобы не заблокировалось. Можно менять при необходимости. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36" } }); // Если Avito вернул редирект на капчу или ошибку, код будет не 200 if (response.getResponseCode() === 200) { return response.getContentText(); } else { Logger.log("fetchPage: Код ответа " + response.getResponseCode() + " для URL " + url); return ""; } } catch (e) { Logger.log("fetchPage: Ошибка при загрузке " + url + ": " + e); return ""; } } /** * Парсим значение из HTML по имени параметра, например: "Сфера деятельности". * Ищем блок вида: * <span class="params-paramsList__paramTitle-...">Сфера деятельности</span> * <span class="params-paramsList__paramValue-...">нужное_значение</span> * * Возвращаем найденное значение или пустую строку, если не нашли. */ function parseParamValue(html, title) { // Шаблон: ищем сначала <span ...>title</span>, затем <span ...>(.*?)</span> // Примерно так: // <span ...>Сфера деятельности</span><span ...>IT</span> // // При необходимости усложняем шаблон, если Avito вставляет лишние теги // или используем "(.|\n)*?" между span'ами, если встречаются переносы строк и т.д. var re = new RegExp( '<span[^>]*>(?:\\s*|)' + title + '(?:\\s*|)<\\/span>\\s*<span[^>]*>([^<]+)<\\/span>', 'i' ); var match = re.exec(html); return match ? match[1].trim() : ""; }
parseAvitoResumes() – основная функция:
sphere
(сфера деятельности).linkRegex
) ищет ссылки на резюме.parseParamValue()
.fetchPage(url) – вспомогательная функция:
UrlFetchApp.fetch()
с заголовком User-Agent
, чтобы повысить шанс корректного ответа от Avito.200
, возвращает пустую строку.parseParamValue(html, title) – ищет в HTML блок вида:
html<span ...>title</span><span ...>Значение</span>
title
= «Сфера деятельности».<span ...>
.Обратите внимание: Avito может вставлять дополнительные теги, переносы строк и т. п., поэтому возможно придётся усложнить регулярку, например:
regex/<span[^>]*>\s*Сфера деятельности\s*<\/span>\s*<span[^>]*>([\s\S]*?)<\/span>/
([\s\S]
вместо .
позволяет матчить переносы строк).
Ctrl+S
).parseAvitoResumes
и нажмите «Выполнить».Avito вернул капчу (вместо HTML – страница с проверкой «Я не робот»).
User-Agent
или запускать реже.Поисковый запрос не дал результатов.
Верстка Avito изменилась.
Лимиты Google Apps Script (время выполнения ~6 минут и т. д.).
p=2
, p=3
, ... и следить, чтобы не превысить лимиты.Скрипт выше – это рабочий пример, но он может потребовать доработок с учётом изменений на сайте. Если строки всё ещё остаются пустыми, значит сайт вернул либо капчу, либо поменял верстку. Тогда нужно «подглядывать» в исходный HTML и править регулярные выражения.
Информация Не удалось получить HTML с Avito. Возможно, сработала защита или нет результатов
Такое выдало после твоего скрипта
Это означает, что Avito возвращает не HTML-страницу списка резюме, а что-то другое (часто капчу, редирект или ошибку). Google Apps Script видит не код 200 и выводит сообщение «Не удалось получить HTML с Avito». Ниже несколько идей, как можно попробовать обойти подобные блокировки, но гарантии нет — парсинг Avito может быть затруднён из-за защиты сайта.
Временно добавьте в функцию fetchPage
логи, чтобы понять, какой именно код ответа вы получаете и не возвращается ли страница-капча:
jsfunction fetchPage(url) { try { var response = UrlFetchApp.fetch(url, { "muteHttpExceptions": true, "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" } }); var code = response.getResponseCode(); var text = response.getContentText(); // Выводим в логи первые 500 символов тела ответа и сам код Logger.log("URL: " + url + " => code: " + code); Logger.log("Ответ (кусок): " + text.substring(0, 500)); if (code === 200) { return text; } else { return ""; } } catch (e) { Logger.log("Ошибка при загрузке " + url + ": " + e); return ""; } }
403
, 429
, 302
, 301
, или вместо HTML там «Captcha», значит Avito блокирует.Иногда помогает:
Utilities.sleep(2000);
между запросами), чтобы не «бомбить» сайт слишком часто.js{ "muteHttpExceptions": true, "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } }
Cookie
, который отправляется браузером на avito.ru, и скопировать его в ваш запрос.Даже с этим подходом Avito может продолжить блокировать при большом количестве запросов.
Avito может чаще показывать капчу при запросах из дата-центров (в том числе из Google Cloud).
Решения:
Если вам нужно добыть 5–10 резюме, проще может оказаться:
Но это, конечно, не «чистый» автоматический парсинг.
Есть коммерческие API и SaaS-сервисы, которые специализируются на обходе капч и блокировок (Bright Data, ScrapingBee, ScraperAPI и пр.).
GET https://www.avito.ru/...
,Если цель — протестировать несколько вакансий вручную, иногда быстрее взять URL и распарсить его любым удобным офлайн-скриптом (или даже вручную) — по причине того, что Avito старается от парсинга защититься.
Информация URL: https://www.avito.ru/moskva/rezume?p=1&q=IT => code: 200
12:53:55 Информация Ответ (кусок): <!DOCTYPE html><html lang="ru"><head><link rel="preconnect" href="https://www.avito.st" crossorigin="anonymous"/><link rel="preconnect" href="https://www.google-analytics.com"/><link rel="preconnect" href="https://yastatic.net"/><link rel="preload" href="https://www.avito.st/s/common/assets/fonts/manrope/manrope-medium.woff2" as="font" type="font/woff2" crossorigin="crossorigin"/><link rel="preload" href="https://www.avito.st/s/common/assets/fonts/manrope/manrope-bold.woff2" as="font" type="font
вот код ответа
Судя по логам, Avito действительно вернул код 200
и начал отдаваться HTML (что видно по <!DOCTYPE html><html lang="ru">...
). Значит, сам запрос в fetchPage
прошёл успешно, а проблема, скорее всего, в том, что регулярные выражения не находят нужные ссылки (или нужные блоки) в структуре верстки. В итоге скрипт может собирать пустой массив ссылок, и на выходе кажется, что «не удалось получить данные».
Ниже разберём основные причины и как попытаться это исправить.
В моём предыдущем примере был такой шаблон:
jsvar linkRegex = /<a[^>]*class="[^"]*iva-item-titleStep[^"]*"[^>]*href="([^"]+)"[^>]*>/g;
Однако Avito регулярно меняет классы. При просмотре кода страницы (через «Просмотр кода HTML» или инструменты разработчика) обычно видно, что ссылки на объявления (и на резюме) часто содержат атрибут data-marker="item-title"
или похожие. Можно попробовать более универсальную регулярку:
js// Ищем якорь вида: <a ... data-marker="item-title" ... href="..." var linkRegex = /<a[^>]*data-marker="item-title"[^>]*href="([^"]+)"[^>]*>/g;
Или даже более «мягкий» вариант:
js// Ищем <a ... href="..." где внутри есть "rezume" — но тогда могут попадаться лишние ссылки: var linkRegex = /<a\s+[^>]*href="([^"]*\/rezume\/[^"]*)"[^>]*>/g;
Посмотрите, какая именно разметка у ссылок в выдаче резюме. К примеру, если вы открыли в браузере страницу
https://www.avito.ru/moskva/rezume?p=1&q=IT,
и видите что-то вроде:
html<a class="link-link-39EVK link-design-default-2sPEv ..." data-marker="item-title" href="/moskva/rezume/it_sfera_123456789" ...> ... </a>
Тогда регулярка с data-marker="item-title"
должна подхватить эти ссылки.
Важно: Если ваш результат links
в коде оказывается []
(пустым), то и при дальнейшей итерации ничего не будет найдено.
parseAvitoResumes()
(фрагмент)js// ... Ваши объявления переменных выше ... // Пробуем ссылку: var url = "https://www.avito.ru/moskva/rezume?p=1&q=" + encodeURIComponent(sphere); // Получаем HTML var html = fetchPage(url); if (!html) { Logger.log("Не удалось получить HTML с Avito. Возможно, сработала защита или нет результатов"); return; } // Ищем ссылки на резюме. // Например, ищем якоря с data-marker="item-title" var linkRegex = /<a[^>]*data-marker="item-title"[^>]*href="([^"]+)"[^>]*>/g; var links = []; var match; while ((match = linkRegex.exec(html)) !== null) { var relativeLink = match[1]; // Проверяем, что ссылка начинается со слэша, а не "javascript:" и т.п. if (relativeLink.startsWith("/")) { links.push("https://www.avito.ru" + relativeLink); } } Logger.log("Найдено ссылок: " + links.length); Logger.log(links);
"Найдено ссылок: X"
. Если там 0
, значит регулярка не подходит под актуальный HTML.links
.Даже если ссылки нашлись, дальнейшее регулярное выражение для Сфера деятельности, Пол, Опыт работы тоже может не совпадать с реальным HTML. Возможно, в карточке резюме эти поля расположены совсем по-другому.
Если Avito в детальной карточке сейчас выглядит так (примерно в «Инструментах разработчика»):
html<div class="params-paramsList__item"> <span class="params-paramsList__paramTitle">Сфера деятельности</span> <span class="params-paramsList__paramValue">IT</span> </div>
то ваш текущий шаблон:
jsvar re = new RegExp( '<span[^>]*>(?:\\s*|)' + title + '(?:\\s*|)<\\/span>\\s*<span[^>]*>([^<]+)<\\/span>', 'i' );
может сработать.
Но если в верстке есть ещё какие-то вложенные теги или другие классы/атрибуты, надо подправлять (например [\s\S]*?
между тегами). Пример:
jsfunction parseParamValue(html, title) { // Учитываем любые пробелы/переносы строк: var re = new RegExp( '<span[^>]*>\\s*' + title + '\\s*<\\/span>\\s*<span[^>]*>([\\s\\S]*?)<\\/span>', 'i' ); var match = re.exec(html); return match ? match[1].trim() : ""; }
Если ни один из вариантов не находит нужный текст — значит, в исходном HTML на самом деле другие теги/классы/тексты. Нужно смотреть конкретный HTML той карточки, которую вы парсите, и писать регулярку под неё.
Часть контента Avito может догружать динамически (React/SPA). Если вы видите в HTML, что искомых блоков нет (только «контейнеры» и скрипты), значит сайт рендерит объявления через JS на лету.
В этом случае простой парсер, который смотрит статический HTML, ничего не найдёт. Приходится:
<script>
), где содержится информация об объявлениях.Но в большинстве случаев Avito выдаёт хотя бы «каркас» объявлений даже без JS. Вам нужно проверить, не пустая ли в итоге та часть, где должны быть теги <a ... data-marker="item-title">
.
<a ... href=...>
для резюме.RegExp
исходя из реального кода.0
, экспериментируйте с другой регуляркой или ищите альтернативные маркеры (например, data-marker="..."
).Когда ссылки найдутся — уже переходите к парсингу внутренних параметров резюме. И там та же схема: открываете HTML детальной страницы, ищете, как реально называется блок для «Сфера деятельности», «Пол» и т.п., и пишете под это регулярку либо парсер.
Если Avito начнёт возвращать капчу — тогда нужно уже искать обходные пути (куки, прокси, задержки и т.д.). Но раз у вас уже есть «код 200» и DOCTYPE, значит пока что Avito HTML отдаёт, просто регулярки не совпадают с содержимым.
Сделай полностью рабочий код со всей информацией выше то что я тебе сказал
Ниже приведён полный пример скрипта на Google Apps Script, который пытается:
Обратите внимание:
parseAvitoResumes()
.javascript/***************************************************** * ГЛАВНАЯ ФУНКЦИЯ: запускаем, чтобы спарсить резюме * Автоматически: * - Очищает лист * - Ищет резюме по запросу (сфера=IT) на 1-й странице * - Собирает ссылки, парсит детали * - Записывает в Google Sheets *****************************************************/ function parseAvitoResumes() { // 1. Получаем активный лист var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); // Очищаем (если надо сохранять старые данные — уберите .clearContents()) sheet.clearContents(); // 2. Заголовки столбцов sheet.appendRow(["Ссылка на резюме", "Сфера деятельности", "Пол", "Опыт работы", "О себе"]); // 3. Указываем «сферу деятельности» (поисковое слово), например "IT" или "программист" var sphere = "IT"; // 4. Формируем URL поиска по Москве, страница 1 // Можно изменить город (moskva → sankt-peterburg или другой) // p=1 (первая страница), q=... (слово для поиска) var url = "https://www.avito.ru/moskva/rezume?p=1&q=" + encodeURIComponent(sphere); // 5. Загружаем HTML этой страницы var html = fetchPage(url); if (!html) { Logger.log("Не удалось получить HTML с Avito. Возможно, сработала защита или нет результатов."); return; } // 6. Ищем ссылки на резюме в HTML. // По текущему состоянию Avito обычно добавляет data-marker="item-title" для заголовков объявлений. // Регулярка ищет <a ... data-marker="item-title" ... href="что-то" ...> var linkRegex = /<a[^>]*data-marker="item-title"[^>]*href="([^"]+)"[^>]*>/g; var links = []; var match; while ((match = linkRegex.exec(html)) !== null) { var relativeLink = match[1]; // например: /moskva/rezume/it_sfera_123456789 // Проверяем, что ссылка не "javascript:", не "#" и т.п. if (relativeLink.startsWith("/")) { // Превращаем относительный путь в полный var fullLink = "https://www.avito.ru" + relativeLink; links.push(fullLink); } } // Убираем дубликаты (Set → Array) var uniqueLinks = Array.from(new Set(links)); Logger.log("Найдено ссылок на резюме: " + uniqueLinks.length); // 7. Для каждой ссылки отдельно загружаем страницу и вытаскиваем нужную информацию uniqueLinks.forEach(function(link) { // Парсим HTML детальной страницы резюме var resumeHtml = fetchPage(link); if (!resumeHtml) { Logger.log("Не удалось загрузить резюме: " + link); return; } // Извлекаем четыре поля: Сфера деятельности, Пол, Опыт работы, О себе // Порядок – как в заголовке: Sphere, Gender, Experience, AboutMe var sphereValue = parseParamValue(resumeHtml, "Сфера деятельности"); var genderValue = parseParamValue(resumeHtml, "Пол"); var experienceValue = parseParamValue(resumeHtml, "Опыт работы"); var aboutMeValue = parseParamValue(resumeHtml, "О себе"); // Записываем итоговую строку sheet.appendRow([ link, sphereValue, genderValue, experienceValue, aboutMeValue ]); // Можно добавить небольшую задержку, чтобы не «бомбить» сайт слишком часто: // Utilities.sleep(1000); // 1 секунда }); } /***************************************************** * fetchPage(url): загружаем HTML-страницу Avito * с нужным User-Agent. Если код ответа = 200, * возвращаем текст HTML, иначе "". *****************************************************/ function fetchPage(url) { try { var response = UrlFetchApp.fetch(url, { muteHttpExceptions: true, headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } }); var code = response.getResponseCode(); var text = response.getContentText(); // Если ответ OK (200) - возвращаем HTML, иначе "" if (code === 200) { return text; } else { Logger.log("fetchPage: Код ответа HTTP " + code + " для URL " + url); return ""; } } catch (e) { Logger.log("fetchPage: Ошибка при загрузке " + url + ": " + e); return ""; } } /***************************************************** * parseParamValue(html, title): * Ищем значение параметра по названию (title), например: * <span ...>Сфера деятельности</span> * <span ...>IT</span> * * В некоторых карточках это выглядит примерно так: * <div class="params-paramsList__item"> * <span class="params-paramsList__paramTitle">Сфера деятельности</span> * <span class="params-paramsList__paramValue">IT</span> * </div> * * Если в верстке добавятся переносы/внутренние теги, надо корректировать RegExp. *****************************************************/ function parseParamValue(html, title) { // Регулярка ищет: // <span ...>title</span> (что-то) <span ...>(ЗНАЧЕНИЕ)</span> // Позволяем пропускать пробелы/переносы между тегами ( [\\s\\S]*? ) // "i" - регистронезависимо. // // ВАЖНО: Если Avito вставит доп.теги, может потребоваться усложнить паттерн. var re = new RegExp( '<span[^>]*>\\s*' + title + '\\s*<\\/span>\\s*<span[^>]*>([\\s\\S]*?)<\\/span>', 'i' ); var match = re.exec(html); if (match) { // Чистим пробелы return match[1].trim(); } else { return ""; } }
parseAvitoResumes():
https://www.avito.ru/moskva/rezume?p=1&q=IT
.fetchPage(url)
— загружает HTML, возвращает строку (или пустую строку, если ошибка).linkRegex
парсит все ссылки на объявления (где data-marker="item-title"
).fetchPage(link)
→ ищем внутри Сфера деятельности
, Пол
, Опыт работы
, О себе
.fetchPage(url):
UrlFetchApp.fetch
.User-Agent
, Accept-Language
, т. п., чтобы выглядеть «как браузер».200
, возвращает содержимое. Иначе — пусто.parseParamValue(html, title):
"<span>title</span><span>(значение)</span>"
.Сайт Avito меняет классы, структуру HTML. Придётся посмотреть исходный код нужной страницы и поправить регулярки (особенно parseParamValue
), если <span>
заменили на <div>
, добавили вложенные теги и т. д.
Вместо HTML может прийти капча — тогда код будет 200, но в тексте содержатся поля вида: «Вы подозрительно быстро делаете запросы». Иногда Avito ставит редиректы или HTTP 403/429. Тогда нужно:
Utilities.sleep(2000)
после каждого запроса).Данные рендерятся JavaScript-ом (SPA) — иногда Avito может подгружать объявления динамически. Тогда простой парс статичного HTML не увидит их. Нужно искать данные внутри <script>
в виде JSON или использовать инструменты вроде Puppeteer/Selenium.
Посмотрите исходный HTML резюме. Для этого:
Уточните регулярки в parseParamValue
. Например, если теперь сайт делает так:
html<span class="params-paramsList__paramTitle">Сфера деятельности</span> <div class="params-paramsList__paramValue"><span>IT</span></div>
тогда придётся менять RegExp, чтобы он ловил </span>\s*<div ...>([\\s\\S]*?)</div>
, и внутри ещё <span>...</span>
. Или вручную доставать <span>IT</span>
.
Смотрите логи (Меню → «Просмотр» → «Журналы»). Добавляйте Logger.log(...)
куда нужно.
Скрипт, приведённый выше, является «рабочим примером» для базовой структуры парсинга Avito в Google Sheets. Но из-за постоянных изменений на сайте Avito может понадобиться регулярное обновление регулярных выражений, учёт капчи и т. п.
Если Avito вернул капчу или структуру не совпадает — придётся дополнительно адаптировать этот пример.
пишет код ответа 429 и не удалось получить html с avito. исправь и направь полностью код
Код ответа 429 означает, что сервер Avito возвращает «Too Many Requests». Это типичная защита от чрезмерного или подозрительного количества запросов. В Google Apps Script такое особенно часто встречается, поскольку запросы идут с IP-адресов Google, и Avito может считать их бот-трафиком.
Полностью обойти защиту одного лишь изменением кода в Google Apps Script невозможно, но можно попробовать:
Однако даже при этих мерах Avito может продолжать выдавать 429, если распознаёт, что это автоматический парсинг. Часто помогает «настоящее» решение через прокси или антикапчу-сервисы, но в Google Apps Script реализовать это сложно.
Ниже — пример «максимально дружелюбного» кода в рамках Apps Script:
Даже с этими мерами нет гарантии, что Avito не заблокирует запросы. Но это даёт шанс.
js/*********************************************************** * ГЛАВНАЯ ФУНКЦИЯ: parseAvitoResumes * 1) Очищает лист * 2) Ищет резюме по заданному поисковому слову (sphere) * 3) Собирает ссылки, парсит поля (Сфера, Пол, Опыт, О себе) * 4) Записывает в Google Sheets ***********************************************************/ function parseAvitoResumes() { var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); // Очистим (можно убрать, если хотите накапливать данные) sheet.clearContents(); // Запишем шапку sheet.appendRow(["Ссылка на резюме", "Сфера деятельности", "Пол", "Опыт работы", "О себе"]); // Поисковый запрос (сфера) var sphere = "IT"; // ← Замените на нужное слово // Формируем URL. Пример: Москва, страница 1 var url = "https://www.avito.ru/moskva/rezume?p=1&q=" + encodeURIComponent(sphere); // Пробуем загрузить HTML (список объявлений) var html = fetchPage(url); if (!html) { Logger.log("Не удалось получить HTML с Avito (скорее всего 429 или другая блокировка)."); return; } // Ищем ссылки на резюме: // <a ... data-marker="item-title" ... href="..."> var linkRegex = /<a[^>]*data-marker="item-title"[^>]*href="([^"]+)"[^>]*>/g; var links = []; var match; while ((match = linkRegex.exec(html)) !== null) { var relativeLink = match[1]; // Если ссылка относительная (/moskva/rezume/...), дополняем домен if (relativeLink.startsWith("/")) { links.push("https://www.avito.ru" + relativeLink); } } // Убираем дубликаты var uniqueLinks = Array.from(new Set(links)); Logger.log("Найдено ссылок: " + uniqueLinks.length); // Перебираем каждую ссылку, парсим детали uniqueLinks.forEach(function(link) { // Задержка 3 секунды перед каждым запросом (можете увеличить, если продолжает выдавать 429) Utilities.sleep(3000); var resumeHtml = fetchPage(link); if (!resumeHtml) { Logger.log("Не удалось загрузить детальную страницу резюме: " + link); return; } // Ищем 4 поля: Сфера деятельности, Пол, Опыт работы, О себе var sphereValue = parseParamValue(resumeHtml, "Сфера деятельности"); var genderValue = parseParamValue(resumeHtml, "Пол"); var experienceValue = parseParamValue(resumeHtml, "Опыт работы"); var aboutMeValue = parseParamValue(resumeHtml, "О себе"); // Добавляем строку в таблицу sheet.appendRow([ link, sphereValue, genderValue, experienceValue, aboutMeValue ]); }); } /*********************************************************** * fetchPage(url): * - Делаем GET-запрос с несколькими попытками (до 3), * - случайным User-Agent из списка * - задержкой перед повтором (если код 429). * * Возвращает HTML-текст, либо "" (если не получилось). ***********************************************************/ function fetchPage(url) { var MAX_ATTEMPTS = 3; var attempt = 0; // Набор User-Agent'ов (браузеры, мобильные, и т.п.) // Можно расширить список; Avito может по-прежнему блокировать. var userAgents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", "Mozilla/5.0 (iPhone; CPU iPhone OS 15_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1", "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_6_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36" ]; while (attempt < MAX_ATTEMPTS) { attempt++; // Случайный выбор User-Agent var randomUA = userAgents[Math.floor(Math.random() * userAgents.length)]; Logger.log("fetchPage: Запрос #%s к URL=%s с User-Agent=%s", attempt, url, randomUA); try { // Перед каждым запросом небольшая задержка, чтобы не слишком часто бить сайт Utilities.sleep(1000); var response = UrlFetchApp.fetch(url, { muteHttpExceptions: true, headers: { "User-Agent": randomUA, "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } }); var code = response.getResponseCode(); var text = response.getContentText(); if (code === 200) { // Если вернулся «200 OK», считаем что получили страницу. return text; } else if (code === 429) { // Too Many Requests — ждём дольше и повторяем Logger.log("fetchPage: 429 Too Many Requests. Ждем 10 сек и пробуем еще (попытка " + attempt + ")."); Utilities.sleep(10000); // 10 секунд «отдыха» перед повтором } else { // Другие коды: 403, 404 и т.п. Logger.log("fetchPage: Код HTTP=" + code + " (не 200). Останавливаемся."); return ""; } } catch (e) { // Если какая-то ошибка на уровне UrlFetchApp Logger.log("fetchPage: Ошибка при загрузке URL=" + url + "; " + e); return ""; } } // Если после MAX_ATTEMPTS так и не удалось получить 200, возвращаем пустую строку return ""; } /*********************************************************** * parseParamValue(html, title): * Ищет в HTML блок формата: * <span>title</span><span>значение</span> * Но Avito может менять структуру. Если не находит, * возвращает "". ***********************************************************/ function parseParamValue(html, title) { // Пример: "<span>Сфера деятельности</span>\s*<span>IT</span>" // Если в Avito добавится <div> или перенос строк, придётся расширять regexp. var re = new RegExp( '<span[^>]*>\\s*' + title + '\\s*<\\/span>\\s*<span[^>]*>([\\s\\S]*?)<\\/span>', 'i' ); var match = re.exec(html); return match ? match[1].trim() : ""; }
429 Too Many Requests:
fetchPage
мы сделали до 3 попыток. Если Avito ответил 429, делаем Utilities.sleep(10000)
(10 секунд) и пробуем заново.Случайный User-Agent:
userAgents
можно добавить больше строк.Задержка между запросами:
parseAvitoResumes
в цикле по ссылкам стоит Utilities.sleep(3000)
, т.е. 3 секунды перед загрузкой следующей карточки резюме.fetchPage
стоит Utilities.sleep(1000)
перед каждым запросом, плюс 10 секунд при 429.parseParamValue:
<span>title</span><span>value</span>
.<div>
вместо <span>
, или вложенные теги), нужно корректировать регулярку.Ограничения Google Apps Script:
parseParamValue
.Этот скрипт — максимально гибкий вариант для Apps Script с учётом того, что Avito отвечает 429:
Но, повторимся, гарантий нет: Avito может жёстко блокировать Google IP. Если при этом продолжится 429, надо переходить на более «тяжёлые» методы (прокси, капча-сервисы, реальный браузер).