Как написать парсер для авито

Парсер Авито

Парсер позволяет искать объявления по запросу, а также настраивать такие параметры как способ сортировки, поиск только по названию объявлений, поиск объявлений с фото, поиск среди частных или принадлежащих компаниям объявлений.

Установка и использование

Для работы парсера необходим интерпретатор Python 3 и библиотеки soupsieve, beautifulsoup4, certifi, chardet, idna, lxml, urllib3, requests.

Для установки нужных библиотек выполните pip3 install -r requirements.txt

Модуль avito_parser (avito_parser.py)

Все объявления с «трактор мтз» в названии, отсортированные по дате:

from avito_parser import get_all_ads

for ad in get_all_ads('трактор мтз', sort_by='date', by_title=True):
    print(ad)

Вывод:

{'Title': 'Трактор мтз 82.1 в Москве', 'Link': 'https://www.avito.ru/moskva/gruzoviki_i_spetstehnika/traktor_mtz_82.1_1534975416', 'Price': 440000, 'Date': '2019-01-14 13:52'}
{'Title': 'Стартер для трактора мтз, Беларусь в Москве', 'Link': 'https://www.avito.ru/moskva/zapchasti_i_aksessuary/starter_dlya_traktora_mtz_belarus_1685911202', 'Price': 5500, 'Date': '2019-01-14 11:47'}
{'Title': 'Аренда Трактор мтз 82.1 с щеткой и отвалом в Зеленограде', 'Link': 'https://www.avito.ru/moskva_zelenograd/predlozheniya_uslug/arenda_traktor_mtz_82.1_s_schetkoy_i_otvalom_915721521', 'Price': 1000, 'Date': '2019-01-14 11:46'}
{'Title': 'Продается Трактор мтз 82.1 "Беларус" в Москве', 'Link': 'https://www.avito.ru/moskva/gruzoviki_i_spetstehnika/prodaetsya_traktor_mtz_82.1_belarus_1638465257', 'Price': 700000, 'Date': '2019-01-14 10:35'}
{'Title': 'Аренда трактора мтз-82 (отвал, щетка) уборка снега в Москве', 'Link': 'https://www.avito.ru/moskva/predlozheniya_uslug/arenda_traktora_mtz-82_otval_schetka_uborka_snega_1579061341', 'Price': 1000, 'Date': '2019-01-14 08:20'}
...

Интерфейс командной строки (avito_parser_cli.py)

python3 avito_parser_cli.py -h
usage: avito_parser_cli.py [-h] [-u OUTPUT] [-s {date,price,price_desc}] [-t]
                           [-f] [-w {private,company}] [-m MINPRICE]
                           [-M MAXPRICE] [-d STARTDATE] [-e ENDDATE] [-a]
                           query

positional arguments:
  query                 Поисковый запрос

optional arguments:
  -h, --help            show this help message and exit
  -u OUTPUT, --output OUTPUT
                        Название cvs файла для вывода (например output.csv)
  -s {date,price,price_desc}, --sortby {date,price,price_desc}
                        date -- сортировка по дате; price -- сортировка по
                        цене; price_desc -- сортировка по убыванию цены
  -t, --bytitle         Поиск только в названиях объявлений
  -f, --withimages      Только объявления с картинками
  -w {private,company}, --owner {private,company}
                        private -- только частные объявления; company –-
                        только объявления принадлежащие компаниям
  -m MINPRICE, --minprice MINPRICE
                        Минимальная цена
  -M MAXPRICE, --maxprice MAXPRICE
                        Максимальная цена
  -d STARTDATE, --startdate STARTDATE
                        Только объявления новее этой даты; Формат – 2019-01-10
                        или 2019-01-10 15:29
  -e ENDDATE, --enddate ENDDATE
                        Только объявления созданные до этой даты; Формат –
                        2019-01-10 или 2019-01-10 15:29
  -a, --statistics      Выводить топ 5 объявлений и общее количество

Все объявления с «трактор мтз» в названии, с минимальной ценой 300000₽, отсортированные по дате:

python3 avito_parser_cli.py "трактор мтз" -t -m 300000 -u 'traktor_mtz_bydate_minprice300000.csv' -s 'date' -a
1. Трактор мтз - 82.1 Беларус 82 мтз 82 2019 г.в в Москве
Ссылка: https://www.avito.ru/moskva/gruzoviki_i_spetstehnika/traktor_mtz_-_82.1_belarus_82_mtz_82_2019_g.v_223835504
Цена: 1250000
Дата: 2019-01-13 12:40
2. Мтз 82 трактора в Москве
Ссылка: https://www.avito.ru/moskva/gruzoviki_i_spetstehnika/mtz_82_traktora_928803851
Цена: 1000000
Дата: 2019-01-11 21:29
3. Трактор Мтз 320 для любых работ в Москве
Ссылка: https://www.avito.ru/moskva/gruzoviki_i_spetstehnika/traktor_mtz_320_dlya_lyubyh_rabot_1418969020
Цена: 490000
Дата: 2019-01-11 12:39
4. Трактор мтз-82 с фронтальным погрузчиком и щеткой в Москве
Ссылка: https://www.avito.ru/moskva/gruzoviki_i_spetstehnika/traktor_mtz-82_s_frontalnym_pogruzchikom_i_schetkoy_935196915
Цена: 920000
Дата: 2019-01-10 12:16
5. Трактор мтз-82.1 продам в Москве
Ссылка: https://www.avito.ru/moskva/gruzoviki_i_spetstehnika/traktor_mtz-82.1_prodam_1033277004
Цена: 400000
Дата: 2019-01-09 13:15
Всего объявлений: 19

Результат сохранен в traktor_mtz_bydate_minprice300000.csv

Все объявления по запросу «audi tt», созданные в январе, отсортированные по цене и принадлежащие компаниям (автодилерам):

python3 avito_parser_cli.py 'audi tt' -u 'auditt_byprice_january.csv' -s 'price' -w 'company' -d '2019-01-01' -e '2019-01-31' -a 
1. Audi TT, 1999 в Москве
Ссылка: https://www.avito.ru/moskva/avtomobili/audi_tt_1999_1194048826
Цена: 319000
Дата: 2019-01-10 15:29
2. Audi TT, 2006 в Москве
Ссылка: https://www.avito.ru/moskva/avtomobili/audi_tt_2006_1265609259
Цена: 530000
Дата: 2019-01-10 21:59
3. Audi TT, 2006 в Москве
Ссылка: https://www.avito.ru/moskva/avtomobili/audi_tt_2006_1592038636
Цена: 629000
Дата: 2019-01-11 20:26
4. Audi TT, 2004 в Москве
Ссылка: https://www.avito.ru/moskva/avtomobili/audi_tt_2004_1104288356
Цена: 690000
Дата: 2019-01-01 16:23
5. Audi TT, 2015 в Москве
Ссылка: https://www.avito.ru/moskva/avtomobili/audi_tt_2015_1564992403
Цена: 1800000
Дата: 2019-01-05 08:30
Всего объявлений: 5

Результат сохранен в auditt_byprice_january.csv

Docker

Вы можете собрать свой образ из Dockerfile

Недавно передо мной встала задача перенести почти 20 000 статей со старого сайта на новый. Фигня вопрос когда речь идет о сайтах на WordPress. С одного экспортнул, на другой импортнул, все готово. Но старый сайт самописное гамно. То есть экспортировать никак, надо собирать инфу в кучу и формировать файл импорта. Тут парсить ничего не нужно, у меня доступ к базе данных. Сформировать файл не составило труда, после чего осталось закинуть на новый сайт и все.

До этого мне не доводилось импортировать на WordPress информацию таким образом, тем более такой большой объем. Поскольку с первого раза мне не удалось произвести импорт, подготовка файла сопровождалась постоянными открытиями и ошибками. Как только задача была решена, я понял что таким образом можно быстро запускать сайты с большим объемом контента. Причем создавать не прибегая к услугам профессионалов.

От моего первого сайта остался домен. Сайт этот был доской объявлений. До сих пор, спустя годы, поисковики обращаются к нему и упорно запрашивают страницы с недвижимостью. Ну думаю, раз просят, значит надо дать, да? Мой сайт был полностью самописным и по функционалу был круче чем авито. Я бы может развивал бы его, но почему-то утратил к нему интерес. Чтобы запустить сайт, надо наполнить его объявлениями. Где брать объявления? Стырить с авито. Как тырить? Писать парсер.

Что такое парсер

По сути парсер — это программа, которая выполняет заложенную в неё функцию. Чаще всего парсер работает по принципу паука, который загружает главную страницу и далее шастает по ссылкам, которые нашел на главной странице. В качестве функции мы можем задать парсеру сбор изображений, а можем и текста с изображениями. Так же парсер может просто проверять сайт на наличие битых ссылок. Тут все зависит от нашей конечной цели.

