Написание простого парсера на python

Часы
10.12.2024
Глазик
51
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0

Вводная

В этой статье я приведу пример самого простого парсера на питоне. В двух версиях. Первая это парсер статических страниц, вторая это парсер динамически-загружаеммых страниц. Для того, кто пишет парсеры разница между ними не очень большая, но есть. В обоих случаях, для сбора ссылок на изображения я использовал BeautifulSoup4.
Так как, разрешения парсить чужие сайты я пока не получил, решил показать на примере своего. Будем парсить "страницу обо мне". Там есть как встроенные изображения, так и динамически прогружаемые. Идеальный пример короче.
Если нужен исходный код и готовый проект, то вот прошу.
Да, заранее извиняюсь за использование @декоратов. Но они мне просто нравятся, поэтому данный парсер будет с небольшим "синтаксическим сахаром". Вот, учти.

Базовый скрипт, его устройство и работа

Начнём пожалуй с установки необходимых пакетов и их импорта. Нам потребуются 3 пакета:
  1. requests - Для отправки запросов на целевой сайт и получение веб-страниц. Требуется для статического парсинга.
  2. selenium - Для создания браузера-без-интерфейса и отправки с него запросов. Требуется для динамического парсинга.
  3. beautifulsoup4 - Для самого скрапинга. Нахождение необходимых элементов и преобразование, сохранение его в нужном формате (файлы, списки ...)
Создадим виртуальное окружение и установим вышеперечисленные пакеты:
python -m venv .venv
Данная команда создаст директорию .venv и загрузит туда базовые библиотеки, необходимые для питона, вместе с его исполняемым файлом.
Теперь активируем (войдём) в виртуальное окружение, для Windows:
.\.vevn\Scripts\Activate.ps1
А это для Linux:
source ./.venv/bin/activate
После активации виртуального окружения, наконец, установим необходимые пакеты:
pip install requests selenium beautifulsoup4
Теперь создадим файл main.py. Добавим необходимые импорты и целевой урл в файл:
# FOR DECORATORS
import functools
# FOR STATIC SCRAPING
import requests
from bs4 import BeautifulSoup
# FOR DYNAMIC SCRAPING
# For creating webdriver
from selenium import webdriver
# For easy to send parameters to driver when set
from selenium.webdriver.firefox.options import Options


URL="https://timthewebmaster.com/ru/about/"
В самый конец файла добавь точку входа программы. Данная строчка говорит о том, что при запуске данного файла через интерпретатор python, она выполнит две функции поочерёдно, get_static и get_dynamic.
if __name__ == "__main__":
get_static(URL)
get_dynamic(URL)
О них мы поговорим в следующих главах, а пока посмотрим на мои декораторы и саму парсер-функцию:
Первый декоратор просто выводит заданное сообщение. В моём случае это имя функции. Есть и другой способ получить имя текущей функции в исполняемом потоке, но это будет уже слишком для данной статьи.
def identifier(message: str = ""):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(message)
func(*args, **kwargs)
return wrapper
return decorator_repeat
О том, как их использовать ты увидишь в следующих главах. Этот декоратор принимает строковый аргумент и выводит его на консоль перед тем, как исполнить функцию вокруг которой данный декоратор обёрнут.
Следующий декоратор работает по схожему принципу, он выводит в консоль все полученные изображения, и он не принимает никаких аргументов.
def scraped_images():
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_debug(*args, **kwargs):
value = func(*args, **kwargs)
images = scrape(value)
for image in images:
print(f'Link->[{image}]')
return value
return wrapper_debug
return decorator_repeat
Дальше нам потребуется функция самого скрапинга. Непосредственно в нем мы и указываем, при помощи чего парсим, как парсим и что парсим. Создаётся суп ...
Суп - это такой стандарт названия того что возвращает BeautifulSoup конструктор. Так принято, но ты волен сам выбирать как это назвать.
В этом "супе" мы находим все изображения и извлекаем из их данные, сохранённые в scr атрибуте. Создаётся список, который передаётся декоратору и там уже обрабатывается, то есть выводится в консоль.
def scrape(source):
soup = BeautifulSoup(source, 'lxml')
soup_images = soup.find_all('img')
images = []
for soup_image in soup_images:
images.append(soup_image['src'])
return images
Это конечно хорошо, но чтобы данная функция работал она должна получить некоторый исходник, то есть целевую страницу. И тут в дело вступают в дело две функции, о которых я говорил ранее; get_static и get_dynamic.

Парсер статических страниц

Если тебе необходимо спарсить статический сайт, то считай тебе повезло. В наши дни, таких сайтов встречаешь всё реже и реже. Но если целевой сайт такой, то данная функция будет работать как часы:
# This function defines how to get an actual content of the target page, in this case I'm using requests module
@identifier('=>> run_static <<=')
@scraped_images()
def get_static(url):
resp = requests.get(url)
return resp.text
Мы подключили 2 декоратора о которых я говорил чуть выше. Они оборачиваются вокруг функции при помощи использования символа @. Эта функция принимает в качестве аргумента урл и возвращает текст самой страницы.
Хочу отметить, что хоть я и брякнул, что мол такая функция будет работать как часы, это правда только в том случае если у сайта нет никакой защиты против парсеров, что на практике очень часто встречается. Но если такая защита есть, всегда можно попробовать замаскировать свой запрос под настоящего пользователя при помощи заголовков в запросах или использовать прокси. А если нужно спарсить множество страниц, то техника ротации (хоть прокси хоть пользовательских агентов) в помощь.

Парсер динамических страниц

