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

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

Данный парсер собирает информацию со страниц сайта, которые содержат в своём адресе 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
3 соединённые точки
0

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

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

Парсер интернет магазина, пример.

Часы
16.11.2024
Глазик
42
Сердечки
0
Соединённые точки
0
Это туториал с примером показывающий то, как нужно писать парсеры интернет магазинов с обходами блокировок при помощи прокси и их ротацией. Используя Selenium и некоторые самолично сделанные инструменты. Всё это …