Поскольку парсер является программой, то он следует четко правилам, которые мы закладываем в него изначально. Из-за этого могут возникать ошибки, поскольку мы не можем предусмотреть абсолютно все на этапе создания. По этой причине чтобы достичь бесперебойной работы, потребуется хорошо поработать выявляя ошибки.

Постановка задачи

Тут я настоятельно рекомендую хорошенько подумать. Правильно поставленная задача является половиной решения. Для этого нужно вдоль и поперек изучить ресурс, с которым будет работать парсер. Нужно определить особенности тех материалов, которые нам нужны и на основе этого сформировать набор критериев фильтрации.

В качестве примера приведу работу с парсингом авито. Первым делом я решил что меня интересуют объявления о продаже недвижимости. Конечно объявления о посуточной аренде меня тоже интересовали, но я почему-то забыл учесть этот аспект и это повлекло за собой проблемы. Проблема заключалась в изменении логики работы программы, а после изменений всегда возникают ошибки. Именно поэтому я и рекомендую заранее определиться с тем, что вам нужно. Лучше насобирать лишнего, чем потом добиратьэ

Проектирование парсера

Сам парсер делите условно на две части. Одна собирает «сырые» данные и кладет их в базу, а вторая обрабатывает эти данные. Это позволит вам сэкономить кучу времени. Чаще всего, ошибки возникают именно при обработке данных. После устранения ошибки, необходимо начинать процесс заново. При обработке данных из базы, времени на обработку будут тратится сущие секунды, если не доли секунд. Когда повторный сбор и обработка отнимут десятки минут, а то и несколько часов.

Первым делом отработайте сбор и хранение информации. Тут у нас встает несколько проблем:

  • Ответ сервера
  • Идентификация материалов

Ответ сервера

Работая с ресурсом парсер может получать ответ отличный от 200, по этой причине необходимо предусмотреть в программе этот момент. Тут нам надо понять является ли каждая страница ценностью или мы можем пренебречь несколькими страницами, которые по каким-то причинам возвращают ответ отличный от 200. Конечно со страницами ответ который 4хх поступать нужно однозначно, просто игнорировать.

А вот со страницами, которые возвращают 5хх, можно подумать что делать. Можно их игнорировать, а можно класть в отдельную папку(таблицу) и потом пробовать их снова запрашивать. То же самое со страницами отвечающими 3хх. Тут 301 и 302 редиректы, можем следовать по ним, а можем не следовать. Если решите следовать, то тщательно проверяйте куда отправляет редирект, чтобы не тратить ресурсы на левые страницы.

Идентификация материалов и обработка

Для идентификации можно использовать URL страницы, с которой была получена информация. Может быть использована часть URL, если там присутствует явно идентификатор. Иногда крупные проекты добавляют в URL идентификаторы. Например у авито он присутствует явно:

https://www.avito.ru/bratsk/doma_dachi_kottedzhi/dom_46_m_na_uchastke_8_sot._1831666660

Как видите в конце ссылки набор цифр начинающийся с нижнего подчеркивания. Гипотезу о том, что та или иная часть URL является идентификатором, нужно проверять на дублирование. В моем случае это было не критично, я не проверял. С другой стороны тут очевидно что это идентификатор. Именно его я использовал для идентификации в базе данных.

Затем нужно предусмотреть механизм, который предотвратит повторное добавление информации в базу. Самое простое сделать идентификатор уникальным ключом, тот же MySQL сам проверит является ли информация уникальна или же такая информация уже есть в базе. Это самый простой подход.

Процесс сборки и хранения можно отладить за пару дней и сделать его запускаемым автоматически через crontab или в виде демона, который стартует вместе с системой. Тут очень важно сделать фиксацию каждого случая, когда что-то пошло не так в работе программы. Это трудозатратно, но в перспективе экономит кучу времени. Если не встроить механизм отлова ошибок, программа может работать некорректно продолжительное время. Некорректная работа программы скажется на качестве собранной информации.

Отладив сбор информации, мы можем приступить к программе по обработке этой информации. Тут уже я не могу дать конкретных советов по обработке. Единственное что я могу, это рассказать про одну вещь, которая сделает 90% всей работы за вас. Вещь это называется phpQuery, если пишете на PHP, pyQuery, если пишете на Python. Для других языков искать не пробовал. Данные библиотеки по логике работы схожи с работой jQuery. Эти библиотеки делают за нас всю работу по поиску элементов в HTML. Нам же остается только дописать обработку полученной информации.

Пишем парсер для авито

