Статья Автоматизированный сбор данных с сайта бесплатных объявлений с помощью Python

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

000.png


Впрочем, авторизацию и даже двухфакторную аутентификацию с помощью SMS обойти вполне возможно. Вот только использование виртуальных номеров напрочь обесценивает эту затею. Так что Авито теперь парсить можно, но только лишь для маркетинговых исследований рынка. Узнать, так сказать, "среднюю температуру" товара "по больнице". Хотя, с другой стороны, это тоже интересная тема. Но сейчас не об этом. Решил я глянуть, а есть ли ещё в Рунете площадки, на которых все же можно получить номер телефона. И хоть данных там "кот наплакал", однако, походив по ним какое-то время можно вполне себе набрать небольшую базу номеров для холодного обзвона. Один из таких сайтов, где можно бесплатно разместить объявление, а значит и засветить номер, это RuDos.

Дисклеймер: Все данные, предоставленные в данной статье, взяты из открытых источников, не призывают к действию и являются только лишь данными для ознакомления, и изучения механизмов используемых технологий.

Что же, продолжим. Изначально, так как предполагалось парсить Авито, я рассчитывал применить Selenium, а точнее антидетект-браузер на основе Chrome. Но, так как планы резко поменялись, будем использовать несколько другие библиотеки.

Что потребуется?

В данном случае будет необходим, можно сказать, стандартный набор для парсинга. Это requests, bs4 и lxml. Также необходимо установить библиотеку для валидации - validators. Давайте выполним установку всего необходимого одной командой:

pip install requests bs4 lxml validators

После того, как будет установлено все необходимое, импортируем нужные модули в скрипт, а заодно добавим заголовки для запроса, в которых будет содержаться User-Agent и создадим множество для проверки номеров телефонов на дубликаты, словарь, куда мы и будем складывать полученные данные и список для ошибок, куда будут попадать ссылки те страницы, где произошла ошибка.

Python:
import json
import random
import time
from concurrent.futures import ThreadPoolExecutor
from urllib.parse import urljoin, unquote

import requests
import validators
from bs4 import BeautifulSoup as Buts

headers = {
    'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 '
                  'YaBrowser/23.1.1.1114 Yowser/2.5 Safari/537.36'
}

phone_set = set()
data_dict = dict()
error_link = []


Получение пагинации

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

pagination.png

Создадим функцию get_pagination(url: str) -> (int, bool), которая на вход будет получать ссылку на страницу категории, а возвращать последнюю страницу из блока пагинации или False, в случае неудачи. Выполняем запрос, ищем в коде блок пагинации, находим все ссылки с тегом «a», забираем последний и получаем текст из атрибута «title». Однако, в данном атрибуте текст содержит, помимо номера последней страницы, еще и текст. Поэтому, очищаем текст от всего ненужного и возвращаем из функции номер последней страницы в виде целого числа.

Python:
def get_pagination(url: str) -> (int, bool):
    """
    Получение номера последней страницы пагинации.
    :param url: Ссылка на страницу категории с объявлениями.
    :return: Номер последней страницы в пагинации.
    """
    try:
        rs = requests.get(url=url, headers=headers, timeout=10)
        if rs.status_code == 200:
            return int(Buts(rs.text, "lxml").find('div', class_='pagination').find_all('a')[-1].get('title').strip().
                       split()[1])
        return False
    except Exception:
        return False


Получение ссылок на страницы объявлений

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

Для того, чтобы понять, как формируется ссылка на следующую страницу в категории, перейдем куда-нибудь дальше и посмотрим, что отображается в адресной строке. В нашем случае к имени категории добавляется слово num и номер.

Создадим функцию get_links_page(pag: int, url: str) -> (list, bool), которая на вход получает номер последней страницы из пагинации, ссылку на страницу категории, а возвращает список со ссылками на объявления или False, в случае неудачи.