Зачастую ты будешь сталкиваться с ситуацией, когда вроде бы ответ 200 и вроде бы страница вернулась, но на ней ничего нет. Так бывает, когда парсишь динамические сайты. Они просто ещё не прогрузили свой контент, а ты просто скачал шаблон без контента :(
Вот как можно получить динамическую страницу:
# This function defines how to get an actual content of the target page, in this case I'm using selenium module
@identifier('=>> run_dynamic <<=')
@scraped_images()
def get_dynamic(url):
firefox_opt = Options()
firefox_opt.add_argument('--headless')
firefox_opt.add_argument("--no-sandbox")
firefox_opt.add_argument("--disable-dev-shm-usage")
driver = webdriver.Firefox(options=firefox_opt)
driver.implicitly_wait(5)
driver.get(url)
return driver.page_source
Принцип работы тот же что и у get_static. Мы подключаем 2 декоратора. Они оборачиваются вокруг функции при помощи использования символа @. Эта функция принимает в качестве аргумента урл и возвращает текст самой страницы.
Отличие лишь в реализации того, как получается сама страничка, ну и используемые инструменты, конечно. В данном случае я использовал Firefox, но ты можешь использовать любой другой браузер, главное чтобы он был у тебя установлен.
Я так же добавил несколько опций для настройки драйвера:
  1. headless - значит не создавать и не отображать окно браузера
  2. no-sandbox - значит использовать все доступные ресурсы машины на которой запущен скрипт
  3. disable-dev-shm-usage - значит создать дополнительный файл подкачки, если не хватит
После создания драйвера, задаём значение ожидания в секундах перед тем как спарсить страницу. Это не лучшее моё решение. Лучше бы использовать таймер, который кончается при загрузке определённого контента, так он будет ждать ровно столько, сколько надо, чтобы забрать страницу. Но дело в том, что все сайты разные и нужно знать, загрузку какого элемента ожидать. Поэтому я решил пойти по универсальному решению и тупо ждать 5 секунд. (´。_。`)

Все-таки выводы

Парсер полностью готов и единственное, что остаётся это его запустить. Да, это не совсем простой парсер, но он очень нагляден и имеет в своей основе всё необходимое чтобы спарсить большинство сайтов. Вот так его запускают:
python main.py
И вот консольный вывод, который ты получишь:
=>> run_static <<=
Link->[https://mc.yandex.ru/watch/95199635]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/Main/img/bg_1.webp]
Link->[placeholder.svg]
Link->[placeholder.svg]
Link->[placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
=>> run_dynamic <<=
Link->[https://mc.yandex.ru/watch/95199635]
Link->[/static/menu.svg]
Link->[/static/favicon.svg]
Link->[/static/Main/img/bg_1.webp]
Link->[/static/Main/img/me1.jpg]
Link->[/static/Main/img/sity1.jpg]
Link->[placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
Link->[/static/placeholder.svg]
Хочешь спарсить ссылки вместо изображений? Пожалуйста. Лишь перепиши функцию scrape, и готово.
Что-то или кто-то блокирует парсин? Пожалуйста. Модифицируй get_static, добавь прокси или начни ротацию пользовательских агентов. Если и это не помогло, переходим на тяжёлую артиллерию и начинаем динамический парсинг, модифицируем и его если потребуется; функция get_dynamic.
Не нравиться вывод в консоль? Ну так всегда можно переделать и сделать собственные декораторы.
В общем ты понял. Это базовый шаблон для твоего парсера. И в случае если ты, что-нибудь не так скопировал, вот полный скрипт:
# FOR DECORATORS
import functools
# FOR STATIC SCRAPING
import requests
from bs4 import BeautifulSoup
# FOR DYNAMIC SCRAPING
# For creating webdriver
from selenium import webdriver
# For easy to send parameters to driver when set
from selenium.webdriver.firefox.options import Options


URL="https://timthewebmaster.com/ru/about/"

# Decorator to print name of function in console
def identifier(message: str = ""):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_debug(*args, **kwargs):
print(message)
func(*args, **kwargs)
return wrapper_debug
return decorator_repeat

# Decorator to print links of the images
def scraped_images():
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_debug(*args, **kwargs):
value = func(*args, **kwargs)
images = scrape(value)
for image in images:
print(f'Link->[{image}]')
return value
return wrapper_debug
return decorator_repeat

# This function defines how to, and what to scrape
def scrape(source):
soup = BeautifulSoup(source, 'lxml')
soup_images = soup.find_all('img')
images = []
for soup_image in soup_images:
images.append(soup_image['src'])
return images

# This function defines how to get an actual content of the target page, in this case I'm using requests module
@identifier('=>> run_static <<=')
@scraped_images()
def get_static(url):
resp = requests.get(url)
return resp.text
# This function defines how to get an actual content of the target page, in this case I'm using selenium module
@identifier('=>> run_dynamic <<=')
@scraped_images()
def get_dynamic(url):
firefox_opt = Options()
firefox_opt.add_argument('--headless')
firefox_opt.add_argument("--no-sandbox")
firefox_opt.add_argument("--disable-dev-shm-usage")
driver = webdriver.Firefox(options=firefox_opt)
driver.implicitly_wait(5)
driver.get(url)
return driver.page_source


if __name__ == "__main__":
get_static(URL)
get_dynamic(URL)
И на этом я откланиваюсь. <(_ _)>

Комментарии

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

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

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

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

Как написать парсер кинопоиска, с исходным кодом

Часы
24.11.2024
В этой статье я раскажу как написать парсер кинопоиска, что для этого тебе потребуется и поделюсь исходным кодом с файлами данного парсера на питоне. С возможностью собрать и подобрать прокси …