В моем случае парсер сохраняет лишь результат. То есть получает содержимое страницы и тут же, обработав, сохраняет его в файл CSV. Да и в целом там не самая правильная логика обработки. Но я возможно выложу свой вариант когда сделаю его в соответствии со здравым смыслом. Текущую «времянку» из «костылей» мне выкладывать стремно. На тот момент, когда я начинал делать парсер для авито я экспериментировал. Как только проект выстрелит, я конечно же займусь парсером с большим энтузиазмом.

Поскольку мне ближе всего PHP, то я обычно все делаю на нем. Данный случай не исключение. Python мне нравится больше, но я не так хорошо его знаю и давно на нем ничего не делал. Тут я попробую накидать примерную логику работы с сайтом авито и возможно потом и сам применю её при написании новой версии парсера.

Сбор информации будет состоять из нескольких вложенных циклов. Первый цикл будет перебирать типы недвижимости. Как я писал ранее, мне интересна только недвижимость, но этот пример я думаю можно адаптировать под любые другие объявления. При желании можно добавить третий цикл, где будут перебираться города. Само собой сбор городов можно автоматизировать или просто зафиксировать их в виде массива. Для наглядности я добавлю такой массив, хотя мне он не особо-то и нужен.

$toponym_list = array(
	"bratsk",
);

По сути у нас есть типы недвижимости и типы сделок, добавим массив с ними:

$types_objects = array(
	"kvartiry/prodam",
	"kvartiry/sdam/posutochno",
	"komnaty/prodam",
	"kommercheskaya_nedvizhimost/prodam",
	"kommercheskaya_nedvizhimost/sdam",
	"doma_dachi_kottedzhi/prodam",
);

Как видите к типу недвижимости добавлен тип сделки. Я выбрал такой вариант из-за экономии времени. Если типы сделок вынести в отдельный массив, то тогда парсер будет запрашивать, к примеру, коммерческую недвижимость посуточно. По этой причине я решил четко обозначить тип недвижимости и тип сделки сразу. Получается даже изящнее и нагляднее.

Теперь эти массивы нам нужно перебрать.

foreach( $toponym_list as $toponym ) {
	foreach( $types_objects as $object_type ) {
		$url = "https://www.avito.ru/" . $toponym . "/" . $object_type . "?i=1";
		$current_page = 1;
		$last_page = 1; // Мы пока не знаем количество страниц пагинации, поэтому ставим 1
		$referer = "https://avito.ru/";
		$error = 0;
		do {
			$new_adv = 0; //Будем считать новые объявления
			include dirname( __FILE__ ) . "/get_pages.php";
			if ( $new_adv == 0 && $error == 0 ) {
				//Если ноль, значит на странице не найдены свежие объявдления
				//Прекращаем цикл для текущего типа чтобы не тратить время попусту
				break(1);
			}
			$current_page++; // Следующая страница
			sleep( rand( 1, 3 ) ); // Делаем паузу чтобы авито нас не заблокировал
		} while ( $last_page >= $current_page );
	}
}

Это можно сохранить в файл с любым именем Я назвал его просто: avito.php. Но прежде в начало добавим вот это:

require_once dirname( __FILE__ ) . "/functions.php";

И вот это:

require_once dirname( __FILE__ ) . "/phpQuery/phpQuery.php";

Саму библиотеку можно скачать отсюда.

Теперь нам нужно создать файлы get_pages.php, в котором будет проходить процесс сбора содержимого страниц, и functions.php, в котором у нас будут функции.

В файл functions.php добавим функции:

function get_last_page_num( $url ) {
	preg_match( "#p=([0-9]+)&#", $url, $match );
	return ( isset( $match[1] ) ? $match[1] : 0 );
}

function get_location( $ch, $header ) {
	global $location;
	if ( strpos( $header, "Location:" ) !== false ) {
		$location = trim( str_replace( "Location:", "", $header ) );
	}
	return strlen( $header );
}

