CoderCastrov logo
CoderCastrov
Питон

Парсинг веб-сайтов с использованием Python: изучите Beautiful Soup, пул прокси-серверов и поддельный user-agent

Парсинг веб-сайтов с использованием Python: изучите Beautiful Soup, пул прокси-серверов и поддельный user-agent
просмотров
7 мин чтение
#Питон

_Статья написана Томмазо Синьори, Уильям ЖАК, Умар Нианг и _Виталь Щутский

Интернет содержит огромное количество данных. Согласно последним оценкам, в 2016 году онлайн было доступно 4,66 миллиарда веб-страниц. Это находка для специалистов по обработке данных? Не совсем. Данные веб-страниц неструктурированы. Другими словами, они требуют очистки и форматирования, чтобы быть обработанными компьютером.

В этом руководстве вы узнаете, как использовать язык Python для обхода нескольких веб-страниц, извлечения интересующей вас информации и ее организации в готовую для использования таблицу. В качестве примера мы будем использовать данные с сайта seloger.com.

Парсинг страницы по странице

Представьте, что вы хотите собрать все объявления об аренде квартир в Париже, чтобы сравнить их цены в зависимости от площади, района, агентства, количества комнат и спален. Один из способов сделать это - перейти на сайт seloger.com и вручную скопировать детали каждого объявления страница за страницей. Эта работа займет у вас часы, а может быть, и дни, так как объявлений более 5000!

К счастью, компьютеры хорошо справляются с выполнением повторяющихся задач. Давайте начнем с создания списка веб-страниц для просмотра. На сайте результаты поиска квартир в Париже отображаются на примерно 295 страницах. Каждая страница содержит 20 объявлений. Вот адреса первых трех страниц:

https://www.seloger.com/immobilier/locations/immo-paris-75/bien-appartement/?LISTING-LISTpg=**1**https://www.seloger.com/immobilier/locations/immo-paris-75/bien-appartement/?LISTING-LISTpg=**2**https://www.seloger.com/immobilier/locations/immo-paris-75/bien-appartement/?LISTING-LISTpg=**3**

Вы заметили, что они практически идентичны. Чтобы получить список из 295 веб-страниц для просмотра, достаточно изменить последнюю цифру, указывающую номер страницы. Это можно сделать с помощью следующей функции:

token = 'https://www.seloger.com/immobilier/locations/immo-paris-75/bien-appartement/?LISTING-LISTpg='def get_pages(token, nb):
    pages = []
    for i in range(1,nb+1):
        j = token + str(i)
        pages.append(j)
    return pagespages = get_pages(token,295)

Навигация по HTML документу

У нас есть список веб-страниц. Теперь, как мы можем получить доступ к каждой странице?

В Python это очень просто. Достаточно трех строк кода:

import requestsfor i in pages:
   response = requests.get(i)

На каждой итерации функция requests.get() пытается подключиться к адресу в нашем списке и сохраняет результат в переменной response. Если попытка успешна, переменная response будет содержать значение <Response [200]>, а также исходный код страницы, к которой мы хотим получить доступ. Используйте response.text, чтобы его отобразить.

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

<!DOCTYPE html>  
<html>  
    <head>
    </head>
    <body>
        <h1> Красивая студия в 10-м районе</h1>
        <p class="promo"> 1100 евро включая коммунальные услуги</p>
        <p> Телефон: 06 82 23 21 </p>  
    <body>
</html>

Заголовок объявления находится между тегами <h1></h1>. Цена квартиры и номер телефона агентства находятся между тегами <p></p>. Иногда теги могут иметь особый класс, своего рода собственное имя, которое отличает их от других тегов того же типа.

HTML-код сайта seloger.com гораздо сложнее. Мы можем просмотреть его с помощью браузера Chrome, щелкнув правой кнопкой мыши на отображаемой странице и выбрав "Просмотреть код". В Chrome есть функция поиска тегов в коде страницы по ключевым словам.

