Введение


Недавно заглянув на КиноПоиск, я обнаружила, что за долгие годы успела оставить более 1000 оценок и подумала, что было бы интересно поисследовать эти данные подробнее: менялись ли мои вкусы в кино с течением времени? есть ли годовая/недельная сезонность в активности? коррелируют ли мои оценки с рейтингом КиноПоиска, IMDb или кинокритиков?
Но прежде чем анализировать и строить красивые графики, нужно получить данные. К сожалению, многие сервисы (и КиноПоиск не исключение) не имеют публичного API, так что, приходится засучить рукава и парсить html-страницы. Именно о том, как скачать и распарсить web-cайт, я и хочу рассказать в этой статье.
В первую очередь статья предназначена для тех, кто всегда хотел разобраться с Web Scrapping, но не доходили руки или не знал с чего начать.

Off-topic: к слову, Новый Кинопоиск под капотом использует запросы, которые возвращают данные об оценках в виде JSON, так что, задача могла быть решена и другим путем.

Задача


Задача будет состоять в том, чтобы выгрузить данные о просмотренных фильмах на КиноПоиске: название фильма (русское, английское), дату и время просмотра, оценку пользователя.
На самом деле, можно разбить работу на 2 этапа:
  • Этап 1: выгрузить и сохранить html-страницы
  • Этап 2: распарсить html в удобный для дальнейшего анализа формат (csv, json, pandas dataframe etc.)

Инструменты


Для отправки http-запросов есть немало python-библиотек, наиболее известные urllib/urllib2 и Requests. На мой вкус Requests удобнее и лаконичнее, так что, буду использовать ее.
Также необходимо выбрать библиотеку для парсинга html, небольшой research дает следующие варианты:
  • re
    Регулярные выражения, конечно, нам пригодятся, но использовать только их, на мой взгляд, слишком хардкорный путь, и они немного не для этого. Были придуманы более удобные инструменты для разбора html, так что перейдем к ним.
  • BeatifulSoup, lxml
    Это две наиболее популярные библиотеки для парсинга html и выбор одной из них, скорее, обусловлен личными предпочтениями. Более того, эти библиотеки тесно переплелись: BeautifulSoup стал использовать lxml в качестве внутреннего парсера для ускорения, а в lxml был добавлен модуль soupparser. Подробнее про плюсы и минусы этих библиотек можно почитать в обсуждении. Для сравнения подходов я буду парсить данные с помощью BeautifulSoup и используя XPath селекторы в модуле lxml.html.
  • scrapy
    Это уже не просто библиотека, а целый open-source framework для получения данных с веб-страниц. В нем есть множество полезных функций: асинхронные запросы, возможность использовать XPath и CSS селекторы для обработки данных, удобная работа с кодировками и многое другое (подробнее можно почитать тут). Если бы моя задача была не разовой выгрузкой, а production процессом, то я бы выбрала его. В текущей постановке это overkill.

Загрузка данных


Первая попытка


Приступим к выгрузке данных. Для начала, попробуем просто получить страницу по url и сохранить в локальный файл.
import requests
user_id = 12345
url = 'http://www.kinopoisk.ru/user/%d/votes/list/ord/date/page/2/#list' % (user_id) # url для второй страницы
r = requests.get(url)
with open('test.html', 'w') as output_file:
  output_file.write(r.text.encode('cp1251'))

Открываем полученный файл и видим, что все не так просто: сайт распознал в нас робота и не спешит показывать данные.
image

Разберемся, как работает браузер


Однако, у браузера отлично получается получать информацию с сайта. Посмотрим, как именно он отправляет запрос. Для этого воспользуемся панелью "Сеть" в "Инструментах разработчика" в браузере (я использую для этого Firebug), обычно нужный нам запрос — самый продолжительный.

image

Как мы видим, браузер также передает в headers UserAgent, cookie и еще ряд параметров. Для начала попробуем просто передать в header корректный UserAgent.
headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/45.0'
      }
r = requests.get(url, headers = headers)

На этот раз все получилось, теперь нам отдаются нужные данные. Стоит отметить, что иногда сайт также проверяет корректность cookie, в таком случае помогут sessions в библиотеке Requests.

Скачаем все оценки


Теперь мы умеем сохранять одну страницу с оценками. Но обычно у пользователя достаточно много оценок и нужно проитерироваться по всем страницам. Интересующий нас номер страницы легко передать непосредственно в url. Остается только вопрос: "Как понять сколько всего страниц с оценками?" Я решила эту проблему следующим образом: если указать слишком большой номер страницы, то нам вернется вот такая страница без таблицы с фильмами. Таким образом мы можем итерироваться по страницам до тех, пор пока находится блок с оценками фильмов (<div class = "profileFilmsList">).