function get_page( $url, $followlocation=false, $ref="", $type="GET", $params=array(), $timeout=30 ) {
	global $location;
	$all_headers = array();
	$cookie = dirname( __FILE__ ) . "/cookie.txt";
	if ( $ch = curl_init() ) {
		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_HEADER, false );
		curl_setopt( $ch, CURLOPT_FAILONERROR, false );
		curl_setopt( $ch, CURLOPT_HEADERFUNCTION, "get_location" );
		curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
		if ( $type == "POST" ) {
			curl_setopt( $ch, CURLOPT_POST, 1 );
			curl_setopt( $ch, CURLOPT_POSTFIELDS, urldecode( http_build_query( $params ) ) );
		}
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		if ( $followlocation ) {
			curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
		}
		curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout );
		curl_setopt( $ch, CURLOPT_COOKIEJAR,  $cookie );
		curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookie );
		curl_setopt( $ch, CURLOPT_REFERER, $ref );
		curl_setopt( $ch, CURLOPT_USERAGENT, 'Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.7.62 Version/11.01' );
		$data = curl_exec( $ch );
		$url = curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
		$code = curl_getinfo( $ch, CURLINFO_RESPONSE_CODE );
		curl_close( $ch );
		return array( "data"=>$data, "code"=>$code, "url"=>$url, "location"=>$location );
	} else {
		return false;
	}
}

Функция get_page, при запросе URL, вернет нам массив, где data содержимое страницы, code ответ сервера, url текущий url страницы, а location информация об url, на которые нас отправляют в случае редиректа. Значение url, в возвращаемом массиве, изменится лишь при двух условиях:

  1. Второй параметр true
  2. Сервер ответил редиректом

Значение этого ключа полезно при включенном редиректе, мы можем проверить принадлежит ли полученная страница частью сайта, который мы парсим или же это страница другого сайта.

Создаем файл get_pages.php и добавляем в него вот это:

$result = get_page( $url, false, $referer ); // Получаем содержимое первой страницы
$referer = $url . ( $current_page > 1 ? "&p=$current_page" : "" ); //Запоминаем текущую страницу, чтобы отправить уже её в качестве рефера

Запрос отправили и получили ответ. Теперь нам нужно обработать полученный результат. Тут у нас в игру вступает наш аналог jQuery. Он поможет нам найти снипеты объявлений и взять оттуда необходимую информацию.

if ( $result ) {
	if ( $result['code'] == 200 ) {
		$dom = phpQuery::newDocument( $result['data'] ); //Скармливаем содержимое страницы
		$items = $dom->find( ".snippet-horizontal" ); //Находим все снипеты объявлений на странице

		foreach( $items as $item ) {
			$item = pq( $item ); // создаем объект DOM из отдельного снипета
			$a = $item->find( "a.snippet-link" ); // ищем ссылку в снипете
			$href = "https://www.avito.ru" . $a->attr( "href" ); // получаем атрибут href

			preg_match( "#_([0-9]+)$#", $href, $match ); // ищем идентификатор в url
			$adv_id = $match[1]; // получаем идентификато

			//Теперь у нас есть ссылка и идентификатор
			//Все это можно записать в базу данных или сразу получить содержимое страницы
			// Отправляем запрос на добавление в базу, в случае удачи мы получим true или false в случае если такое объявление уже в базе
			if ( @$db->exec( "INSERT INTO `adv_links` VALUES ( $adv_id, '$object_type', '$href', '$referer', '' )" ) ) {
				$new_adv++; // Делаем инкремент, сообщаем что новое объявление добавлено
			}
		}
		// Чтобы понять сколько страниц нужно обойти, ищем номер последней страницы
		if ( $last_page == 1 ) {
			$link = $dom->find( "a.pagination-page:last" );
			$link = pq( $link );
			$href = $link->attr( "href" );
			$last_page = get_last_page_num( $href );
		}

	} else if ( $result['code'] == 0 ) {
		// Что-то с соединением, попробуем снова
		// Чтобы сие не продолжалось бесконечно, ограничим количество запросов
		if ( $error <= 3 ) {
			$current_page--;
			$error++;
		}
	} else if ( $result['code'] > 300 ) {
		// сработает если get_page вторым параметром передан false
		// Тут мы можем проверить значение $result['location'] и если там ссылка на текущий сайт, то попробовать запросить её
		// Только имейте в виду, снова придется проверять ответ, поэтому хорошо подумайте, надо ли оно вам
	} else {
		// Что-то делаем если ответ не 200
	}
} else {
	//Если возникли ошибка с CURLом, то что-то делаем
}

Если вы обратили внимание, то в коде выше затесался запрос к базе данных. В качестве базы данных я выбрал SQLite3. Причины две:

  1. Давно хотел поработать с SQLite
  2. Не нужно ничего устанавливать и настраивать

В отличии от того же MySQL, SQLite не требует установки и настройки. Создания баз данных и пользователей. То есть не нужно никаких заморочек, просто скопировал, запустил — работает. Это особенно удобно для начинающих и малоопытных программистов или вообще людей далеких от программирования.