Выполняем итерацию в цикле по диапазону от 1 до количества страниц в пагинации. Переходим на нужную страницу, забираем html-код и передаем в BeautifulSoup. Здесь нужно пройти путь к ссылке. Он достаточно прямолинеен. Итак, для начала находим тег «div» с классом «items-board». В этом блоке содержаться все объявления. Затем находим все «div» с классом «items-wrap». В каждом из таких «div» находиться карточка объявления, в том числе и искомая ссылка на страницу объявления. Она находиться в «div» с классом «info-block», «div» с классом «name», где нужно найти тег «a» и забрать из него ссылку в атрибуте «href».

01.png


Так как ссылка здесь не абсолютная, а относительная, с помощью urljoin добавляем ей хост и складываем в список со ссылками. После того, как проитерируемся по всем страницам, возвращаем из функции список со ссылками или, если он пуст, False.

Python:
def get_links_page(pag: int, url: str) -> (list, bool):
    """
    Получение ссылок на страницы объявлений.
    :param pag: Номер последней страницы пагинации.
    :param url: Ссылка на страницу категории.
    :return: Список ссылок на страницы объявлений.
    """
    links = []
    for x in range(1, pag + 1):
        try:
            rs = requests.get(url=f'{url}num{x}.html', headers=headers, timeout=5)
            if rs.status_code == 200:
                print(f'\r  - Получение ссылок: {rs.url}', end="")
                board = Buts(rs.text, 'lxml').find('div', class_='items-board').find_all('div', class_='items-wrap')
                for item in board:
                    link = urljoin(url, item.find('div', class_='info-block').find('div', class_='name').find('a').
                                   get('href'))
                    links.append(link)
            else:
                return False
        except Exception:
            continue
    return links if links else False


Получение данных о разместившем объявление

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

03.png

Если мы заглянем в код, то увидим, что данный блок находиться в «div» с классом «contact-adsv». И в этом блоке уже находятся все остальные данные, которые нам и необходимо забрать.

04.png

Создадим функцию get_phone(link: str, num: int). На вход она принимает ссылку на страницу объявления, а также индексный номер данной ссылки из списка. Номер вовсе не обязателен. Передается он сюда только для информации выводимой для пользователя.

Для начала, перед тем как парсить страницу, сделаем паузу в несколько секунд. Сколько, выберем с помощью рандома. Это необходимо для того, чтобы исключить одновременный доступ к страницам с одного адреса. Так как мы будем выполнять дальнейший парсинг с помощью потоков. Конечно же, полностью этого избежать не получиться, но хотя бы попытаться стоит. Далее, создаем запрос к странице объявления. Оборачиваем его в блок try — except, чтобы обработать возможные ошибки. Передадим в запрос ссылку на страницу, заголовки и установим таймаут.

Проверяем статус-код. Если он 200, двигаемся дальше. Находим весь блок с информацией о разместившем объявление. Забираем из него отображаемое имя. Двигаемся дальше и забираем адрес. Конечно же, он здесь не полный, но представление о городе дать может. И получаем ID разместившего объявление. В нашем случае ID будет нужен в двух случаях. Первый — это для того, чтобы создать уникальный ключ, который будет формироваться из имени пользователя + ID. Так как данные мы будем складывать в словарь, который затем сохраним в json. И второй случай, но тем не менее, очень важный — ID необходим для получения номера телефона.

Python:
def get_phone(link: str, num: int):
    """
    Получение основных данных о пользователе разместившем объявление.
    :param link: Ссылка на страницу объявления.
    :param num: Номер ссылки в списке.
    """
    time.sleep(random.randrange(1, 4))
    try:
        rs = requests.get(url=link, headers=headers, timeout=7)
        if rs.status_code == 200:
            contact = Buts(rs.text, 'lxml').find('div', class_="contact-adsv")
            name = contact.find('div', class_="items").find('div', class_="main-adsv"). \
                find('div', class_="name-user").find('a').text.strip()
            address = contact.find('div', class_='geo').text.strip()
            id_contact = contact.find('div', class_="block_number_adv").text.strip().split(":")[1].strip()

Перейдем в инструменты разработчика на вкладку «Network», во вкладку «Feth/XHR». Очистим все, что в ней находится и нажмем на кнопку «Показать телефон». И мы видим, что выполняется запрос для получения необходимых данных.

05.png

Если мы взглянем на данный запрос в инструментах, то увидим, что это POST запрос, который передает на сервер некоторые данные. А именно, ID и ключевое слово. Чтобы увидеть это в более привычном виде, щелкнем по запросу правой кнопкой мыши, выберем пункт меню «Copy → Copy all as cURL».

06.png

Теперь перейдем на сайт , на котором можно автоматически получить код python. Вставим скопированные данные в специальное поле и выберем вкладку Python. Ниже отобразиться код, в котором мы видим, что формируется словарь data, который содержит ключ «id_advert» и ключи «type_data». type_data останется неизменной, а вот ключ id_advert будет постоянно меняться.

07.png

Теперь, когда мы понимаем, что необходимо отправить в POST-запросе, заполним словарь для отправки.

Python:
            data = {
                'id_advert': id_contact,
                'type_data': 'phone',
            }
            time.sleep(random.uniform(0.3, 1.9))

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

Python:
            resp = requests.post('https://rudos.ru/moduls/doska/include/get_type_data_elm_doska.php',
                                 headers=headers, data=data, )
            phone = "".join(unquote(resp.text).split(",")[-1].replace("'", "").replace("}", "").
                            split(":")[-1].split()).replace("(", "").replace(")", "").replace("-", "")

Так как номера пользователи вводят самостоятельно, телефон может начинаться как с +7, так и с 8. В данном случае это не является ошибкой. Но для нас, чтобы привести все номера к одному виду, это имеет значение. Поэтому я и очищал текст от лишних тире, скобок и прочего, что может встретиться в номере телефона. Теперь обработаем случай, когда номер телефона начинается с «8». Проверяем, начинается ли текст с 8. Если да, преобразуем номер в список из символов. Удаляем первый символ по нулевому индексу, вставляем на его место +7 и снова объединяем список в строку.

Python:
            if phone.startswith("8"):
                phone = list(phone)
                phone.pop(0)
                phone.insert(0, "+7")
                phone = "".join(phone)

Возможны случаи, когда пользователь не разместил номера телефона. В этом случае переменная phone будет пустой, после всех преобразований. Поэтому обработаем этот случай. Если переменная пустая, сообщаем пользователю и выходим из функции, если нет, проверяем, не находиться ли полученный номер в множестве. Это необходимо, чтобы избежать дублирования данных. Так как под разными ID, на данном сайте, могут находиться разные объявления, но от одного человека. Если номера нет, обновляем словарь с данными, добавляем полученный номер телефона в множество для будущего сравнения. И выводим сообщение пользователю.

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

Python:
            if not phone:
                print(f"  - {num + 1} | Данные не получены: номера нет | {link}")
                return
            else:
                if phone not in phone_set:
                    data_dict.update({f"{name}_{id_contact}": {"address": address, "phone": phone}})
                    phone_set.add(phone)
                print(f"  - {num+1} | Данные получены: {phone} | {link}")
    except Exception as ex:
        print(f"  - {num+1} | Данные не получены: {link} {ex}")
        error_link.append(link)
        return

Python:
def get_phone(link: str, num: int):
    """
    Получение основных данных о пользователе разместившем объявление.
    :param link: Ссылка на страницу объявления.
    :param num: Номер ссылки в списке.
    """
    time.sleep(random.randrange(1, 4))
    try:
        rs = requests.get(url=link, headers=headers, timeout=7)
        if rs.status_code == 200:
            contact = Buts(rs.text, 'lxml').find('div', class_="contact-adsv")
            name = contact.find('div', class_="items").find('div', class_="main-adsv"). \
                find('div', class_="name-user").find('a').text.strip()
            address = contact.find('div', class_='geo').text.strip()
            id_contact = contact.find('div', class_="block_number_adv").text.strip().split(":")[1].strip()
            data = {
                'id_advert': id_contact,
                'type_data': 'phone',
            }
            time.sleep(random.uniform(0.3, 1.9))
            resp = requests.post('https://rudos.ru/moduls/doska/include/get_type_data_elm_doska.php',
                                 headers=headers, data=data, )
            phone = "".join(unquote(resp.text).split(",")[-1].replace("'", "").replace("}", "").
                            split(":")[-1].split()).replace("(", "").replace(")", "").replace("-", "")
            if phone.startswith("8"):
                phone = list(phone)
                phone.pop(0)
                phone.insert(0, "+7")
                phone = "".join(phone)
            if not phone:
                print(f"  - {num + 1} | Данные не получены: номера нет | {link}")
                return
            else:
                if phone not in phone_set:
                    data_dict.update({f"{name}_{id_contact}": {"address": address, "phone": phone}})
                    phone_set.add(phone)
                print(f"  - {num+1} | Данные получены: {phone} | {link}")
    except Exception as ex:
        print(f"  - {num+1} | Данные не получены: {link} {ex}")
        error_link.append(link)
        return