image
Полный код для загрузки данных
import requests
# establishing session
s = requests.Session() 
s.headers.update({
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/45.0'
    })

def load_user_data(user_id, page, session):
    url = 'http://www.kinopoisk.ru/user/%d/votes/list/ord/date/page/%d/#list' % (user_id, page)
    request = session.get(url)
    return request.text

def contain_movies_data(text):
    soup = BeautifulSoup(text)
    film_list = soup.find('div', {'class': 'profileFilmsList'})
    return film_list is not None

# loading files
page = 1
while True:
    data = load_user_data(user_id, page, s)
    if contain_movies_data(data):
        with open('./page_%d.html' % (page), 'w') as output_file:
            output_file.write(data.encode('cp1251'))
            page += 1
    else:
            break


Парсинг


Немного про XPath


XPath — это язык запросов к xml и xhtml документов. Мы будем использовать XPath селекторы при работе с библиотекой lxml (документация). Рассмотрим небольшой пример работы с XPath
from lxml import html
test = '''
    <html>
        <body>
            <div class="first_level">
                <h2 align='center'>one</h2>
                <h2 align='left'>two</h2>
            </div>
            <h2>another tag</h2>
        </body>
    </html>
'''
tree = html.fromstring(test)
tree.xpath('//h2') # все h2 теги
tree.xpath('//h2[@align]') # h2 теги с атрибутом align
tree.xpath('//h2[@align="center"]') # h2 теги с атрибутом align равным "center"

div_node = tree.xpath('//div')[0] # div тег
div_node.xpath('.//h2') # все h2 теги, которые являются дочерними div ноде

Подробнее про синтаксис XPath также можно почитать на W3Schools.

Вернемся к нашей задаче


Теперь перейдем непосредственно к получению данных из html. Проще всего понять как устроена html-страница используя функцию "Инспектировать элемент" в браузере. В данном случае все довольно просто: вся таблица с оценками заключена в теге <div class = "profileFilmsList">. Выделим эту ноду:
from bs4 import BeautifulSoup
from lxml import html

# Beautiful Soup
soup = BeautifulSoup(text)
film_list = soup.find('div', {'class': 'profileFilmsList'})

# lxml 
tree = html.fromstring(text)
film_list_lxml = tree.xpath('//div[@class = "profileFilmsList"]')[0]

Каждый фильм представлен как <div class = "item"> или <div class = "item even">. Рассмотрим, как вытащить русское название фильма и ссылку на страницу фильма (также узнаем, как получить текст и значение атрибута).
image
# Beatiful Soup
movie_link = item.find('div', {'class': 'nameRus'}).find('a').get('href')
movie_desc = item.find('div', {'class': 'nameRus'}).find('a').text

# lxml
movie_link = item_lxml.xpath('.//div[@class = "nameRus"]/a/@href')[0]
movie_desc = item_lxml.xpath('.//div[@class = "nameRus"]/a/text()')[0]

Еще небольшой хинт для debug'a: для того, чтобы посмотреть, что внутри выбранной ноды в BeautifulSoup можно просто распечатать ее, а в lxml воспользоваться функцией tostring() модуля etree.
# BeatifulSoup
print item

#lxml
from lxml import etree
print etree.tostring(item_lxml)

Полный код для парсинга html-файлов под катом
def read_file(filename):
    with open(filename) as input_file:
        text = input_file.read()
    return text

def parse_user_datafile_bs(filename):
    results = []
    text = read_file(filename)

    soup = BeautifulSoup(text)
    film_list = film_list = soup.find('div', {'class': 'profileFilmsList'})
    items = film_list.find_all('div', {'class': ['item', 'item even']})
    for item in items:
        # getting movie_id
        movie_link = item.find('div', {'class': 'nameRus'}).find('a').get('href')
        movie_desc = item.find('div', {'class': 'nameRus'}).find('a').text
        movie_id = re.findall('\d+', movie_link)[0]

        # getting english name
        name_eng = item.find('div', {'class': 'nameEng'}).text

        #getting watch time
        watch_datetime = item.find('div', {'class': 'date'}).text
        date_watched, time_watched = re.match('(\d{2}\.\d{2}\.\d{4}), (\d{2}:\d{2})', watch_datetime).groups()

        # getting user rating
        user_rating = item.find('div', {'class': 'vote'}).text
        if user_rating:
            user_rating = int(user_rating)

        results.append({
                'movie_id': movie_id,
                'name_eng': name_eng,
                'date_watched': date_watched,
                'time_watched': time_watched,
                'user_rating': user_rating,
                'movie_desc': movie_desc
            })
    return results