Для инициализации базы данных добавим в наш файл avito.php Ещё одну строку в самое начало:

require_once dirname( __FILE__ ) . "/db.php";

А в файл db.php добавим следующий код

$db = new SQLite3( dirname( __FILE__ ) . "/db.sqlite" );

if ( ! @$db->exec( "SELECT adv_id FROM adv_links WHERE 1 LIMIT 0, 1 " ) ) {
	$db->busyTimeout( 5000 );
	$db->exec( "PRAGMA journal_mode=WAL;" );
	//$db->exec( "PRAGMA foreign_keys = ON;" );
	$res = @$db->exec( 'CREATE TABLE "adv_links" (
		"adv_id"	INTEGER NOT NULL UNIQUE,
		"adv_type"	TEXT NOT NULL,
		"adv_link"	TEXT NOT NULL,
		"adv_ref"	TEXT,
		"adv_date"	TEXT,
		"adv_content"	TEXT,
		PRIMARY KEY("adv_id"),
		UNIQUE("adv_id")
		);'
	);
	if ( ! $res ) {
		die( "Не удалось создать таблицуn" );
	}
	$res = @$db->exec( 'CREATE TABLE "adv_new" (
		"adv_id"	INTEGER NOT NULL UNIQUE,
		PRIMARY KEY("adv_id")
		);'
	);
	if ( ! $res ) {
		die( "Не удалось создать таблицуn" );
	}
	$res = @$db->exec( 'CREATE TRIGGER new_adv_insert BEFORE INSERT
		ON adv_links
		BEGIN
		INSERT INTO adv_new (adv_id) VALUES (NEW.adv_id);
		END;'
	);
	if ( ! $res ) {
		die( "Не удалось создать триггерn" );
	}
	$res = @$db->exec( 'CREATE TRIGGER new_adv_del AFTER DELETE
		ON adv_links
		BEGIN
		DELETE FROM adv_new WHERE adv_id=OLD.adv_id;
		END;'
	);
	if ( ! $res ) {
		die( "Не удалось создать триггерn" );
	}
}

Таким образом нам не нужно даже специально создавать базу данных. Достаточно запустить скрипт и он автоматически создаст файл базы и создаст в нем таблицу. На текущий момент таблица одна. То есть, если что-то пошло не так, достаточно просто удалить файл базы данных и начать заново. Это очень удобно. С MySQL в этом плане сложнее, все-таки она создана не для мелкого баловства.

Работа парсера

По сути у нас готова основа для парсера. Парсером это сложно назвать, скорее это больше недокраулер. Почему НЕДО? Потому что он проходит только по определенным ссылкам, а не шарится по сайту в целом или сети. Если запустить скрипт, то сначала он создаст базу данных, создаст в ней таблицу и перейдет к сбору информации.

Затем скрипт перейдет к сбору ссылок на страницы объявлений. Завершив сбор ссылок, скрипт перейдет к сбору контента. То есть запросит из базы данных записи с пустым полем «content» и начнет собирать уже контент со страниц объявлений и сохранять его в базу.

Имея в базе контент объявлений, мы можем делать все что захотим. Формировать из них новую базу, создавать файлы для тех или иных программ. В общем все, что угодно. Сбор объявлений мы теперь можем поместить в crontab и забыть о необходимости запускать скрипт вручную.

Дополнительно можно добавить скрипт, который будет скачивать изображения из объявлений. Если этого не делать сразу, то в таком случае необходимо будет актуализировать базу. Я не проверял, но мне кажется что изображения удаляются после удаления объявления. Именно после удаления, а не завершения.

Обработка данных

Из полученных данных я буду формировать несколько файлов импорта для плагина WP All import. Для этого мне потребуется написать дополнительный скрипт. Этому скрипту нужно будет из базы доставать сохраненный HTML и выжимать с него информацию. Из этой информации уже формировать файлы в формате CSV.

На вскидку рисуется сразу проблема. Допустим я впервые запустил сбор объявлений. Программа собрала все объявления. Мне остается только сформировать файл импорта и запихать его на сайт. Допустим сделано. Проходит пару дней, программа накопала ещё объявлений. Как программа, формирующая файл импорта, должна понять какие объявления новые, а какие попали в первый файл импорта. Если этого не отслеживать, то на сайте каждый раз будут появляться дубли, и плодиться они будут после каждого импорта.

Решение простое. Нам поможет ещё одна таблица и триггер. Таблицу назовем просто «adv_new», в неё будут записываться идентификатор и ссылка. За запись будет отвечать триггер. Таким образом после добавления записи в таблицу «adv_links» автоматически будет добавляться запись в таблицу «adv_new». После первого запуска мы получим равное количество записей в таблицах.