Оказывается, что информация, которую мы хотим извлечь, находится между тегами <em>...</em>:

<em class="agency-website" data-codeinsee="750118" data-codepostal="75018" data-idagence="46600" data-idannonce="119026673" data-idtiers="22739" data-idtypepublicationsourcecouplage="SL" data-nb_chambres="0" data-nb_photos="6" data-nb_pieces="1" data-position="3" data-prix="999 €" data-produitsvisibilite="AD:AC:BB:BX:AW" data-surface="32,2400016784668" data-typebien="1" data-typedetransaction="1"> Сайт </em>

Чтобы получить доступ к информации этого тега, мы должны найти его в HTML-коде нашей переменной response. Мы можем сделать это с помощью библиотеки BeautifulSoup. Сначала мы преобразуем response в объект BeautifulSoup, а затем ищем все теги <em>...</em> с помощью функции find_all(). Мы указываем в скобках, что мы хотим теги <em>...</em>, которые имеют атрибут class="agency-website":

import bs4soup = bs4.BeautifulSoup(response.text, 'html.parser')
em_box = soup.find_all("em", {"class":"agency-website"})

Мы почти достигли нашей цели! Нам осталось только извлечь по одной интересующие нас информации. Переменная em_box работает как словарь. Когда мы даем ключ одному из ее элементов, например, em_box[3]['data-prix'], она возвращает его значение: '999 €'. Мы можем сделать цикл, который извлекает все значения, которые нам нужны, и помещает их в таблицу:

import pandas as pdparameters = ['data-prix','data-codepostal','data-idagence','data-idannonce','data-nb_chambres','data-nb_pieces','data-surface','data-typebien']df_f = pd.DataFrame()
for par in parameters:
    l = []
    for el in em_box:
        j = el[par]
        l.append(j)
    l = pd.DataFrame(l, columns = [par])
    df_f = pd.concat([df_f,l], axis = 1)

Последние две строки кода объединяют извлеченные значения в столбцы и объединяют эти столбцы вместе. И вот, мы смогли спарсить страницу:


Мой, робот? Вовсе нет!

Теперь вам нужно повторить ту же операцию для оставшихся 294 страниц. На первый взгляд это может показаться простым, но после нескольких итераций ваша функция requests.get() вернет следующий ответ:

<!DOCTYPE  html >

<html>
<head>
<meta content="noindex,nofollow" name="robots"/>

Информация, содержащаяся в теге <meta/>, указывает на то, что seloger.com идентифицировал вас как робота и отказывается отправлять запрошенную страницу.

Обычно сайты защищаются от роботов, потому что они нарушают их работу. Роботы делятся на два типа: хорошие и плохие. Хорошие роботы помогают поисковым системам найти ваш сайт, следят за его состоянием и получают RSS-ленты. Плохие роботы пытаются сбить ваш сайт, скачать его содержимое или отправить спам (да, мы учим вас делать плохие вещи!).

Чтобы узнать, какие роботы разрешены на сайте, нужно прочитать его файл robots.txt. С помощью Python мы можем сделать это следующим образом:

from urllib.request import urlopen

with urlopen("[https://www.seloger.com/robots.txt](https://www.seloger.com/robots.txt)") as stream:
    print(stream.read().decode("utf-8"))

Вот фрагмент robots.txt с сайта seloger.com:

Мы видим, что он запрещает роботам, таким как Srapy, Zao, UbiCrawler, Nutch и многим другим. Имя робота предшествует User_agent. Это своего рода идентификационная карта пользователя, собираемая сайтами. Роботы просто представляются своими именами. Однако, если мы получаем доступ к seloger.com из браузера, поле User_agent будет содержать имя нашего браузера и информацию о нашей операционной системе, например: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0.

Какой User_agent у нашего робота? Попробуйте следующую команду: requests.utils.default_user_agent(). Запустив ее, вы узнаете, что ваш робот называется 'python-requests/2.21.0'. Он не входит в список плохих роботов, но это не означает, что он приветствуется. Сервер seloger.com быстро распознает его "роботическое" поведение и временно блокирует его.

В отличие от людей, роботы:

  • Не используют браузеры и поэтому имеют плохой User_agent
  • Остаются на странице очень недолго
  • Запрашивают несколько страниц одновременно с одного IP-адреса

Чтобы избежать блокировки, мы можем научить нашего робота правильно представляться, как человек, указав правильный User_agent. Чтобы не нанести вред сайту и вести себя более "человечески", мы также можем попросить нашего робота подождать несколько секунд на странице, прежде чем перейти к следующей. Кроме того, при каждом проходе мы можем изменить IP-адрес робота.

Вот как сделать эти настройки. Начнем с создания пула прокси. В Интернете можно найти несколько списков бесплатных прокси. Чтобы создать наш пул, мы использовали этот: www.proxy-list.download/HTTPS. Вам нужно будет скачать его и импортировать в свое рабочее пространство. Затем мы создаем итератор, объект, который позволит нам менять прокси при каждом подключении с помощью функции next().

import itertools as it

proxies = pd.read_csv('proxy_list.txt', header = None)
proxies = proxies.values.tolist()
proxies = list(it.chain.from_iterable(proxies))
proxy_pool = it.cycle(proxies)
proxy = next(proxy_pool)

Теперь научим нашего робота правильно представляться и быть менее нетерпеливым. Для этого мы используем библиотеки time, random и fake-useragent:

import random
import time
# !pip install fake-useragent
from fake_useragent import UserAgent

ua = UserAgent()
time.sleep(random.randrange(1,5))

Следовательно, мы изменяем нашу функцию requests.get():

response = requests.get(i,proxies={"http": proxy, "https": proxy}, headers={'User-Agent': ua.random},timeout=5)

Теперь она содержит proxy, который меняется при каждом новом выполнении строки proxy = next(proxy_pool), и фальшивую личность ua.random. Мы также добавили аргумент timeout=5, что означает, что каждая попытка подключения к странице длится максимум 5 секунд.

Бесплатные прокси никогда не очень надежны. Чтобы вернуться к странице, которую наша функция requests.get() не смогла открыть из-за плохого подключения, мы добавили следующее условие:

while len(pages) > 0:
    try:
       ...
       pages.remove(i)
    except:
       print("Skipping. Connnection error")

Цикл while будет повторять попытку подключения до исчерпания страниц для просмотра в нашем списке.

С таким запросом сервер никогда не узнает, что вы робот, и вы получите всю нужную информацию. Однако стоит отметить, что seloger.com запрещает парсинг в своих условиях использования. Выполнение кода в этом руководстве полностью лежит на вашей ответственности!

В заключение...

Техники парсинга облегчают сбор и обработку данных, разбросанных по всему интернету. Для выполнения парсинга необходимо:

  • Создать список веб-страниц для обхода.
  • Найти интересующую вас информацию в исходном коде этих страниц. Знание основ HTML поможет вам в этом этапе работы.
  • После того, как вы определили теги для извлечения, вы можете создать цикл, который повторяет ту же операцию для нескольких страниц.
  • Если сайт блокирует вас, измените User_agent и используйте прокси.

Структура веб-страниц постоянно меняется. Достаточно незначительного изменения, чтобы ваш код стал устаревшим. Поэтому время от времени вам придется обновлять свой парсер.

В целом, сайты не любят парсеры. Сбор их данных может рассматриваться как нарушение интеллектуальной собственности или даже как кража личных данных. Поэтому внимательно прочитайте условия использования сайтов, которые вы хотите парсить.

Комплексный код

Что делать с этим набором данных?

Мы приглашаем вас перейти к следующей статье, чтобы узнать, как очистить и проанализировать набор данных: Введение в очистку данных на Python с использованием библиотеки Pandas, а также статью о визуализации данных: Визуализация данных на Python с использованием библиотеки Matplotlib и Seaborn.