def parse_user_datafile_lxml(filename):
    results = []
    text = read_file(filename)

    tree = html.fromstring(text)

    film_list_lxml = tree.xpath('//div[@class = "profileFilmsList"]')[0]
    items_lxml = film_list_lxml.xpath('//div[@class = "item even" or @class = "item"]')  
    for item_lxml in items_lxml:
        # getting movie id
        movie_link = item_lxml.xpath('.//div[@class = "nameRus"]/a/@href')[0]
        movie_desc = item_lxml.xpath('.//div[@class = "nameRus"]/a/text()')[0]
        movie_id = re.findall('\d+', movie_link)[0]

        # getting english name
        name_eng = item_lxml.xpath('.//div[@class = "nameEng"]/text()')[0]

        # getting watch time
        watch_datetime = item_lxml.xpath('.//div[@class = "date"]/text()')[0]
        date_watched, time_watched = re.match('(\d{2}\.\d{2}\.\d{4}), (\d{2}:\d{2})', watch_datetime).groups()

        # getting user rating
        user_rating = item_lxml.xpath('.//div[@class = "vote"]/text()')
        if user_rating:
            user_rating = int(user_rating[0])

        results.append({
                'movie_id': movie_id,
                'name_eng': name_eng,
                'date_watched': date_watched,
                'time_watched': time_watched,
                'user_rating': user_rating,
                'movie_desc': movie_desc
            })
    return results


Резюме


В результате, мы научились парсить web-сайты, познакомились с библиотеками Requests, BeautifulSoup и lxml, а также получили пригодные для дальнейшего анализа данные о просмотренных фильмах на КиноПоиске.
image
Полный код проекта можно найти на github'e.

UPD