Запуск потоков для получения номеров

Создадим функцию thread_run(links: list). На вход она получает список со ссылками на страницы объявлений. С помощью контекстного менеджера создаем объект класса ThreadPoolExecutor(max_workers=5). Определяем в нем максимальное количество одновременных потоков. В нашем случае это 5. Итерируемся по списку ссылок. Создаем временный список, куда будем складывать текущие ссылки. Проверяем, не равна ли длина временного списка количество потоков. Если да, итерируемся по временному списку, забираем из него индексный номер ссылки и саму ссылку. Запускаем поток в котором, в качестве целевой функции указываем get_phone и передаем нужные параметры.

Так как ссылок может быть не равное количество, по завершении цикла проверяем длину списка, если он меньше 5, забираем из списка остатки и запускаем потоки.

Python:
def thread_run(links: list):
    """
    Итерация по списку ссылок и запуск потоков для парсинга страниц объявлений.
    :param links: Список со ссылками на страницы объявлений.
    """
    with ThreadPoolExecutor(max_workers=5) as executor:
        temp = []
        for num, link in enumerate(links):
            temp.append([num, link])
            if len(temp) >= 5:
                for x in temp:
                    n = x[0]
                    ln = x[1]
                    executor.submit(get_phone, link=ln, num=n)
                temp.clear()
        if len(temp) < 5:
            for x in temp:
                n = x[0]
                ln = x[1]
                executor.submit(get_phone, link=ln, num=n)
            temp.clear()


Функция main

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

Python:
def main():
    # Пример ссылки на страницу категории: https://rudos.ru/rossiya/astrologiya/ekstrasensorika/'
    """
    Запуск парсинга данных со страниц объявлений.
    """
    url = input("Введите ссылку на страницу категории: ")
    if validators.url(url):
        if pag := get_pagination(url):
            print(f'\nНайдено страниц в категории: {pag}')
            if links := get_links_page(pag, url):
                print(f'\nНайдено ссылок на страницы товаров: {len(links)}')
                thread_run(links)
                if data_dict:
                    with open(f'rudos_phone_{url.split("/")[-2]}.json', 'w', encoding='utf-8') as file:
                        json.dump(data_dict, file, indent=4, ensure_ascii=False)
                    print(f"\nПолучено номеров: {len(data_dict)}\nДанные сохранены: "
                          f"rudos_phone_{url.split('/')[-2]}.json")
                else:
                    print("\nНе удалось получить данные")
                if error_link:
                    with open(f'rudos_phone_{url.split("/")[-2]}_error.txt', 'w', encoding='utf-8') as text:
                        for txt in error_link:
                            text.write(f'{txt}\n')
                    print(f"\nОшибок: {len(error_link)}.\nДанные об ошибках сохранены: "
                          f"rudos_phone_{url.split('/')[-2]}_error.txt'")
            else:
                print("\nСсылок не найдено. Ошибка получения данных")
        else:
            print("\nПагинация не получена. Ошибка получения данных")
    else:
        print("Введенная ссылка не прошла валидацию. Проверьте правильность ввода")

Python:
import json
import random
import time
from concurrent.futures import ThreadPoolExecutor
from urllib.parse import urljoin, unquote

import requests
import validators
from bs4 import BeautifulSoup as Buts

headers = {
    'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 '
                  'YaBrowser/23.1.1.1114 Yowser/2.5 Safari/537.36'
}

