Как написать парсер кинопоиска на python с файлами и исходным кодом

Часы
24.11.2024
Часы
15.04.2025
Часы
5 минут
Глазик
220
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0

Как он работает

Данный парсер собирает информацию со страниц сайта, которые содержат в своём адресе list. Проще говоря, это парсер страниц фильтраций и страниц пагинаций. Во каламбур :). Не стану скрывать, работает он довольно медленно, ибоб чтобы получить доступ к страницам данного сайта требуется использовать, так называемый динамический парсинг. То есть, парсинг страниц с предварительной отрисовкой и обработкой браузером.
Это первый барьер, который необходимо преодолеть, перед тем как получить доступ к необходимым страницам. Второй барьер на нашем пути, это блокировка определённых IP адресов с которых происходит парсинг. По этому используются, соответственно подобранные прокси.
Те прокси, которые ты получишь по умолчанию с исходниками данного парсера скорее всего уже не работают. По этому, не забудь заглянуть в главы о том, как такие прокси найти и как их подобрать для целевого сайта, то есть kinopoisk.ru
Рабочий парсер сайта kinopoisk можно скачать от сюда. Тебе останется только активировать виртуальное окружение и установить необходимые пакеты.

Как его сделать, установка пакетов

Данный прасер мы напишем на python с использованием сторонних библиотек и утилит. Для начала создадим виртуальное оружение и установим его. Так же необходимо скачать мой прокси ротатор.
python -m venv .venv
Если windows то:
.\.venv\Scripts\activate
Если *unix системы то:
source ./.venv/bin/activate
Теперь очередь за необходимыми пакетами
pip install selenium requests lxml beautifulsoup4 pandas openpyxl
Или можно использовать специально подготовленный файл с зависимостями для данного проекта.
pip install -r req.txt

Как его сделать, написание скрипта

Создай файл main.py в директории проекта. Наш скрипт не будет очень большим, чуть больше 100 строчек кода. Вот полностью готовый код парсера:
import pandas
from bs4 import BeautifulSoup
# For creating webdriver
from selenium import webdriver
# For navigation purpose
from selenium.webdriver.common.by import By
# For easy to send parameters to driver when set
from selenium.webdriver.firefox.options import Options
# For events handling
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

from proxy_rotator import Proxy, Rotator, load_proxies


URL = "https://www.kinopoisk.ru/lists/movies/genre--action/country--2/?b=series&ss_subscription=ANY"
TEST_FILE = "result"
RESULT_FILE = 'result'


def get_page_number(text):
soup = BeautifulSoup(text, features='lxml')
number = int(soup.find_all('a', class_="styles_page__zbGy7")[-1].text)
return number + 1

def save_to_exel(data):
# So, because of missing values length of arrays in dict are different. Pandas work just fine if write data in rows
# and then transpose it.
# See https://stackoverflow.com/questions/40442014/pandas-valueerror-arrays-must-be-all-same-length
frame = pandas.DataFrame.from_dict(data, orient='index')
frame = frame.transpose()
frame.to_excel(f"{RESULT_FILE}.xlsx")

def scrape_kinopoisk_list(text, data):
soup = BeautifulSoup(text, features='lxml')
soup_films = soup.find_all('a', class_="base-movie-main-info_link__YwtP1")
for soup_film in soup_films:
soup_children = soup_film.findChildren('div', recursive=False)
# if target element has more than default 4 children
if len(soup_children) > len(data):
for i in range(0, len(soup_children) - len(data)):
data.update({f"{i}": []})
for indx, soup_child in enumerate(soup_children):
for data_indx, key in enumerate(data):
if indx == data_indx:
data[key].append(soup_child.text)


def run():
proxies: list[Proxy] = []
# Load proxies to memory
proxies_json = load_proxies("proxies.json")
for proxy_item in proxies_json:
proxies.append(Proxy(
proxy_item['path'],
proxy_item['type'],
proxy_item['protocol'],
proxy_item['status'],
proxy_item['weight'])
)