Как отметили в комментариях, в контексте Web Scrapping'a могут оказаться полезны следующие темы:
  • Аутентификация: зачастую для того, чтобы получить данные с сайта нужно пройти аутентификацию, в простейшем случае это просто HTTP Basic Auth: логин и пароль. Тут нам снова поможет библиотека Requests. Кроме того, широко распространена oauth2: как использовать oauth2 в python можно почитать на stackoverflow. Также в комментариях есть пример от Terras того, как пройти аутентификацию в web-форме.
  • Контролы: На сайте также могут быть дополнительные web-формы (выпадающие списки, check box'ы итд). Алгоритм работы с ними примерно тот же: смотрим, что посылает браузер и отправляем эти же параметры как data в POST-запрос (Requests, stackoverflow). Также могу порекомендовать посмотреть 2й урок курса "Data Wrangling" на Udacity, где подробно рассмотрен пример scrapping сайта US Department of Transportation и посылка данных web-форм.

Комментарии (48)


  1. Terras
    27.03.2016 09:03
    +3

    Вопрос.
    Допустим у нас ряд ссылок динамически формируются с помощью JS, т.е. мы их не можем получить во время парсинга. А они нам нужны для перехода парсера по страничкам. Что делать?


    1. rSedoy
      27.03.2016 10:06
      +3

      Рендерить с помощью selenium, phantomjs и т.п. или разбираться как js их формирует (допустим выяснили что ссылки получают ajax запросом, делаем подобный запрос и парсим его). У каждого метода свои плюсы и минусы, какой использовать зависит уже от конкретного задания.


      1. EvilsInterrupt
        27.03.2016 10:08
        -4

        с помощью selenium

        Не вариант. Мелькание браузера и непонятно что происходящего с ним может очень здорово испугать пользователя. Я как-то автоматизировал одному человеку с помощью Selenium и ChromeDriver-а. Так меня потом человек раз 5 спрашивал, а его браузер не испорится ли случайно? ;)))


        1. rSedoy
          27.03.2016 10:21
          +4

          а причем тут пользователи?


        1. phantomit
          27.03.2016 11:13
          +1

          Как уже было написано ниже, в большинстве случае если смысл посмотреть какие запросы делает браузер, и понять, каким образом JS заполняет сайт. Второй вариант — Selenium. Его совсем необязательно использовать в оконном режиме, к примеру: headless firefox


          1. EvilsInterrupt
            27.03.2016 11:19

            к примеру: headless firefox

            Спасибо. Посмотрю!


        1. BSoD
          27.03.2016 15:33

          Отлично работает связка из Selenium + PhantomJS. Ничего не мелькает и при этом всё делает.
          P.S. Долго читал, медленно отвечал.


    1. fox_12
      27.03.2016 10:26

      Использовать Phantomjs либо Selenium webdriver


    1. karb0f0s
      27.03.2016 10:26

      использовать Selenium. позволяет подключать драйвер как реального (Chrome, Firefox, IE) так и виртуального (Phantom JS) браузера и через этот «браузер» получать динамически формируемые данные.


    1. mixaly4
      27.03.2016 10:26

      Эмулятор браузера вам нужен — Selenium, например. Но скорость парсинга, конечно, упадет — сами понимаете.


      1. mixaly4
        27.03.2016 11:29
        -1

        Да, с R&C аккаунтом чувствуешь себя идиотом: после одобрения комментария оказывается, что точно такой же ответ дали еще трое человек до тебя и столько же — после, а опубликовалось все это оптом.


        1. lostpassword
          27.03.2016 11:51
          +2

          Так напишите статейку, избавьтесь от проблем.)


    1. drupa
      27.03.2016 10:26

      И использовать selenium вместе с phantomjs. Пример


    1. quarckster
      27.03.2016 10:27

      Можно использовать Selenium или phantom.js для исполнения скриптов.


    1. mizhgun
      27.03.2016 11:37

      Использовать splash от kmike сотоварищи из Scrapinghub. Selenium вообще для других задач, Splash — специально заточенный под скрапинг JS-рендер на PhantomJS.


    1. excentro
      27.03.2016 16:51

      Для Scrapy можно попробовать scrapyjs. Чем и займусь в ближайшее время…


      1. mizhgun
        27.03.2016 20:31

        ScrapyJS это middleware для работы со Splash.


    1. hudson
      28.03.2016 11:39

      Из опыта могу сказать что в каждом случае подход индивидуальный. Зависит от сайта — иногда ссылка есть, но не прямо в href/src. Иногда можно понять правило ее формирования, иногда можно запросить API сайта и получить ссылку там. Иногда комбинируется несколько вариантов. Т.е. производится своеобразный реверс-инжиниринг сайта.
      В особо тяжелых случаях придется использовать (как писали выше) реальный браузер или же эмулятор, но такие на моей практике встречались нечасто.


  1. EvilsInterrupt
    27.03.2016 10:07
    +1

    Не осветили двух важных вопросов:

    1. Авторизация на сайте. Популярные по логину и паролю, и oauth2
    2. Не осветили вопроса вбивания данных в контролы на странице, к примеру на странице может быть Combobox для выбора города. Или Edit для ввода возраста. Тажке часто встречается снять\выделить галочку и т.д. и т.п.


    1. Odeann
      27.03.2016 10:53
      +1

      Отвечу на второй вопрос. Такие контролы либо перезагружают страницу и тогда мы можем получить адрес, либо грузят дополнительные данные по Ajax, тогда нам совсем просто.


    1. Terras
      27.03.2016 15:52
      +2

      USERNAME = input('Введите вашу почту: ')
      PASSWORD = input('Введите ваш пароль: ')
      
      LOGIN_URL = "***" # Страница Логина
      URL = "***" # Страница самого контента для парсинга
      
      session_requests = requests.session()
      
      def parse_one():
      
                  # Create payload
          payload = {
              "email": USERNAME,
              "password": PASSWORD
          }
      
          # Perform login
          result = session_requests.post(LOGIN_URL, data = payload, headers = dict(referer = LOGIN_URL))
      
          # Scrape journal_url
          result = session_requests.get(URL, headers = dict(referer = URL))
          soup = BeautifulSoup(result.content)

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


      1. EvilsInterrupt
        27.03.2016 16:14
        +1

        Я не про то что я не знаю. Я бы поленился подобный вопрос задавать, а просто и тупо вбил в гугл "python authentication to website", что привело бы меня к страничке на SO.
        Прочитайте внимательно формулировку предложений! Она звучит не "А не подскажите как сделать авторизацию?", нет, она звучит по-другому: "не раскрыты важные вопросы.
        По опыту могу сказать, что очень много людей ищущих ответы на вопросы в гугле не читаю больше чем 2-3 комментариев к статье, если вообще читают. Поэтому подобные вопросы должны освещаться непосредствено в самой статье!
        Мне показалось, что автор хочет передать знания, значит нужно осветить как можно больше важных вопросов, которые очень часто встают перед новичком. Если же автор ленится, то это уже не желание научить, а попытка сказать "смотрите какой я крутой".


    1. miptgirl
      27.03.2016 18:52
      +1

      Спасибо, действительно эти темы могут пригодиться при решении задач Web Scrapping'a — добавила в статью.


  1. quarckster
    27.03.2016 10:27
    +5

    Для парсинга веб-страниц есть отличный фреймворк Grab. На Хабре были статьи о нём от автора.


  1. Odeann
    27.03.2016 10:51
    +1

    Если скрэппинг нужен не на регулярной основе (например когда сервер постоянно откачивает чужой контент), а для разовой или периодических задач, когда можно запустить скрипт вручную, то мне гораздо быстрее и проще написать JS скрипт с использованием jQuery и запустить его прямо из командной строки инспектора. Тогда уже будет и правильный юзер агент и решена проблема с авторизацией и вообще проблем будет меньше.


  1. alexkuku
    27.03.2016 11:49

    Lxml получается быстрее? А если использовать beautifulsiup с бекэндом lxml разница остаётся ?


    1. miptgirl
      27.03.2016 17:30
      +2

      Стоит отметить, что BeautifulSoup выбирает оптимальный парсер из установленных:

      If you don’t specify anything, you’ll get the best HTML parser that’s installed. Beautiful Soup ranks lxml’s parser as being the best, then html5lib’s, then Python’s built-in parser
      (источник)

      Я измерила время работы на своих данных и в среднем получила такие цифры
      bs_html_parser: 0.43 секунды
      bs_lxml_parser: 0.43 секунды (значимой разницы между lxml и python's default html-parser в BS на своих данных я не вижу)
      lxml: 0.05 секунд (lxml явно выигрывает)


      1. itforge
        27.03.2016 18:07

        Я бы ещё потребление памяти померил.


  1. sledopit
    27.03.2016 12:35
    +2

    В целом тема интересная, но конкретно вашу задачу можно решить чуть проще.
    По ссылке httр://www.kinopoisk.ru/user/<user_id>/votes/list/export/xls/vs/vote/ оно вернёт вам xls файл со всей необходимой информацией (и даже больше чем нужно).
    Правда, в новом кинопоиске такую замечательную возможность выпилили. Там нужно либо извращаться, либо не пользоваться.
    У меня даже где-то shell скрипт закронен, чтобы скачивать его раз в неделю (после осенних событий я им больше так не доверяю, как это было раньше).


    1. and7ey
      27.03.2016 13:24
      +1

      Вот это действительно полезно. Как вы об этой ссылке узнали? Что-то еще подобное-полезное есть?
      (ps. кажется, у вас в ссылке p в http русская, вот корректная ссылка — http://www.kinopoisk.ru/user/<user_id>/votes/list/export/xls/vs/vote/)


      1. sledopit
        27.03.2016 13:27
        +1

        Эта ссылка доступна всем желающим авторизованным пользователям на странице собственного профиля :)
        http://www.kinopoisk.ru/user/<user_id>/votes/ > прямо под ником там есть ссылка "экспорт в MS Excel".


      1. Tomio
        27.03.2016 13:56
        +4

        Еще есть в открытом доступе ссылка, для получения рейтинга фильма или сериала:
        http://rating.kinopoisk.ru/{{kinopoisk_id}}.xml
        Вместо {{kinopoisk_id}} просто подставьте нужный ID. Например, для Зверополиса — http://www.kinopoisk.ru/film/775276/ — вам нужно подставить в ссылку id 775276
        Еще есть вот такой сервис с API для Кинопоиска — http://kinopoisk.cf/, узнал про него в свое время с Тостера


  1. EvilsInterrupt
    27.03.2016 19:29
    -1

    with open('test.html', 'w') as output_file:
      output_file.write(r.text.encode('cp1251'))

    А почему вы явно указываете кодировку?


    1. miptgirl
      27.03.2016 20:01
      +1

      Requests возвращает ответ в виде Unicode

      isinstance(r.text, unicode) # True

      При попытке записать unicode строку в файл по умолчанию будет использована кодировка ASCII и ничего не получится (ASCII не может закодировать символы >128):
      UnicodeEncodeError: 'ascii' codec can't encode characters in position 23-31: ordinal not in range(128).
      Вот поэтому я явным образом указываю кодировку cp1251.

      Если интересно, подробнее про кодировки в Python есть хорошая статья на Хабре.


      1. EvilsInterrupt
        27.03.2016 20:17

        Я про хардкод. Ведь можно же спросить про кодировку у пришедшего ответа. Если использовать urllib, то это как-то так:

        >>> from urllib.request import urlopen
        >>> URL = 'http://habrahabr.ru'
        >>> page = urlopen(URL)
        >>> page.info().get_content_charset()
        'utf-8'
        >>> charset = page.info().get_content_charset()
        >>> document = page.read().decode(charset)


        1. EvilsInterrupt
          27.03.2016 20:24

          Снят Вопрос.
          Не внимательно читаю код, в упор не видел вызова "save".
          Вы же пишите, тогда все логично ;)))


  1. mizhgun
    27.03.2016 20:50
    -1

    Вот такая конструкция
    tree.xpath('//div[@class = "profileFilmsList"]')[0]
    поломает Ваш скрапер не только на первой 4xx/5xx странице, но и просто на той, на которой элемент по какой-либо причине отсутствует — например, как это часто бывает, элементы с отсутствующими данными просто не рендерятся в HTML. При парсинге файлов оно, может быть, и не суть важно, но когда данные парсятся без сохранения в кэш это существенно.


    1. miptgirl
      28.03.2016 01:01
      +1

      Да, верно: в полноценной production версии пришлось бы обвешать код дополнительными проверками на наличие тегов/атрибутов и обрабатывать exceptional cases


  1. stagnantice
    27.03.2016 22:47
    -1

    Если честно не понятно о чем статья. О том, что такое xpath и как парсить html есть целая куча статей. О том же scrapy и grab есть статьи на хабре.

    Вот если бы в статье рассматривались какие-то хитрые обходы блокировок сайтов от роботов, ну или хотя бы была раскрыта тема подмены прокси, куков, referrer. А тут извините очень банально все.


    1. spitty
      28.03.2016 00:56
      +3

      Если ограничивать авторов в темах требованием писать всё более и более хардкорные вещи, пишущих авторов будет становится всё меньше и меньше.
      Данная статья имеет свою целевую аудиторию — тех людей, кто не является профи в разборе страниц сайтов и хочет с чего-то начать. То, что вы не относитесь к этой категории, делает статью "банальной" лишь для вас.


  1. tessio
    28.03.2016 16:14
    +1

    Возможно, стоило делать запросы к Kinopoisk в несколько потоков? Однопоточная версия скрипта не самая быстрая.


    1. miptgirl
      28.03.2016 16:21

      Да, Вы правы, это могло бы ускорить время выгрузки данных, но тут нужно пробовать: сайт может забанить за слишком частые запросы с одного IP.


      1. tessio
        28.03.2016 17:55

        Сайту ничего не мешает вас забанить и при запросах в один поток. Всё упирается в разумную паузу между отправкой запросов или пачками запросов (скажем, отправляем N запросов, засыпаем на M секунд, снова отправляем и т.д.).


      1. Tomio
        28.03.2016 21:04
        +1

        Я, кстати, когда парсил кинопоиск, тоже был забанен за частые запросы =) Но голь на выдумку хитра. Чтобы это обойти, банально нужно авторизоваться на нем, плюс сохранить куки. В итоге в один поток парсил лимитировано по 150 сериалов за запрос, включая всю информацию по сезонам и эпизодам. Ничего, живем :)
        Вот с afisha.mail.ru такое не прокатило(( Пришлось купить 3 персональных анонимных проксика, и уже с ними, рандомно выбирая при каждом запросе один из них, маилру тоже поддался и пропали баны за многократное обращение =)


  1. Stas911
    28.03.2016 18:23

    Для imhonet нового никто не писал выгрузку своих оценок фильмов\книг?


  1. lotint
    30.03.2016 16:00
    +1

    Все отлично, хорошая статья для начинающих.
    Только название можно было изменить: Web scraping
    en.wikipedia.org/wiki/Web_scraping


    1. miptgirl
      30.03.2016 18:33

      Спасибо за комментарий и замечание, поправила название.


    1. spitty
      31.03.2016 00:01
      +1

      Спасибо, lotint.
      Поймал себя на мысли, что никогда бы не заметил эту опечатку.
      Как выясняется, написание с 'pp' довольно популярно.

      www.google.ru/search?q=web+scraping
      About 557,000 results

      www.google.ru/search?q=web+scrapping
      About 460,000 results