phone_set = set()
data_dict = dict()
error_link = []


def get_pagination(url: str) -> (int, bool):
    """
    Получение номера последней страницы пагинации.
    :param url: Ссылка на страницу категории с объявлениями.
    :return: Номер последней страницы в пагинации.
    """
    try:
        rs = requests.get(url=url, headers=headers, timeout=10)
        if rs.status_code == 200:
            return int(Buts(rs.text, "lxml").find('div', class_='pagination').find_all('a')[-1].get('title').strip().
                       split()[1])
        return False
    except Exception:
        return False


def get_links_page(pag: int, url: str) -> (list, bool):
    """
    Получение ссылок на страницы объявлений.
    :param pag: Номер последней страницы пагинации.
    :param url: Ссылка на страницу категории.
    :return: Список ссылок на страницы объявлений.
    """
    links = []
    for x in range(1, pag + 1):
        try:
            rs = requests.get(url=f'{url}num{x}.html', headers=headers, timeout=5)
            if rs.status_code == 200:
                print(f'\r  - Получение ссылок: {rs.url}', end="")
                board = Buts(rs.text, 'lxml').find('div', class_='items-board').find_all('div', class_='items-wrap')
                for item in board:
                    link = urljoin(url, item.find('div', class_='info-block').find('div', class_='name').find('a').
                                   get('href'))
                    links.append(link)
            else:
                return False
        except Exception:
            continue
    return links if links else False


def get_phone(link: str, num: int):
    """
    Получение основных данных о пользователе разместившем объявление.
    :param link: Ссылка на страницу объявления.
    :param num: Номер ссылки в списке.
    """
    time.sleep(random.randrange(1, 4))
    try:
        rs = requests.get(url=link, headers=headers, timeout=7)
        if rs.status_code == 200:
            contact = Buts(rs.text, 'lxml').find('div', class_="contact-adsv")
            name = contact.find('div', class_="items").find('div', class_="main-adsv"). \
                find('div', class_="name-user").find('a').text.strip()
            address = contact.find('div', class_='geo').text.strip()
            id_contact = contact.find('div', class_="block_number_adv").text.strip().split(":")[1].strip()
            data = {
                'id_advert': id_contact,
                'type_data': 'phone',
            }
            time.sleep(random.uniform(0.3, 1.9))
            resp = requests.post('https://rudos.ru/moduls/doska/include/get_type_data_elm_doska.php',
                                 headers=headers, data=data, )
            phone = "".join(unquote(resp.text).split(",")[-1].replace("'", "").replace("}", "").
                            split(":")[-1].split()).replace("(", "").replace(")", "").replace("-", "")
            if phone.startswith("8"):
                phone = list(phone)
                phone.pop(0)
                phone.insert(0, "+7")
                phone = "".join(phone)
            if not phone:
                print(f"  - {num + 1} | Данные не получены: номера нет | {link}")
                return
            else:
                if phone not in phone_set:
                    data_dict.update({f"{name}_{id_contact}": {"address": address, "phone": phone}})
                    phone_set.add(phone)
                print(f"  - {num+1} | Данные получены: {phone} | {link}")
    except Exception as ex:
        print(f"  - {num+1} | Данные не получены: {link} {ex}")
        error_link.append(link)
        return


def thread_run(links: list):
    """
    Итерация по списку ссылок и запуск потоков для парсинга страниц объявлений.
    :param links: Список со ссылками на страницы объявлений.
    """
    with ThreadPoolExecutor(max_workers=5) as executor:
        temp = []
        for num, link in enumerate(links):
            temp.append([num, link])
            if len(temp) >= 5:
                for x in temp:
                    n = x[0]
                    ln = x[1]
                    executor.submit(get_phone, link=ln, num=n)
                temp.clear()
        if len(temp) < 5:
            for x in temp:
                n = x[0]
                ln = x[1]
                executor.submit(get_phone, link=ln, num=n)
            temp.clear()