# Making a rotator for a proxies
rotator = Rotator(proxies)
isFinished = False
index = 1
max = 3
data = {
"titles": [],
"dates": [],
'coutry, producer': [],
'actors': [],
}
while not isFinished:
try:
proxy = rotator.get()
print(f"Using proxy: {proxy}")
# Create a webdriver
firefox_opt = Options()
firefox_opt.add_argument('--headless')
firefox_opt.add_argument("--no-sandbox")
firefox_opt.add_argument("--disable-dev-shm-usage")
firefox_opt.add_argument(f"--proxy-server={proxy}")
driver = webdriver.Firefox(options=firefox_opt)
# Retrieve all reqeusts element on pages, while waiting they are loaded
for i in range(index, max):
if i == 1:
print(f"\tPage: {i} [{URL}]")
driver.get(URL)
else:
print(f"\tPage: {i} [{URL}&page={i}]")
driver.get(f'{URL}&page={i}')
items_container = WebDriverWait(driver, 1).until(
EC.presence_of_all_elements_located((By.CLASS_NAME, 'base-movie-main-info_link__YwtP1'))
)
scrape_kinopoisk_list(driver.page_source, data)
save_to_exel(data)
print(f"\tSuccess.")
if i == 1:
max = get_page_number(driver.page_source)
print(f"\tGet number of available pages. [{max - 1}]")

index = i + 1
if index >= max :
isFinished = True

except Exception as ex:
print(f"\tProxy failed")
print('Save the result')
save_to_exel(data)



if __name__ == "__main__":
run()
Дальше, чтобы было проще понять код, я разбил его на структурные элементы и функции. Начнём с главной функции run. Эта функция запускает парсинг и проводит ротацию прокси и решает, когда парсинг стоит закончить.
proxies: list[Proxy] = []
# Load proxies to memory
proxies_json = load_proxies("proxies.json")
for proxy_item in proxies_json:
proxies.append(Proxy(
proxy_item['path'],
proxy_item['type'],
proxy_item['protocol'],
proxy_item['status'],
proxy_item['weight'])
)

# Making a rotator for a proxies
rotator = Rotator(proxies)
Она же, создаёт виртуальный браузер и делает запросы необходимых URL.
# Create a webdriver
firefox_opt = Options()
firefox_opt.add_argument('--headless')
firefox_opt.add_argument("--no-sandbox")
firefox_opt.add_argument("--disable-dev-shm-usage")
firefox_opt.add_argument(f"--proxy-server={proxy}")
driver = webdriver.Firefox(options=firefox_opt)
# Retrieve all reqeusts element on pages, while waiting they are loaded
for i in range(index, max):
if i == 1:
print(f"\tPage: {i} [{URL}]")
driver.get(URL)
else:
print(f"\tPage: {i} [{URL}&page={i}]")
driver.get(f'{URL}&page={i}')
Дальше, Селениуму нужно зацепиться за что-то при загрузке. То есть начать действовать незамедлительно, как только появятся соответствующие элементы на странице. В случае с kinopoisk это элементы с классом base-movie-main-info_link__YwtP1. Вот цикл обхода по всем доступным страницам в том или ином списке.
# Retrieve all reqeusts element on pages, while waiting they are loaded
for i in range(index, max):
if i == 1:
print(f"\tPage: {i} [{URL}]")
driver.get(URL)
else:
print(f"\tPage: {i} [{URL}&page={i}]")
driver.get(f'{URL}&page={i}')
items_container = WebDriverWait(driver, 1).until(
EC.presence_of_all_elements_located((By.CLASS_NAME, 'base-movie-main-info_link__YwtP1'))
)
scrape_kinopoisk_list(driver.page_source, data)
save_to_exel(data)
print(f"\tSuccess.")
if i == 1:
max = get_page_number(driver.page_source)
print(f"\tGet number of available pages. [{max - 1}]")