Допустим мы запускаем скрипт, который формирует файл импорта. Скрипт запрашивает записи из таблицы «adv_new» с подключением к «adv_links». Обработав первую запись, скрипт удаляет эту запись из таблицы «adv_new». И так далее пока таблица «adv_new» не опустеет.

Допустим скрипт первый раз обработал 2000 записей. Через какое-то время в базе появилось ещё 100 объявлений. В итоге в таблице «adv_links» станет 2100 записей, а в таблице «adv_new» только 100. То есть при повторном запуске скрипта, который формирует файл импорта, он обработает только те 100 записей, которые были добавлены с момента последней обработки.

Опубликовано 05.04.2017
Обновлено 03.02.2020

Данный PHP скрипт позволяет получить объявления с популярной доски объявлений Avio, включая телефоны собственников.
Данный скрипт «заточен» под разбор объявлений по недвижимости, но легко может быть адаптирован под любую рубрику.
Скрипт обходит защиту от скачивания, корректно получает телефон человека, разместившего объявление.
Скрипт парсера авито оформлен в виде двух классов:

  • Класс чтения любых сайтов на основе Curl. Данные кэшируются, поддерживается работа с куки, мультизагрузка, работа через proxy и много другого.
  • Класс работы с Avito, который с помощью регулярных выражений разбирает полученные данные

Скрипт парсер Avito недвижимость успешно разбирает и подливает объявления только собственников на сайты по недвижимости моих клиентов.
Вы можете параметрами выбирать какие категории объявлений по недвижимости вас интересуют, в каких областях или городах.
Используется альтернативная функция strip_tags, т.к. стандартная PHP функция портит данные.
Для чтения объявлений с сайта используется многофункциональный класс, который поднимает сессию,
передает куки, делает временные паузы имитируя работу человека, чтобы избежать блокировки.
Если будете парсить большие объёмы данных с одного IP адреса, то блокировки Вам не избежать.
Для такой ситуации предусмотрена работа через прокси-сервера.
Актуальные прокси сервера можно взять здесь: список бесплатных Proxy серверов
Вы можете парсер авито скачать по ссылке ниже.

Если вы не хотите покупать скрипт, мучаться с прокси-серверами, но хотите получить свежий список недвижимости в формате Avito,
вы можете воспользоваться сервисом парсинга авито на сайте kvartir-market.ru.