def main():
    # Пример ссылки на страницу категории: https://rudos.ru/rossiya/astrologiya/ekstrasensorika/'
    """
    Запуск парсинга данных со страниц объявлений.
    """
    url = input("Введите ссылку на страницу категории: ")
    if validators.url(url):
        if pag := get_pagination(url):
            print(f'\nНайдено страниц в категории: {pag}')
            if links := get_links_page(pag, url):
                print(f'\nНайдено ссылок на страницы товаров: {len(links)}')
                thread_run(links)
                if data_dict:
                    with open(f'rudos_phone_{url.split("/")[-2]}.json', 'w', encoding='utf-8') as file:
                        json.dump(data_dict, file, indent=4, ensure_ascii=False)
                    print(f"\nПолучено номеров: {len(data_dict)}\nДанные сохранены: "
                          f"rudos_phone_{url.split('/')[-2]}.json")
                else:
                    print("\nНе удалось получить данные")
                if error_link:
                    with open(f'rudos_phone_{url.split("/")[-2]}_error.txt', 'w', encoding='utf-8') as text:
                        for txt in error_link:
                            text.write(f'{txt}\n')
                    print(f"\nОшибок: {len(error_link)}.\nДанные об ошибках сохранены: "
                          f"rudos_phone_{url.split('/')[-2]}_error.txt'")
            else:
                print("\nСсылок не найдено. Ошибка получения данных")
        else:
            print("\nПагинация не получена. Ошибка получения данных")
    else:
        print("Введенная ссылка не прошла валидацию. Проверьте правильность ввода")


if __name__ == "__main__":
    main()

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

08.png


А на этом, пожалуй, все.

Спасибо за внимание. Надеюсь, данная информация будет вам полезна
 

Вложения

  • rudos_parse.py.zip
    2,4 КБ · Просмотры: 260
Последнее редактирование модератором:

szybnev

Green Team
19.07.2018
102
136
BIT
12
Спасибо за отличный материал, понятное объяснение!
 

satfan

Green Team
26.06.2022
78
1
BIT
81
Спасибо.
Получаю такой ответ:
*** Remote Interpreter Reinitialized ***
Введите ссылку на страницу категории:

Пагинация не получена. Ошибка получения данных
 

Johan Van

Green Team
13.06.2020
363
691
BIT
395
Спасибо.
Получаю такой ответ:
*** Remote Interpreter Reinitialized ***
Введите ссылку на страницу категории:

Пагинация не получена. Ошибка получения данных

Я конечно извиняюсь, но не могу понять одну небольшую вещь. Вы умеете читать? Разве я писал статью о сборе данных с авито? Почему вы решили именно так? Не хотелось бы вас обижать, но по моему (и это уже не в первый раз) вы не читаете статьи, а просто пробегаете по строчкам и мозг выдергивает из них знакомые слова. Если вы почитаете статью внимательно, вы поймете, что парсятся телефоны не с авито, а именно с того сайта, который указан в статье. А он в ней указан. Несколько раз. Вот для примера, сайт этот в коде:

2023-03-16_20-14.png


Вопрос теперь к вам: причем здесь Авито?
 

satfan

Green Team
26.06.2022
78
1
BIT
81
В начале этого поста Вы писали:
Решил я тут на днях попробовать парсить Авито.
 

Johan Van

Green Team
13.06.2020
363
691
BIT
395
В начале этого поста Вы писали:
Решил я тут на днях попробовать парсить Авито.

И что? А если я напишу, что решил я на днях слетать на Марс, тогда что? Это и будет содержание статьи? Описание полета?
А дальше напишу, что у меня ничего не получилось, потому как не нашел подходящего транспорта. Но похоже для вас это уже не будет иметь значения, да? Ведь дальше читать не надо. В коде разбираться - не надо. Странно, не кажется? То есть, вы спрашиваете у меня о том, почему не работает, даже не удосужившись прочитать статью... весело живем...
 

Johan Van

Green Team
13.06.2020
363
691
BIT
395
В начале этого поста Вы писали:
Решил я тут на днях попробовать парсить Авито.

Понимаете ли, есть такой оборот речи. Если выделить нужные слова из абзаца, становиться понятно. Это конечно, если прочитать абзац.

02.png
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!