Запись результатов парсинга в CSV файл
Начал изучать парсинг сайтов на Python. Могу спарсить данные и вывести их на экран. В окне терминала все выглядит идеально. Но если допустим эти данные отправлять клиенту, то ему эти данные скорее всего нужны будут в виде файла. Пытаюсь записать результаты в CSV файл, но не получается. То либо только последняя строка записывается в файл в единственной строке, то либо в нужном количестве строк, но на все строки опять же дублируется последняя запись. Чую, что где то близко, но в голову ничего пока не лезет.
Нужно записать результат парсинга в CSV файл. Первый столбец это номер по порядку, второй - наименование товара, третий - цена в тенге.
Вот код (в коде есть мусорные вещи, но это либо остатки поиска вариантов либо остатки от кода с пагинацией):
import requests
from bs4 import BeautifulSoup
import csv
from csv import writer
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36 OPR/90.0.4480.84 (Edition Yx 08)'
}
params = {'page': 1}
# задаем число больше номера первой страницы, для старта цикла
pages = 20 # количество страниц
page = 1 # номер начальной страницы
n = 1 # Начало нумерации
url = 'https://www.sulpak.kz/f/smartfoniy?page=1' # делаем запрос на страницу
response = requests.get(url) # обработка запроса метотдом get
soup = BeautifulSoup(response.text, 'lxml') # создаем суп
items = soup.find_all('li', class_='tile-container')# разбираем ячейки товара
if page <= pages: # Если текущая страница меньше или равно количеству страниц всего
for n, i in enumerate(items, start=n): # задаем нумерацию для всех ячеек
itemName = i.find('h3', class_='title').text.replace("Смартфон", "").strip() # делаем выборку из супа. Тут в теге ищем клас с названием класса
itemPrice = i.find('div', class_='price') # Ищем второе значение
if itemPrice is None: # Обработчик, если вдруг нет искомого класса или тега во втором значении
itemPrice = 'No Price' #
else:
itemPrice = itemPrice.text.replace("Цена:", "").replace("₸", "").strip() # Удаляем лишний текст и переносы
Index = n
GoodName = itemName
Price = itemPrice
print(Index, GoodName, Price) # Выводим на экран список товаров
with open("Goods_Sulpak.csv", mode="w", newline = '', encoding='cp1251') as w_file:
file_writer = csv.writer(w_file, delimiter = ";", lineterminator="\r")
file_writer.writerow(["Index", "Good Name", "Price"])
for Index, GoodName, Price in items:
file_writer.writerow([n, itemName, itemPrice])
Пагинация тоже работает и весь массив данных со всех страниц собирается надо.