Скрипт использования класса avito и описание методов и свойств класса:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<?
// парсим авито
$count=0; // кол-во подлитых объявлений
// https://www.avito.ru/tomsk/nedvizhimost
$spis_city= [1711=>'rostov-na-donu',2188=>'tomsk']; // здесь указываете все города, которые Вас интересуют
// здесь указываете интересующие Вас рубрики
$spis_vid= [1=>'kvartiry', 19=>'doma_dachi_kottedzhi', 2=>'kommercheskaya_nedvizhimost',12=>'komnaty',3=>'zemelnye_uchastki'];
$spis_act= [1=>'prodam',2=>'sdam'];
foreach($spis_city as $city=>$city_url){
    foreach($spis_vid as $vid=>$vid_name){
        echo "<h3>".$vid_name."</h3>";
        foreach($spis_act as $act=>$act_name){
            echo "<h4>".$act_name."</h4>";
            for($p=0;$p<10;$p++){ // иду по первым 10-ти страницам
                $root="/".$city_url."/".$vid_name."/".$act_name.($p?'?p='.$p:''); // https://m.avito.ru/rostov-na-donu/kvartiry/prodam?p=2
                $objs=Avito::get($root);// вторым необязательным параметром передаете массив параметров, например:
                                        // array('agency'=>0) если вы не хотите видеть объявления агентств
                if(is_null($objs))break; // объявления закончились
                if($objs)foreach($objs as $id=>&$obj){
                    if(empty($obj['tel']))continue; // не будет, если это агенство и указано агенства не подливать
                    $obj['city']=$city;
                    $obj['act']=$act;
                    $obj['parent']=$id; // id объекта у донора
                    $obj['cat']=( empty($obj['agency']) ? 1 : 2 );
                    if($vid==1){
                        if(stripos($obj['cost'],'за сутки')!==false || stripos($obj['comment'],'посуточно')!==false){// kvartiry_posutochno
                            $obj['vid']=5;
                            $obj['cost']=trim(str_ireplace('за сутки','',$obj['cost']));
                            $obj['name']=trim(str_ireplace('посуточно','',$obj['name']));
                        }elseif(($komnat=intval($obj['name'])) >0)$obj['vid']=12+min(6,$komnat);
                        elseif(stripos($obj['name'], 'студия')!==false) $obj['vid']=11;
                        elseif(stripos($obj['name'], 'комната')!==false) $obj['vid']=12;
                    }else $obj['vid']=$vid;
                    if($act==1)$obj['name']="Продам ".$obj['name'];
                    elseif($act==2)$obj['name']="Сдам ".$obj['name'];
                    if(!empty($obj['agency'])){
                        if(stripos($obj['comment'],'собственник')!==false) {
                            $obj['comment']=str_ireplace('собственник','',$obj['comment']);
                        }
                        $obj['comment'].="n".$obj['agency'];
                    }
                    if(!empty($obj['person'])) $obj['fullname']=$obj['person'];
                    if(empty($obj['address'])&&!empty($obj['district'])) $obj['address']=$obj['district'];
                    if(!empty($obj['address'])){
                        $obj['address']=str_ireplace(array('собственник','!'),'',$obj['address']);
                    }
                    echo "<br><a href='".$obj['link']."'>".$obj['name']."</a>, ".$obj['address'].", стоимость: ".$obj['cost'].
                        (empty($obj['latitude'])?'':", GPS:".$obj['latitude'].",".$obj['longitude']) .
                        (empty($obj['tel'])?'':", телефон: ".$obj['tel']).
                        "<br>".$obj['comment'];
                    //var_export($obj);
                    $count++;
                    echo "<br> картинки:";
                    if(!empty($obj['images']))foreach($obj['images'] as $i => $link){
                        echo "<br><a href='".$link."' target=_blank>".$link."</a>";
                    }
                    if($obj['date'] < ($t=date('Y-m-d h:i',strtotime('-1 hour'))) ){  // подлив за один час
                        echo ("<br>подливаю объявления не старше ".$t);
                        $p=100;
                        break;
                    }
                }
            }
        }
    }
} // city

class Avito {
    /** чтение и разбор страницы, возвращает массив данных по каждому объявлению
     * @param string $root - адрес считываемой страницы
     * @param array $options['agency'] =0 только от собственников
     * @return array|bool|null
     */
    static function get($root, $options=[])   {
        // читаю страницу со списком объявлений
        // цикл по всем объявлениям
        //     читаю объявление, выделяю большие картинки, описание, адрес, координаты, свойства недвижимости.
        //     читаю телефон собственника
    }

    static function error($s){
        echo " <b style='color:red'>".$s."</b>";
    }
    static function info($s){
        echo "<br>n<i>".$s."</i>";
    }
    static function info2($s){
        echo " <u>".$s."</u>";
    }

    static function NormalPhone($tel){
        $tel=str_replace(' ','',str_replace('(','',str_replace(')','',str_replace('-','',str_replace('+','',$tel)))));
        if(substr($tel,0,1)=='7')$tel='8'.substr($tel,1);
        return $tel;
    }

    static function NormalName($str){
        $str=str_ireplace(array('собственник','!'),'',str_replace('м^2','м?',$str));
        $str=str_replace(array(' м? ',' м?',' мВІ '),' кв.м. ',str_replace(' м?,',' кв.м.,',$str));
        return trim($str);
    }
}
?>
</body>
</html>

Здесь представлен фрагмент скрипта чтения и разбора объявлений avito. Полный скрипт вы можете приобрести.

Всего за 2499 рублей (~33$)
Вы можете приобрести полный пример парсинга объявлений с Avito.
Все скипты и классы хорошо отдокументированы и оптимизированы.
Код скрипта реализован на PHP, полностью открытый и не использует никаких дополнительных библиотек.

    Соглашение по использованию платной версии:

  • Вы можете использовать полученный код в любых своих разработках, вы не обязаны указывать ссылку на источник.
  • Вы НЕ имеете права перепродавать её, размещать в свободном или ограниченном доступе, а также публиковать в любом виде.
  • Все остальные права сохраняются за автором.


Регистрация Войти Войти через VK Войти через FB Войти через Google Войти через Яндекс

Понравилась статья? Поделить с друзьями:
  • Как написать пароль на питоне
  • Как написать пароль на латинице
  • Как написать пароль на компьютере
  • Как написать пароль на английском языке на ноутбуке
  • Как написать пароль на английской раскладке русскими буквами на телефоне