Скрапинг даёт вам строки; он не даёт вам ответов. Данные, которые сходят с краулера, почти всегда беспорядочны: цены, хранящиеся как строки с символами валют, даты в трёх разных форматах, наполовину заполненные столбцы и один и тот же товар, указанный дважды, потому что две страницы ссылались на него. Прежде чем что-либо из этого станет полезным, вы должны очистить его, и стандартный инструмент для этой задачи в Python это pandas.
Это руководство берёт реалистичный спарсенный датасет, CSV с объявлениями товаров, и проводит его весь путь от сырых строк до инсайта с помощью pandas. Вы загружаете его в DataFrame, осматриваете, обрабатываете пропущенные значения и дубликаты, исправляете типы столбцов, разбираете цены и даты в реальные числа и временные метки, затем фильтруете, группируете, агрегируете, сортируете и экспортируете результат. Каждый фрагмент реальный и пригодный для копирования. Единственное допущение в том, что входные данные изначально были собраны ответственно из публичных источников.
Что вы создадите
Короткий, повторяемый конвейер очистки и анализа, который превращает сырой скрап в аккуратный датасет и несколько сводных таблиц. К концу вы коснётесь каждого поля типичной строки товара:
- product_name: свободный текст, часто со случайным регистром и пробелами.
- category: столбец группировки, по которому вы будете агрегировать.
-
price: спарсено как строка вроде
"$1,299.00", которая должна стать float. - rating: числовой столбец с пропущенными значениями, которые нужно обработать.
- in_stock: булевоподобный флаг, хранящийся непоследовательно как текст.
- scraped_at: строка даты, которую нужно разобрать в реальную временную метку.
Тот же рабочий процесс применим к любому табличному скрапу, объявлениям, отзывам, объявлениям о вакансиях или строкам недвижимости. Если ваш краулер уже пишет чистый JSON или CSV, вы переходите прямо к половине с анализом. Для более глубокого взгляда на два формата и на то, когда выбирать каждый, обратитесь к JSON против CSV: основные различия.
Почему спарсенные данные сначала нуждаются в очистке
Сырые спарсенные данные редко готовы к анализу. Слой извлечения вытягивает любой текст, который сидит внутри селектора, поэтому цена возвращается как "$1,299.00", а не как число 1299.0, ячейка рейтинга может быть пустой, когда страница так и не отрисовала её, и одно и то же объявление может появиться дважды, когда пагинация перекрывается. Подайте это прямо в вычисление, и вы получите ошибки или, что хуже, тихо неверные ответы.
Очистка исправляет три класса проблем до того, как запустится любой анализ: пропущенные значения от неполного извлечения, дублирующиеся строки от перекрывающихся обходов и непоследовательные форматы по столбцам цены, даты и текста. Сделайте их правильно, и остальной pandas, сортировка, группировка, агрегация, станет несколькими однострочниками. Если вы встраиваете эту дисциплину в более крупный конвейер, шаблоны в структурировании и очистке веб-спарсенных данных для AI и ML хорошо обобщаются за пределы одного CSV.
Предварительные требования
Чтобы следовать за повествованием, вам нужно немногое.
Базовый Python. Вам стоит уверенно запускать скрипт и читать несколько строк кода. Знания того, что такое список и словарь, достаточно; pandas выполняет основную работу.
Python 3.8 или новее. Проверьте свою версию командой python --version. Если у вас её нет, установите её с python.org.
Спарсенный файл CSV или JSON. Подойдёт любой табличный экспорт. Примеры ниже предполагают файл с именем products.csv со столбцами, перечисленными выше, но методы те же, как бы ни назывались ваши столбцы.
Настройте проект
Создайте виртуальное окружение, чтобы проект оставался изолированным, затем установите pandas. Это единственная обязательная зависимость; matplotlib опционален и нужен только для быстрого графика в конце.
python --version python -m venv pandas_env source pandas_env/bin/activate pip install pandas matplotlib
В Windows активируйте окружение командой pandas_env\Scripts\activate вместо строки source. pandas даёт вам DataFrame и каждый метод очистки ниже; matplotlib это то, через что pandas строит графики, когда вы вызываете .plot() в конце.
Шаг 1: Загрузите данные в DataFrame
Всё в pandas начинается с DataFrame, табличной структуры строк и столбцов, очень похожей на электронную таблицу. Один её столбец это Series. Чтение спарсенного CSV в DataFrame это один вызов, а JSON так же прост с read_json.
import pandas as pd # Load the scraped product listings df = pd.read_csv("products.csv") # JSON works the same way: # df = pd.read_json("products.json") # Show the first five rows print(df.head())
Метод head() печатает первые пять строк, ваше первое подтверждение того, что файл загрузился и столбцы оказались там, где вы ожидаете. Если ваш скрап произвёл по одному объекту JSON на строку, передайте lines=True в read_json. С этого момента каждая операция работает над объектом df в памяти; ничто не трогает исходный файл, пока вы не экспортируете в конце.
Чем чище ваш products.csv до того, как он достигнет pandas, тем меньшая часть этого руководства вам реально нужна. Scraper API автоматически разбирает поддерживаемые страницы в структурированные поля JSON, цены, заголовки, рейтинги и наличие, уже разделённые на типизированные значения, так что строки, которые вы загружаете, близки к готовности к анализу, а не к стене сырых строк. Меньше времени на исправление dtypes, больше времени на поиск инсайтов.
Шаг 2: Осмотрите датасет
Прежде чем что-либо менять, посмотрите на то, что у вас есть. Три метода говорят вам почти всё: head() показывает образцы строк, info() сообщает типы столбцов и сколько ненулевых значений хранит каждый, а describe() даёт сводную статистику для числовых столбцов.
# Shape: (rows, columns) print(df.shape) # Column names, dtypes, and non-null counts df.info() # Summary stats for numeric columns print(df.describe())
Читайте info() внимательно: именно здесь вы рано замечаете проблему. Если price отображается как object, а не как число, это всё ещё строка, и арифметика на ней провалится. Если у rating меньше ненулевых значений, чем количество строк, у вас есть пропущенные данные для обработки. Этот единственный вызов говорит вам, какой из следующих шагов вам реально нужен.
Шаг 3: Обработайте пропущенные значения
Пропущенные ячейки происходят от неполного извлечения, поля, которое не отрисовалось, селектора, который ничему не соответствовал на одной странице. Сначала посчитайте их по столбцам, затем решите, удалить ли затронутые строки или заполнить их разумным значением по умолчанию.
# Count missing values per column print(df.isnull().sum()) # Drop rows missing a product name or price (can't analyse those) df = df.dropna(subset=["product_name", "price"]) # Fill missing ratings with the column average instead of dropping df["rating"] = df["rating"].fillna(df["rating"].mean())
Выбор между удалением и заполнением это суждение, а не правило. Удаляйте строки, когда пропущенное поле существенно и не может быть угадано, объявление без цены бесполезно для анализа цен. Заполняйте, когда столбец второстепенный и разумная замена лучше потери всей строки; здесь пропущенный rating становится средним по столбцу, так что эти товары всё равно учитываются в агрегатах по категориям. Использование subset в dropna держит удаление прицельным, вместо того чтобы выбрасывать любую строку с одной пустой ячейкой.
Шаг 4: Удалите дублирующиеся строки
Перекрывающиеся обходы и перезапуски оставляют дублирующиеся строки, и они тихо раздувают подсчёты и средние. Найдите их, затем удалите, сохраняя первое вхождение.
# How many fully duplicated rows are there? print(df.duplicated().sum()) # Drop exact duplicate rows, keep the first df = df.drop_duplicates(keep="first") # Or de-duplicate on a unique key, like a product URL or ID df = df.drop_duplicates(subset=["product_name", "category"])
Голый drop_duplicates() удаляет только строки, идентичные по каждому столбцу, что упускает почти-дубликаты, где одно поле отличается. Когда у вашего скрапа есть естественный ключ, идентификатор товара или канонический URL, дедуплицируйте по нему с subset, чтобы два захвата одной и той же позиции схлопнулись в один, даже если спарсенная временная метка отличается. Запустите это перед любым подсчётом, чтобы ваши итоги отражали отдельные товары, а не артефакты обхода.
Шаг 5: Исправьте типы данных и разберите цены
Это шаг, который превращает спарсенные строки в значения, на которых вы можете вычислять. Спарсенный price приходит как текст вроде "$1,299.00"; вы убираете символ валюты и разделитель тысяч, затем приводите к float. Та же идея стандартизирует флаг наличия и приводит в порядок текстовые столбцы.
# "$1,299.00" -> 1299.0 df["price"] = ( df["price"] .str.replace(r"[$,]", "", regex=True) .astype("float") ) # Tidy text: strip whitespace and normalise case df["product_name"] = df["product_name"].str.strip() df["category"] = df["category"].str.lower() # "Yes"/"No" text flag -> real booleans df["in_stock"] = df["in_stock"].str.lower().map({"yes": True, "no": False})
Аксессор .str позволяет вам выполнять строковые операции по всему столбцу сразу, так что регулярное выражение [$,] убирает и знак доллара, и запятую из каждой цены, прежде чем astype("float") сделает столбец числовым. Приведение category к нижнему регистру важнее, чем кажется: если половина ваших строк говорит "Phones", а половина "phones", группировка трактует их как две категории, пока вы не нормализуете. Сопоставление флага наличия с реальными булевыми значениями позволяет вам чисто фильтровать по нему на следующем шаге.
После любого преобразования типа запустите df.info() снова и подтвердите, что столбец читается как float64, bool или datetime64, а не как object. Тип object на столбце, который вы считаете числовым, это самая распространённая причина того, что более позднее вычисление выбрасывает ошибку или тихо возвращает мусор.
Шаг 6: Разберите даты
Строки дат бесполезны для анализа на основе времени, пока pandas не понимает их как временные метки. to_datetime преобразует столбец, и как только он становится реальным datetime, вы можете сортировать по нему, фильтровать диапазоны и вытягивать части вроде месяца.
# Parse the scraped timestamp into a real datetime df["scraped_at"] = pd.to_datetime(df["scraped_at"], errors="coerce") # Now date parts are available df["scrape_month"] = df["scraped_at"].dt.to_period("M")
Передача errors="coerce" это безопасное значение по умолчанию для спарсенных данных: любое значение, которое pandas не может разобрать, становится NaT (эквивалент null для datetime) вместо того, чтобы выбросить ошибку и остановить весь скрипт. Если каждая строка разделяет один известный формат, вы можете передать format="%Y-%m-%d", чтобы сделать разбор быстрее и строже. С реальным столбцом datetime аксессор .dt открывает year, month, day и помощники вроде to_period для помесячной группировки.
Шаг 7: Фильтруйте и выбирайте
С чистыми и типизированными данными вы можете задавать им вопросы. Булево индексирование фильтрует строки по условию, и вы можете комбинировать условия, чтобы сузить дальше.
# Only in-stock products over $100 premium = df[(df["in_stock"]) & (df["price"] > 100)] # Just the columns you care about premium = premium[["product_name", "category", "price", "rating"]] print(premium.head())
Каждое условие внутри скобок производит булеву Series, а & комбинирует их строка за строкой, поэтому оборачивайте каждое условие в круглые скобки, чтобы сохранить правильный приоритет оператора. Выбор списка столбцов двойными скобками возвращает более узкий DataFrame, удобно, когда у скрапа тридцать столбцов, а вам нужно лишь четыре для конкретного вопроса.
Шаг 8: Группируйте, агрегируйте и сортируйте
Реальная отдача это обобщение. groupby разбивает строки по ключу, затем вы агрегируете каждую группу, средняя цена на категорию, количество товаров на категорию, верхний рейтинг в каждой, и сортируете результат, чтобы прочитать его с одного взгляда.
# Average price per category, highest first avg_price = ( df.groupby("category")["price"] .mean() .sort_values(ascending=False) ) # Several aggregates at once summary = df.groupby("category").agg( product_count=("product_name", "count"), avg_price=("price", "mean"), max_price=("price", "max"), avg_rating=("rating", "mean"), ) print(avg_price) print(summary)
Одно-метрическая форма, groupby("category")["price"].mean(), возвращает одно число на категорию и читается почти как английский. Когда вам нужно несколько метрик в одной таблице, именованная агрегация с .agg() чище: каждое ключевое слово становится выходным столбцом, а кортеж (column, function) говорит, какое поле обобщать и как. Цепочка sort_values в конце упорядочивает результат, так что самые дорогие и самые дешёвые категории оказываются вверху и внизу.
Шаг 9: Применяйте статистику
Для быстрого числового прочтения одного столбца встроенные статистические методы покрывают большую часть того, что вам нужно, не покидая pandas.
# Full statistical summary of the price column print(df["price"].describe()) # Individual measures median_price = df["price"].median() std_price = df["price"].std() print(f"Median price: {median_price:.2f}") print(f"Std deviation: {std_price:.2f}")
Вызов describe() на одном столбце даёт вам количество, среднее, стандартное отклонение, минимум, максимум и квартили за один заход, быстрый способ заметить выбросы, такие как ошибка скрапинга, оставившая цену на нуле или в миллионах. median() часто честнее среднего на ценовых данных, потому что несколько очень дорогих позиций смещают среднее вверх, а сравнение этих двух говорит вам, насколько перекошено распределение.
Шаг 10: Визуализируйте и экспортируйте
График делает распределение очевидным, и pandas строит графики прямо через matplotlib. Когда анализ сделан, запишите очищенные данные обратно, чтобы следующий этап, блокнот, дашборд, коллега, мог их подхватить.
import matplotlib.pyplot as plt # Average price per category as a bar chart avg_price.plot(kind="bar", title="Average price per category") plt.ylabel("Price") plt.tight_layout() plt.show() # Export the cleaned dataset df.to_csv("products_clean.csv", index=False) summary.to_csv("category_summary.csv") # JSON export works the same way df.to_json("products_clean.json", orient="records", indent=2)
Передача index=False в to_csv удерживает pandas от записи номеров строк как случайного первого столбца, что почти всегда то, чего вы хотите для чистого экспорта. Для JSON orient="records" производит список из одного объекта на строку, формы, которую ожидает большинство других инструментов. С этим сырой скрап стал аккуратным файлом и сводной таблицей, которую вы можете передать или превратить в график.
Как выглядит вывод
После прогона конвейера сводка по категориям читается как небольшая, чистая таблица. Экспортированная в JSON с orient="records", каждая строка выглядит так:
[ { "category": "laptops", "product_count": 128, "avg_price": 942.55, "max_price": 2499.00, "avg_rating": 4.31 }, { "category": "phones", "product_count": 204, "avg_price": 611.20, "max_price": 1399.00, "avg_rating": 4.12 } ]
Эти числа иллюстративны, но форма это в точности то, что производит код: одна запись на категорию, каждая несёт подсчёты и агрегаты, которые вы определили в .agg(). Этот структурированный вывод и есть то, что питает следующий этап, будь то дашборд отслеживания цен, блокнот или модель.
Масштабирование за пределы одного файла
Один CSV это отправная точка, а не пункт назначения. По мере роста ваших скрапов несколько привычек поддерживают работу того же конвейера на объёме.
-
Объединяйте много файлов. Когда обход пишет по одному CSV на запуск, прочитайте их все и сложите с
pd.concat([...])в один DataFrame перед очисткой, затем дедуплицируйте, чтобы перекрывающиеся запуски не считались дважды. -
Обрабатывайте большие файлы порциями. Если файл слишком велик для памяти, передайте
chunksizeвread_csvи итерируйте, очищая и агрегируя каждую порцию, чтобы вы никогда не держали весь датасет сразу. -
Сделайте очистку функцией. Оберните Шаги с 3 по 6 в единую функцию
clean(df), чтобы каждая новая партия получала одинаковую обработку, и ваш конвейер оставался воспроизводимым вместо груды разовых ячеек. - Выбирайте правильный входной формат. Вложенные данные, отзывы с ответами, товары со списками вариантов, часто подходят к JSON лучше, чем к CSV. Правильный выбор зависит от формы ваших данных, что освещено в JSON против CSV.
Если pandas начинает казаться ограничивающим на столбце, который вы парсите часто, более широкая экосистема библиотек Python для скрапинга и данных охватывает инструменты, которые подхватывают там, где один DataFrame заканчивается.
Ключевые выводы
- Очищайте, прежде чем анализировать. Спарсенные данные приходят с пропущенными ячейками, дублирующимися строками и числами строкового типа; исправьте их сначала, иначе каждое последующее вычисление под подозрением.
- Осматривайте с head, info и describe. Эти три метода говорят вам форму, dtypes и подсчёты пропущенных значений, так что вы точно знаете, какие шаги очистки вам нужны.
-
Разбирайте цены и даты в реальные типы. Убирайте символы валют и приводите к float, преобразуйте строки дат с помощью
to_datetimeи подтверждайте черезinfo(), что ни один числовой столбец всё ещё неobject. - groupby плюс agg это там, где живёт инсайт. Разбивайте по ключу, агрегируйте несколько метрик сразу именованной агрегацией и сортируйте результат, чтобы прочитать его с одного взгляда.
- Чище вход означает меньше очистки. Чем более структурированы ваши данные к моменту, когда они достигают pandas, тем короче становится весь этот конвейер.
Часто задаваемые вопросы
Как мне установить pandas в Python?
Запустите pip install pandas из своего терминала, в идеале внутри виртуального окружения, чтобы он не конфликтовал с другими проектами. Затем импортируйте его в свой скрипт командой import pandas as pd, обычным псевдонимом, используемым почти во всём коде pandas. Если вам также нужны быстрые графики, показанные здесь, установите matplotlib вместе с ним командой pip install pandas matplotlib.
В чём разница между DataFrame и Series в pandas?
DataFrame это вся таблица, строки и столбцы вместе, очень похоже на электронную таблицу или таблицу базы данных. Series это один столбец этой таблицы, одномерный помеченный массив. Когда вы выбираете один столбец с df["price"], вы получаете Series; большинство методов очистки вроде fillna и str.replace работают над Series, и вы присваиваете результат обратно столбцу.
Как мне преобразовать спарсенную строку цены в число?
Используйте аксессор .str, чтобы убрать нечисловые символы, затем приведите столбец. Для значения вроде "$1,299.00" вызов это df["price"].str.replace(r"[$,]", "", regex=True).astype("float"), который удаляет знак доллара и запятую и превращает столбец в float, на котором вы можете вычислять. Всегда перепроверяйте через df.info(), что столбец после этого больше не имеет тип object.
Стоит ли мне удалять или заполнять пропущенные значения в спарсенных данных?
Это зависит от того, насколько существен столбец. Удаляйте строку, когда пропущенное поле критично и не может быть разумно угадано, объявление без цены бесполезно для анализа цен. Заполняйте значение, когда столбец второстепенный и замена лучше потери всей строки, например, заменяя пропущенный рейтинг средним по столбцу, чтобы товар всё равно учитывался в агрегатах по категориям. Используйте dropna(subset=[...]), чтобы нацелиться на конкретные столбцы, а не удалять любую строку с одной пустой ячейкой.
Как мне обобщить спарсенные данные по категории?
Используйте groupby, за которым следует агрегация. Для одной метрики df.groupby("category")["price"].mean() даёт среднюю цену на категорию. Для нескольких сразу используйте именованную агрегацию, df.groupby("category").agg(count=("product_name", "count"), avg_price=("price", "mean")), где каждое ключевое слово становится выходным столбцом. Прицепите sort_values в конце, чтобы упорядочить результат.
Могу ли я очищать и анализировать спарсенные данные автоматически?
Да. Оберните свои шаги очистки в функцию и вызывайте её на каждой новой партии, затем запускайте весь скрипт по расписанию, чтобы каждый свежий скрап обрабатывался одинаково. Держать логику в одной воспроизводимой функции, а не в разбросанных ячейках блокнота, и есть то, что делает конвейер надёжным по мере роста ваших данных.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