Ответы (3 шт):
Можно сделать так:
import csv
import requests
from bs4 import BeautifulSoup
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
'application/signed-exchange;v=b3;q=0.9',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/104.0.5112.102 Safari/537.36 OPR/90.0.4480.84 (Edition Yx 08) '
}
params = {'page': 1}
# задаем число больше номера первой страницы, для старта цикла
pages = 20 # количество страниц
page = 1 # номер начальной страницы
n = 1 # Начало нумерации
# создаем csv документ с нужными столбцами
with open('Goods_Sulpak.csv', 'w', encoding='utf-8', newline='') as fil:
writer = csv.writer(fil)
writer.writerow((
"Индекс",
"Наименование",
"Стоимость"
))
url = 'https://www.sulpak.kz/f/smartfoniy?page=1' # делаем запрос на страницу
response = requests.get(url) # обработка запроса метотдом get
soup = BeautifulSoup(response.text, 'lxml') # создаем суп
items = soup.find_all('li', class_='tile-container') # разбираем ячейки товара
if page <= pages: # Если текущая страница меньше или равно количеству страниц всего
for n, i in enumerate(items, start=n): # задаем нумерацию для всех ячеек
itemName = i.find('h3', class_='title').text.replace("Смартфон",
"").strip() # делаем выборку из супа. Тут в теге ищем клас с названием класса
itemPrice = i.find('div', class_='price') # Ищем второе значение
if itemPrice is None: # Обработчик, если вдруг нет искомого класса или тега во втором значении
itemPrice = 'No Price' #
else:
itemPrice = itemPrice.text.replace("Цена:", "").replace("₸", "").strip() # Удаляем лишний текст и переносы
Index = n
GoodName = itemName
Price = itemPrice
print(Index, GoodName, Price) # Выводим на экран список товаров
# записываем полученные данные в csv построчно
with open('Goods_Sulpak.csv', 'a', encoding='utf-8', newline='') as fil:
writer = csv.writer(fil)
writer.writerow((
Index,
GoodName,
Price
))
Вот финальный код, где применен ответ Johan с перебором заданного диапазона страниц
import csv
import requests
from bs4 import BeautifulSoup
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
'application/signed-exchange;v=b3;q=0.9',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/104.0.5112.102 Safari/537.36 OPR/90.0.4480.84 (Edition Yx 08) '
}
params = {'page': 1}
# задаем число больше номера первой страницы, для старта цикла
pages = 20 # количество страниц
page = 1 # номер начальной страницы
n = 1 # Начало нумерации
# создаем csv документ с нужными столбцами
with open('Goods_Sulpak.csv', 'w', encoding='utf-8-sig', newline='') as fil:
writer = csv.writer(fil, delimiter = ";")
writer.writerow((
"Индекс",
"Наименование",
"Стоимость"
))
while True:
url = 'https://www.sulpak.kz/f/smartfoniy?page=' + str(page) # делаем запрос на страницу
response = requests.get(url) # обработка запроса методом get
soup = BeautifulSoup(response.text, 'lxml') # создаем суп
items = soup.find_all('li', class_='tile-container') # разбираем ячейки товара
if page <= pages: # Если текущая страница меньше или равно количеству страниц всего
for n, i in enumerate(items, start=n): # задаем нумерацию для всех ячеек
itemName = i.find('h3', class_='title').text.replace("Смартфон",
"").strip() # делаем выборку из супа. Тут в теге ищем класс с названием класса
itemPrice = i.find('div', class_='price') # Ищем второе значение
if itemPrice is None: # Обработчик, если вдруг нет искомого класса или тега во втором значении
itemPrice = 'No Price' #
else:
itemPrice = itemPrice.text.replace("Цена:", "").replace("₸", "").strip() # Удаляем лишний текст и переносы
Index = n
GoodName = itemName
Price = itemPrice
print(Index, GoodName, Price) # Выводим на экран список товаров
# записываем полученные данные в csv построчно
with open('Goods_Sulpak.csv', 'a', encoding='utf-8-sig', newline='') as fil:
writer = csv.writer(fil, delimiter = ";")
writer.writerow((
Index,
GoodName,
Price
))
page += 1 # Прибавляем каждый цикл единицу к текущей странице
else: # Если достигли последней страницы, то завершаем цикл.
break
На правах варианта реализации:
pip install fake-useragent requests tqdm pydantic bs4
from typing import List
import csv
from bs4 import BeautifulSoup as Soup
from fake_useragent import UserAgent
from pydantic import BaseModel, Field
from requests import Session
from tqdm import tqdm
u_agent = UserAgent()
s = Session()
s.headers.update(
{'User-Agent': u_agent.firefox}
)
base_url = 'https://www.sulpak.kz'
class Good(BaseModel):
id: int = Field(alias='Index')
name: str = Field(alias='Good Name')
price: float = Field(0, alias='Price')
class Config:
allow_population_by_field_name = True
validate_assignment = True
@classmethod
def fields(cls):
return list(cls.schema(by_alias=True).get('properties').keys())
goods: List[Good] = []
def process_page(page=1):
# noinspection SpellCheckingInspection
response = s.get(
base_url + '/f/smartfoniy',
params={
'page': page
}
)
soup = Soup(response.content, 'html.parser')
total_pages = int(
paginator.get('data-pagescount') if (
paginator := soup.find(
'div', {'class': 'pagination'}
)
) else 1
)
for item in soup.find_all('li', {'class': 'tile-container'}):
good = Good(
id=item.get('data-code'),
name=item.get('data-name')
)
if price := item.get('data-price'):
good.price = price
goods.append(
good
)
return total_pages
for p in tqdm(range(2, (pages := process_page()) + 1), initial=1, total=pages):
process_page(p)
with open('result.csv', 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=Good.fields(), dialect=csv.excel)
writer.writeheader()
writer.writerows(
[
item.dict(by_alias=True) for item in goods
]
)
# 100%|██████████| 19/19 [00:08<00:00, 2.14it/s]
Вариант для пошустрее:
Правда на 19-ти страницах так-себе прирост скорости но "чтобы было"
# В импорт добавляем
from multiprocessing.pool import ThreadPool as Pool
# Дальше здесь оcтальной код без изменения до цикла
with Pool(8) as pool:
for _ in tqdm(
pool.imap_unordered(
process_page,
range(
2,
(pages := process_page()) + 1)
),
initial=1,
total=pages
):
pass
# 100%|██████████| 19/19 [00:02<00:00, 6.02it/s]
# Запись в файл тоже не меняется
Не стоит злоупотреблять с многопоточнгостью при парсинге ибо забанят нафиг с гарантией в 95%
