Python

Парсинг сайтов на Python для личных нужд своими руками

У веб-мастеров часто возникает ситуация, когда надо проанализировать чужой сайт или просто спарсить с него часть контента. Это может пригодиться для того, чтобы на основе полученного чужого контента делать дорвеи, отдавать текст на рерайт другим авторам или просто проводить первичный автоматический перевод с Google Translate.

Да и вообще: автоматический парсинг сайта или сайтов полезен в куче прикладных вещей, слабо связанных непосредственно с последующей контент-маркетингом. Например, достаточно часто техника применяется для парсинга страниц интернет-магазина, когда его лень наполнять самостоятельно; часто техника применяется для парсинга цен и уведомлении о наиболее приятной цены. Нередко парсинг сайтов (web dcraping) применяется просто для мониторинга новостей по определенной метке или тегу.

Впервые я столкнулся с Web Scraping как таковым достаточно давно, еще когда работал в web-студии. Тогда я пользовался Scrapinghub, однако, инструмент нельзя было назвать гибким. С появлением некоторого свободного времени и увлечении Python решено было сделать собственный инструмент. Однако, как показала праткика, сделать универсальный инструмент на все случаи жизни не получается: слишком разные сайты необходимо парсить, и к каждому нужен индивидуальный подход. В результате появился некоторый опыт, с которым и хотелось бы поделиться. Python-програмимсты Middle уровня и выше вряд ли найдут в этой статье что-то полезное, но новичкам, думаю, статья будет в масть.

Инструменты

Инспектор объектов Chrome

Как уже было сказано, уникальный скрипт создать весьма сложно. Данные хранятся в разных дивах разного уровня вложенности, и везде есть своя специфика. В 99% случаев перед непосредственно парсингом приходится вдумчиво изучать HTML-структуру исследуемых страниц, править код, и  только потом натравливать скрипт на домен.

В подавляющем большинстве случаев для этого хватает встроенного в Chrome инспектора объектов. Например, хабр на странице поста хранит название статьи в строке post__title-text блока post__title post__title_full.

Scrapy

Есть готовый фреймворк для парсинга – Scrapy. Он удобен тем, что позволяет работать с куками (а, следовательно, обходит примитивные бот-детекторы), работать с задержкой, адекватно обрабатывает исключения. Однако вход к Scrapy достаточно высок, и лично я бросил этот фреймворк на полпути: мои прикладные задачи позволяют обходиться артиллерией меньшего калибра.

К тому же, для вебмастера, который пытается взять в займы чужой контент для своих доров, функционал Scrapy может показаться избыточным.

BeautifulSoup

BeautifulSoup – правая рука моего рукожопия. Она позволяет достаточно легко обрабатывать HTML, полученный с помощью библиотеки requests. Своего рода, золотая середина между собственный велосипедом и Scrapy, середина, которой лично я и пользуюсь.

Работает с BeautifulSoup, вам нужно быть готовым  ктому, что вам придется самостоятельно обрабатывать исключения, в том числе связанные с блокировкой IP, самостоятельно думать об анонимизации и проксях, самостоятельно же городить способ экспортирования данных.

Основной функционал

Основной функционал заключается в том, чтобы просто забрать текст из нужного нам блока. Для этого стоит разобраться, в каких div-ах блок находится. Допустим, у нас есть следующая структура документа:

<div class=“page”>
     <div class=“entrytext'”>
          Нужный контент, который мы и сохраним.
     </div>
</div>

Соответственно, чтобы достать «Нужный контент, который мы и сохраним», требуется сделать такое:

from bs4 import BeautifulSoup

s=requests.get(“http://primer-domena.ru/primer-stranisy.hrml”)
b=bs4.BeautifulSoup(s.text, "html.parser")
array=b.select('.page .entrytext')
result= array.getText()

Соответственно, если блоков, удовлетворяющих условию селектора несколько, то нужно выбрать нужный индекс в массиве p3. Ну а потом уже решать, что вам нужно, а что нет.

Возможные проблемы

Названия классов и их ID

Методология следования названию классов помогает в 90% случаев. Некоторые сайты автоматически генерируют названия класса (привет, WineStyle!) именно для того, чтобы их не парсили. В этом случае иногда помогает привязываться к id блока, а не его class. А иногда – ен помогает.

Использованиt заголовков и user-agent

Некоторые серверы не пускают вас к себе, если у вас пустой заголовок запроса.  Вэтом случае. А некоторые сайты просто показываются разный контент для разных user-agent-ов (хотя это люто наказывается поисковиками). Да и вообще – генерируя десятки (а то и сотни) запросов в секунду нужно быть готовым к бану. Для работы с этим лучше использовать библиотеку user-agent:

from user_agent import generate_user_agent
headers = {'User-Agent': generate_user_agent(device_type="desktop", os=('mac', 'linux'))}
page_response = requests.get(page_link, headers=headers)

Таймаут

Библиотека requests будет ждать ответа от сервера бесконечно долго, поэтому, лучше ограничить ее в этом удовольствии, установив тайм-аут.

page_response = requests.get(page_link, timeout=5, headers=headers)

Коды ответа сервера

Рано или поздно вас забанят. Чтобы нормально обрабатывать это событие, нужно смотреть коды состояния ответа сервера. При бане часто отдаются коды 404, 408, 403, 500.

try:
    page_response = requests.get(page_link, timeout=5)
    if page_response.status_code == 200:
        # начинаем парсинг
    else:
        print(page_response.status_code)
        # уведомляем и пробуем еще
except requests.Timeout as e:
    print("Время передохнуть")
    print(str(e))
except # остальные исключения

Можно использовать и другую конструкцию для обработки HTTP-исключений:

from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
try:
    html = urlopen("https://www.python.org/")
except HTTPError as e:
    print(e)
else:
    res = BeautifulSoup(html.read(),"html5lib")
    print(res.title)

Вторая конструкция позволяет более детально проанализировать все ошибки и разобраться в их причине.

Смена IP адресов

Библиотека Requests приятна еще и тем, что по умолчанию поддерживает работу с прокси. Делается эо следующим образом:

proxies = {'http' : 'http://10.10.0.0:0000', 
          'https': 'http://120.10.0.0:0000'}
page_response = requests.get(page_link, proxies=proxies, timeout=5)

Мины

В английском языке это явление называется Honeypots («горшочек c медом»), хотя термин «мина» мне нравится больше. Мина – это небольшая ссылка, не видимая для пользователя (например, ссылка-пиксель белого цвета), переход по которой детектируется сервером, после чего сервер банит IP, с которого пришел запрос.

Я защищаюсь от такого рода «мин» способом, достаточно простым: я заранее загружаю список URL, и заставляю скрипт идти по конкретному и конечному списку адресов, никуда не переходя.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *