Добрый день! Мне было нечем заняться и решил создать через CHATGPT парсер отзывов стим Для полный работы бота введите эту команду в cmd pip install aiohttp pandas openpyxl Весь парсинг конвертируется в .xlsx ( Потому что так удобно сортировать список через Excel) В самом боте имеется проверка на кол-во отзывов на игру Какие данные входят: Гиперссылка на отзыв Текст отзыва Положительный/Отрицательный отзыв Кол-во helpful Кол-во funny Кол-во игр автора отзыва Кол-во отзывов автора Общее кол-во часов в игре Где найти appid от игры? Заходите на любую страницу игры стима и в url будет номерок, он нам и нужен Скидываю это сюда, может кому-то понадобится такой парсер) Написан он через CHATGPT Сам код: import asyncio import aiohttp import pandas as pd import tkinter as tk from tkinter import ttk, messagebox import threading class SteamReviewsApp: def __init__(self, root): self.root = root root.title("Steam Reviews Downloader") root.geometry("450x360") root.resizable(False, False) root.configure(bg="#1e1e2f") # Стили style = ttk.Style(root) style.theme_use('clam') style.configure("TLabel", background="#1e1e2f", foreground="#e0e0e0", font=("Segoe UI", 11)) style.configure("TButton", background="#4a4e69", foreground="#e0e0e0", font=("Segoe UI", 11, "bold"), padding=8) style.map("TButton", background=[('active', '#22223b')]) style.configure("TEntry", fieldbackground="#2a2a40", foreground="#f0f0f0", font=("Segoe UI", 11), padding=5) # Отступы вокруг элементов pad_opts = {'padx': 15, 'pady': 10} # Заголовок title_lbl = ttk.Label(root, text="Загрузчик отзывов Steam", font=("Segoe UI", 18, "bold"), foreground="#f2e9e4", background="#1e1e2f") title_lbl.pack(pady=(15,5)) # Frame для ввода appid и кнопки проверки input_frame = ttk.Frame(root) input_frame.pack(pady=(10, 0), fill="x", padx=15) ttk.Label(input_frame, text="AppID игры:").grid(row=0, column=0, sticky="w", **pad_opts) self.appid_entry = ttk.Entry(input_frame, width=20) self.appid_entry.grid(row=0, column=1, sticky="w") self.check_button = ttk.Button(input_frame, text="Проверить", command=self.check_reviews_count) self.check_button.grid(row=0, column=2, padx=10) self.available_label = ttk.Label(root, text="Всего отзывов: не проверено", font=("Segoe UI", 10), foreground="#a7a9be", background="#1e1e2f") self.available_label.pack(anchor="w", padx=25, pady=(3, 10)) # Кол-во отзывов count_frame = ttk.Frame(root) count_frame.pack(fill="x", padx=15) ttk.Label(count_frame, text="Количество отзывов:").grid(row=0, column=0, sticky="w", **pad_opts) self.num_entry = ttk.Entry(count_frame, width=20) self.num_entry.grid(row=0, column=1, sticky="w") # Progress bar self.progress_var = tk.IntVar() self.progressbar = ttk.Progressbar(root, maximum=100, variable=self.progress_var, length=400) self.progressbar.pack(pady=(20, 10)) # Статус self.status_label = ttk.Label(root, text="Готов к запуску", font=("Segoe UI", 10), foreground="#a7a9be", background="#1e1e2f") self.status_label.pack() # Кнопка запуска self.start_button = ttk.Button(root, text="Начать загрузку", command=self.start_download) self.start_button.pack(pady=20, ipadx=10, ipady=5) self.loop = None self.reviews_downloaded = 0 self.total_reviews = 0 self.available_reviews = None def check_reviews_count(self): appid = self.appid_entry.get().strip() if not appid.isdigit(): messagebox.showerror("Ошибка", "Введите корректный numeric AppID") return self.available_label.config(text="Проверка...", foreground="#f2cc8f") self.check_button.config(state="disabled") threading.Thread(target=self.async_check_reviews_count, args=(appid,), daemon=True).start() def async_check_reviews_count(self, appid): try: count = asyncio.run(self.fetch_total_reviews(appid)) self.available_reviews = count self.available_label.config(text=f"Всего доступно отзывов: {count}", foreground="#90be6d") except Exception as e: self.available_label.config(text=f"Ошибка проверки: {e}", foreground="#f05454") finally: self.check_button.config(state="normal") async def fetch_total_reviews(self, appid): url = f"https://store.steampowered.com/appreviews/{appid}" params = { 'json': 1, 'filter': 'all', 'language': 'all', 'day_range': 9223372036854775807, 'review_type': 'all', 'purchase_type': 'all', 'num_per_page': 1, 'cursor': '*', } headers = {"User-Agent": "Mozilla/5.0"} async with aiohttp.ClientSession() as session: async with session.get(url, params=params, headers=headers) as resp: resp.raise_for_status() data = await resp.json() return data.get('query_summary', {}).get('total_reviews', 0) def start_download(self): appid = self.appid_entry.get().strip() num_str = self.num_entry.get().strip() if not appid.isdigit(): messagebox.showerror("Ошибка", "Введите корректный numeric AppID") return if not num_str.isdigit() or int(num_str) <= 0: messagebox.showerror("Ошибка", "Введите положительное число отзывов") return if self.available_reviews is not None and int(num_str) > self.available_reviews: if not messagebox.askyesno("Подтверждение", f"Запрошено {num_str} отзывов, а доступно только {self.available_reviews}. Продолжить?"): return self.total_reviews = int(num_str) self.reviews_downloaded = 0 self.progress_var.set(0) self.status_label.config(text="Запуск загрузки...", foreground="#f2cc8f") self.start_button.config(state="disabled") self.check_button.config(state="disabled") threading.Thread(target=self.run_async_download, args=(appid, self.total_reviews), daemon=True).start() def run_async_download(self, appid, total_reviews): asyncio.run(self.async_download(appid, total_reviews)) async def async_download(self, appid, total_reviews): try: reviews = await self.fetch_reviews(appid, total_reviews) self.save_to_excel(reviews, appid) self.status_label.config(text=f"Загрузка завершена. Отзывы сохранены в {appid}_reviews.xlsx", foreground="#90be6d") except Exception as e: self.status_label.config(text=f"Ошибка: {e}", foreground="#f05454") finally: self.start_button.config(state="normal") self.check_button.config(state="normal") async def fetch_reviews(self, appid, max_reviews): reviews = [] seen_ids = set() cursor = '*' headers = {"User-Agent": "Mozilla/5.0"} async with aiohttp.ClientSession() as session: while len(reviews) < max_reviews: url = f"https://store.steampowered.com/appreviews/{appid}" params = { 'json': 1, 'filter': 'all', 'language': 'all', 'day_range': 9223372036854775807, 'review_type': 'all', 'purchase_type': 'all', 'num_per_page': 100, 'cursor': cursor, } async with session.get(url, params=params, headers=headers) as resp: resp.raise_for_status() data = await resp.json() batch_reviews = data.get('reviews', []) if not batch_reviews: break for r in batch_reviews: rid = r.get('recommendationid') if rid not in seen_ids: seen_ids.add(rid) steamid = r['author']['steamid'] recommendationid = rid review_url = f"https://steamcommunity.com/profiles/{steamid}/recommended/{appid}/#review_{recommendationid}" review_text = r.get('review', '') recommended = "Положительный" if r.get('voted_up', False) else "Отрицательный" helpful = r.get('votes_up', 0) funny = r.get('votes_funny', 0) games_owned = r['author'].get('num_games_owned', 0) reviews_posted = r['author'].get('num_reviews', 0) hours_played = r['author'].get('playtime_forever', 0) / 60 reviews.append({ 'Автор': steamid, 'Ссылка на отзыв': review_url, 'Отзыв': review_text, 'Тип отзыва': recommended, 'Helpful': helpful, 'Funny': funny, 'Кол-во игр': games_owned, 'Кол-во отзывов': reviews_posted, 'Часов наиграно': round(hours_played, 2), }) self.reviews_downloaded += 1 self.root.after(0, self.update_progress) if len(reviews) >= max_reviews: break cursor = data.get('cursor') if not cursor: break return reviews def update_progress(self): percent = int(self.reviews_downloaded / self.total_reviews * 100) self.progress_var.set(percent) self.status_label.config(text=f"Загружено отзывов: {self.reviews_downloaded} из {self.total_reviews}", foreground="#e0e0e0") def save_to_excel(self, reviews, appid): df = pd.DataFrame(reviews) def make_hyperlink(row): return f'=HYPERLINK("{row["Ссылка на отзыв"]}", "{row["Автор"]}")' df['Ссылка на отзыв'] = df.apply(make_hyperlink, axis=1) df.drop(columns=['Автор'], inplace=True) filename = f"{appid}_reviews.xlsx" df.to_excel(filename, index=False) if __name__ == "__main__": root = tk.Tk() app = SteamReviewsApp(root) root.mainloop() Python import asyncio import aiohttp import pandas as pd import tkinter as tk from tkinter import ttk, messagebox import threading class SteamReviewsApp: def __init__(self, root): self.root = root root.title("Steam Reviews Downloader") root.geometry("450x360") root.resizable(False, False) root.configure(bg="#1e1e2f") # Стили style = ttk.Style(root) style.theme_use('clam') style.configure("TLabel", background="#1e1e2f", foreground="#e0e0e0", font=("Segoe UI", 11)) style.configure("TButton", background="#4a4e69", foreground="#e0e0e0", font=("Segoe UI", 11, "bold"), padding=8) style.map("TButton", background=[('active', '#22223b')]) style.configure("TEntry", fieldbackground="#2a2a40", foreground="#f0f0f0", font=("Segoe UI", 11), padding=5) # Отступы вокруг элементов pad_opts = {'padx': 15, 'pady': 10} # Заголовок title_lbl = ttk.Label(root, text="Загрузчик отзывов Steam", font=("Segoe UI", 18, "bold"), foreground="#f2e9e4", background="#1e1e2f") title_lbl.pack(pady=(15,5)) # Frame для ввода appid и кнопки проверки input_frame = ttk.Frame(root) input_frame.pack(pady=(10, 0), fill="x", padx=15) ttk.Label(input_frame, text="AppID игры:").grid(row=0, column=0, sticky="w", **pad_opts) self.appid_entry = ttk.Entry(input_frame, width=20) self.appid_entry.grid(row=0, column=1, sticky="w") self.check_button = ttk.Button(input_frame, text="Проверить", command=self.check_reviews_count) self.check_button.grid(row=0, column=2, padx=10) self.available_label = ttk.Label(root, text="Всего отзывов: не проверено", font=("Segoe UI", 10), foreground="#a7a9be", background="#1e1e2f") self.available_label.pack(anchor="w", padx=25, pady=(3, 10)) # Кол-во отзывов count_frame = ttk.Frame(root) count_frame.pack(fill="x", padx=15) ttk.Label(count_frame, text="Количество отзывов:").grid(row=0, column=0, sticky="w", **pad_opts) self.num_entry = ttk.Entry(count_frame, width=20) self.num_entry.grid(row=0, column=1, sticky="w") # Progress bar self.progress_var = tk.IntVar() self.progressbar = ttk.Progressbar(root, maximum=100, variable=self.progress_var, length=400) self.progressbar.pack(pady=(20, 10)) # Статус self.status_label = ttk.Label(root, text="Готов к запуску", font=("Segoe UI", 10), foreground="#a7a9be", background="#1e1e2f") self.status_label.pack() # Кнопка запуска self.start_button = ttk.Button(root, text="Начать загрузку", command=self.start_download) self.start_button.pack(pady=20, ipadx=10, ipady=5) self.loop = None self.reviews_downloaded = 0 self.total_reviews = 0 self.available_reviews = None def check_reviews_count(self): appid = self.appid_entry.get().strip() if not appid.isdigit(): messagebox.showerror("Ошибка", "Введите корректный numeric AppID") return self.available_label.config(text="Проверка...", foreground="#f2cc8f") self.check_button.config(state="disabled") threading.Thread(target=self.async_check_reviews_count, args=(appid,), daemon=True).start() def async_check_reviews_count(self, appid): try: count = asyncio.run(self.fetch_total_reviews(appid)) self.available_reviews = count self.available_label.config(text=f"Всего доступно отзывов: {count}", foreground="#90be6d") except Exception as e: self.available_label.config(text=f"Ошибка проверки: {e}", foreground="#f05454") finally: self.check_button.config(state="normal") async def fetch_total_reviews(self, appid): url = f"https://store.steampowered.com/appreviews/{appid}" params = { 'json': 1, 'filter': 'all', 'language': 'all', 'day_range': 9223372036854775807, 'review_type': 'all', 'purchase_type': 'all', 'num_per_page': 1, 'cursor': '*', } headers = {"User-Agent": "Mozilla/5.0"} async with aiohttp.ClientSession() as session: async with session.get(url, params=params, headers=headers) as resp: resp.raise_for_status() data = await resp.json() return data.get('query_summary', {}).get('total_reviews', 0) def start_download(self): appid = self.appid_entry.get().strip() num_str = self.num_entry.get().strip() if not appid.isdigit(): messagebox.showerror("Ошибка", "Введите корректный numeric AppID") return if not num_str.isdigit() or int(num_str) <= 0: messagebox.showerror("Ошибка", "Введите положительное число отзывов") return if self.available_reviews is not None and int(num_str) > self.available_reviews: if not messagebox.askyesno("Подтверждение", f"Запрошено {num_str} отзывов, а доступно только {self.available_reviews}. Продолжить?"): return self.total_reviews = int(num_str) self.reviews_downloaded = 0 self.progress_var.set(0) self.status_label.config(text="Запуск загрузки...", foreground="#f2cc8f") self.start_button.config(state="disabled") self.check_button.config(state="disabled") threading.Thread(target=self.run_async_download, args=(appid, self.total_reviews), daemon=True).start() def run_async_download(self, appid, total_reviews): asyncio.run(self.async_download(appid, total_reviews)) async def async_download(self, appid, total_reviews): try: reviews = await self.fetch_reviews(appid, total_reviews) self.save_to_excel(reviews, appid) self.status_label.config(text=f"Загрузка завершена. Отзывы сохранены в {appid}_reviews.xlsx", foreground="#90be6d") except Exception as e: self.status_label.config(text=f"Ошибка: {e}", foreground="#f05454") finally: self.start_button.config(state="normal") self.check_button.config(state="normal") async def fetch_reviews(self, appid, max_reviews): reviews = [] seen_ids = set() cursor = '*' headers = {"User-Agent": "Mozilla/5.0"} async with aiohttp.ClientSession() as session: while len(reviews) < max_reviews: url = f"https://store.steampowered.com/appreviews/{appid}" params = { 'json': 1, 'filter': 'all', 'language': 'all', 'day_range': 9223372036854775807, 'review_type': 'all', 'purchase_type': 'all', 'num_per_page': 100, 'cursor': cursor, } async with session.get(url, params=params, headers=headers) as resp: resp.raise_for_status() data = await resp.json() batch_reviews = data.get('reviews', []) if not batch_reviews: break for r in batch_reviews: rid = r.get('recommendationid') if rid not in seen_ids: seen_ids.add(rid) steamid = r['author']['steamid'] recommendationid = rid review_url = f"https://steamcommunity.com/profiles/{steamid}/recommended/{appid}/#review_{recommendationid}" review_text = r.get('review', '') recommended = "Положительный" if r.get('voted_up', False) else "Отрицательный" helpful = r.get('votes_up', 0) funny = r.get('votes_funny', 0) games_owned = r['author'].get('num_games_owned', 0) reviews_posted = r['author'].get('num_reviews', 0) hours_played = r['author'].get('playtime_forever', 0) / 60 reviews.append({ 'Автор': steamid, 'Ссылка на отзыв': review_url, 'Отзыв': review_text, 'Тип отзыва': recommended, 'Helpful': helpful, 'Funny': funny, 'Кол-во игр': games_owned, 'Кол-во отзывов': reviews_posted, 'Часов наиграно': round(hours_played, 2), }) self.reviews_downloaded += 1 self.root.after(0, self.update_progress) if len(reviews) >= max_reviews: break cursor = data.get('cursor') if not cursor: break return reviews def update_progress(self): percent = int(self.reviews_downloaded / self.total_reviews * 100) self.progress_var.set(percent) self.status_label.config(text=f"Загружено отзывов: {self.reviews_downloaded} из {self.total_reviews}", foreground="#e0e0e0") def save_to_excel(self, reviews, appid): df = pd.DataFrame(reviews) def make_hyperlink(row): return f'=HYPERLINK("{row["Ссылка на отзыв"]}", "{row["Автор"]}")' df['Ссылка на отзыв'] = df.apply(make_hyperlink, axis=1) df.drop(columns=['Автор'], inplace=True) filename = f"{appid}_reviews.xlsx" df.to_excel(filename, index=False) if __name__ == "__main__": root = tk.Tk() app = SteamReviewsApp(root) root.mainloop()