index = i + 1
Стоит обратить внимание на то, что я сохраняю результат в таблицу при парсинге каждой страницы и после обхода всех страниц. Таким образом, даже если вдруг парсер "падёт", мы всё ещё сможем получить необходимые данные.
Функция get_page_number, проще простого. Находим необходимый элемент и конвертируем строку в число:
def get_page_number(text):
soup = BeautifulSoup(text, features='lxml')
number = int(soup.find_all('a', class_="styles_page__zbGy7")[-1].text)
return number + 1
Функция save_to_exel, ещё проще. Так как мы установили пакет pandas, всё что делает эта функция это создаёт особый фрейм данных и конвертирует его в таблицу:
def save_to_exel(data):
frame = pandas.DataFrame.from_dict(data, orient='index')
frame = frame.transpose()
frame.to_excel(f"{RESULT_FILE}.xlsx")
Важно отметить, чтобы работать с эксель таблицами необходимо установить пакет openpyxl. Ну и что? Спросишь ты, а то что по идее она должна быть зависимостью при установке pandas, но таковой не является. Поэтому требуется мануальная установка.
Функция scrape_kinopoisk_list ищет необходимые элементы на странице и заполняет отправленный словарь.
def scrape_kinopoisk_list(text, data):
soup = BeautifulSoup(text, features='lxml')
soup_films = soup.find_all('a', class_="base-movie-main-info_link__YwtP1")
for soup_film in soup_films:
soup_children = soup_film.findChildren('div', recursive=False)
# if target element has more than default 4 children
if len(soup_children) > len(data):
for i in range(0, len(soup_children) - len(data)):
data.update({f"{i}": []})
for indx, soup_child in enumerate(soup_children):
for data_indx, key in enumerate(data):
if indx == data_indx:
data[key].append(soup_child.text)
Это собственно всё. Осталось только запустить функцию run. То, как это делать решать тебе. Просто запустить её внизу скрипта или сделать как я, то есть:
if __name__ == "__main__":
run()

Сбор бесплатных прокси

Так как kinopoisk это сайт работающий на территории России и СНГ(по крайней мере аудитория оттуда) то и прокси должны быть оттуда, для большей правдоподобности наших парсеров на обычных пользователей.
Итак, как и откуда можно взять бесплатные прокси? Представляю тебе моего скраппера бесплатных прокси, с возможностью выбора и фильтрации прокси по странам, используемым протоколам и типу самих прокси.
Чтобы собрать только русские прокси с используемыми протоколоми http, https, socks4, socks5 введи следующую команду:
python .\main.py -p http, https, socks4, socks5 -c RU, BY, KZ
Если хочешь узнать какие коды, каким странам соответствуют введи:
python .\main.py -HC
В результате ты получишь JSON файлы со списками прокси. Все такие файлы находятся в директории data. Всё происходит в параллельном режиме и ты можешь в любой момент остановить скрипт если посчитаешь, что тебе хватит.

Проверка бесплатных прокси

Итак, мы собрали сотни прокси и могу тебе гарантировать большинство из них это откровенный мусор. Нам нужно будет его отфильтровать, при чём, отфильтровать используя в качестве фильтра целевой сайт, то есть kinopoisk.
Видишь ли, большинство сайтов, которые предоставляют прокси, проверяют живы ли они просто пингуя их. Но это не значит что эти прокси будут работать с тем или иным сайтом.
Для проверки работоспособности прокси на том или ином сайте, я создал специальный CLI инструмент. Который ты можешь скачать по ссылке в предыдущем предложении (⊙_(⊙_⊙)_⊙). Вот как проверить список таких прокси, команда:
python .\main.py -i 0_64.json -o .\res.json -U https://kinopoisk.ru
Где -i это те прокси которые ты получил используя мой скрапер прокси
Где -o это имя файла-результата где каждому прокси будет присвоен вес.
Где -U это список сайтов проверки
Больше опций и вариантов можно посмотреть используя -h флаг. Но в данном случае нас больше заинтересует log.txt файл. Ведь там хранятся результаты проверок каждого прокси и то сколько раз он успешно подключался к целевому сайту. Выбери самые успешные прокси и объедини их в один JSON файл, который потом будешь использовать для парсинга сайтов.

Комментарии

(0)

captcha
Отправить
Сейчас тут пусто. Буть первым (o゚v゚)ノ

Другое

Похожие статьи


Простой парсер на python

Часы
10.12.2024
Глазик
337
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье я покажу как написать простой парсер на python, на примере того, как собирать изображения с сайта. Данный парсер представляет из себя пример того, как парсить статические и …

Пишем парсер поисковой выдачи google

Часы
15.02.2025
Глазик
260
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье будет описан процесс написания парсера поисковой выдачи google используя их официальный API. Покажу как получить API-ключ и ID поисковой машины. А так же предоставлю примеры и открытый …

Использованные термины


Релевантные вопросы