автордың кітабын онлайн тегін оқу Современный скрапинг веб-сайтов с помощью Python. 2-е межд. издание
Научный редактор С. Бычковский
Переводчик Е. Сандицкая
Литературный редактор Н. Хлебина
Художник В. Мостипан
Корректоры Н. Гринчик, Е. Павлович, Е. Рафалюк-Бузовская
Райан Митчелл
Современный скрапинг веб-сайтов с помощью Python. 2-е межд. издание . — СПб.: Питер, 2021.
ISBN 978-5-4461-1693-5
© ООО Издательство "Питер", 2021
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Введение
Если человек не слишком хорошо знаком с программированием, оно ему может показаться чем-то вроде волшебства. Но если программирование — волшебство, то веб-скрапинг — это очень сильное колдунство: написав простую автоматизированную программу, можно отправлять запросы на веб-серверы, запрашивать с них данные, а затем анализировать их и извлекать необходимую информацию.
Работая инженером-программистом, я обнаружила, что веб-скрапинг — одна из немногих областей программирования, восхищающая как разработчиков, так и обычных людей. Умение легко написать простой бот, который бы собирал данные и передавал их через терминал или сохранял в базе данных, не перестает повергать в некий трепет от осознания своих возможностей, независимо от того, сколько раз вам приходилось делать это раньше.
К сожалению, общаясь с другими программистами на тему веб-скрапинга, я обнаружила, что не все хорошо понимают суть метода. Одни считают его не вполне законным (и они ошибаются), другие не умеют обрабатывать страницы, содержащие много кода JavaScript или требующие регистрации. Многие не знают, как начать крупный проект по скрапингу или даже где искать нужные данные. Книга призвана ответить на многие из этих вопросов, развеять ошибочные представления о веб-скрапинге, а также предоставить исчерпывающее руководство по решению его наиболее распространенных задач.
Веб-скрапинг — обширная и быстро развивающаяся область, поэтому я постаралась представить здесь не только общие принципы, но и конкретные примеры, охватывающие практически все способы сбора данных, с которыми вы, вероятно, столкнетесь. В книге приводятся примеры кода, демонстрирующие эти принципы и позволяющие проверить их на практике. Сами примеры можно использовать и изменять как с указанием авторства, так и без него (хотя благодарности всегда приветствуются). Все примеры кода доступны на GitHub (http://www.pythonscraping.com/code/), где их можно просмотреть и скачать.
Что такое веб-скрапинг
Автоматизированный сбор данных в Интернете почти так же стар, как и сам Интернет. Несмотря на то что термин «веб-скрапинг» не является новым, еще несколько лет назад эту методику чаще называли анализом интерфейсных данных, интеллектуальным анализом данных, сбором веб-данных и т.п. Похоже, что наконец-то все пришли к единому мнению и предпочли называть это веб-скрапингом, поэтому я буду использовать данный термин на протяжении всей книги, хотя специализированные программы, которые просматривают несколько веб-страниц, я буду называть веб-краулерами, а программы, предназначенные для собственно веб-скрапинга, — ботами.
Теоретически веб-скрапинг — это сбор данных с использованием любых средств, за исключением программ, взаимодействующих с API. Обычно для этого пишут автоматизированную программу, которая обращается к веб-серверу, запрашивает данные (как правило, в формате HTML или в других форматах веб-страниц), а затем анализирует эти данные и извлекает оттуда полезную информацию.
На практике веб-скрапинг включает в себя широкий спектр методов и технологий программирования, таких как анализ данных, синтаксический анализ естественных языков и информационная безопасность. Именно потому, что эта область столь широка, в части I данной книги будут рассмотрены фундаментальные основы веб-скрапинга и веб-краулинга, а в части II — более углубленные темы. Я рекомендую внимательно изучить первую часть и погружаться в более специализированные разделы второй части по мере необходимости.
Почему это называется веб-скрапингом
Получать доступ к Интернету только через браузер — значит упускать массу возможностей. Браузеры (кроме прочего) удобны для выполнения скриптов JavaScript, вывода изображений и представления объектов в понятной для человека форме, однако веб-скраперы гораздо лучше справляются с быстрым сбором и обработкой больших объемов данных. Вместо того чтобы просматривать страницу за страницей на экране монитора, можно читать сразу целые базы данных, в которых хранятся тысячи и даже миллионы страниц.
Кроме того, веб-скраперы позволяют заглядывать в места, недоступные обычным поисковым системам. Так, при поиске в Google «самых дешевых рейсов в Бостон» вы получите кучу ссылок на рекламные объявления и популярные сайты поиска авиарейсов. Google знает только то, что сообщается на страницах оглавлений этих сайтов, а вовсе не точные результаты различных запросов, введенных в приложение поиска рейсов. Однако правильно построенный веб-скрапер способен создать график изменения стоимости перелета в Бостон во времени на разных сайтах и определить даты, когда можно купить самый выгодный билет.
Вы спросите: «Разве API не создаются специально для сбора данных?» (О том, что такое API, см. в главе 12.) Действительно, возможности API бывают просто фантастическими, если удастся найти тот из них, который соответствует вашим целям. API предназначены для построения удобного потока хорошо отформатированных данных из одной компьютерной программы в другую. Для многих типов данных, которые вы, возможно, захотите использовать, существуют готовые API — например, для постов Twitter или страниц «Википедии». Как правило, если существует подходящий API, то предпочтительнее использовать его вместо создания бота для получения тех же данных. Однако нужного API может не оказаться, или же этот API может не соответствовать вашим целям по нескольким причинам:
• вам необходимо собирать относительно небольшие, ограниченные наборы данных с большого количества сайтов, у которых нет единого API;
• нужных данных сравнительно мало или они необычны и разработчик посчитал неоправданным создание для них специального API;
• источник не обладает инфраструктурой или техническими возможностями для создания API;
• это ценные и/или защищенные данные, не предназначенные для широкого распространения.
Даже если API действительно существует, его возможности по объему и скорости обрабатываемых запросов, а также по типам или формату предоставляемых данных могут оказаться недостаточными для ваших целей.
Именно в таких случаях в дело вступает веб-скрапинг. За редким исключением, если данные доступны в браузере, то доступны и через скрипт Python. Данные, доступные в скрипте, можно сохранить в базе данных. А с сохраненными данными можно делать практически все что угодно.
Разумеется, у доступа к почти любым данным есть множество чрезвычайно полезных вариантов применения: системы прогнозирования рынка, машинного перевода и даже медицинской диагностики получили огромное распространение благодаря возможности извлекать и анализировать данные с новостных сайтов, из переведенных текстов и с форумов по вопросам здоровья соответственно.
Даже в мире искусства веб-скрапинг расширяет возможности для творчества. В 2006 году проект We Feel Fine («Мы прекрасно себя чувствуем») (http://wefeelfine.org/) Джонатана Харриса (Jonathan Harris) и Сэпа Камвара (Sep Kamvar) собрал из нескольких англоязычных блогов фразы, начинающиеся со слов I feel или I am feeling («я чувствую, я ощущаю»). В итоге получилась визуализация большого количества данных, описывающих то, что чувствовал мир день за днем, минуту за минутой.
Независимо от того, чем вы занимаетесь, веб-скрапинг почти всегда дает возможность сделать это более эффективно, повысить продуктивность или даже перейти на совершенно новый уровень.
Об этой книге
Данная книга — не только начальное пособие по веб-скрапингу, но и всеобъемлющее руководство по сбору, преобразованию и использованию данных из несовместимых источников. Однако, несмотря на то что здесь применяется язык программирования Python и изложены многие его основы, книгу не следует использовать для знакомства с этим языком.
Если вы вообще не знаете Python, то вам может быть сложно читать данную книгу. Пожалуйста, не используйте ее в качестве учебника по основам Python. Учитывая эту проблему, я постаралась представить все концепции и примеры кода с ориентиром на начальный и средний уровень программирования на Python, чтобы они были понятны широкому кругу читателей. Поэтому иногда здесь приводятся пояснения более сложных аспектов программирования на Python и общих вопросов информатики.
Если вы ищете более подробный учебник по Python, то рекомендую Introducing Python Билла Любановича (Bill Lubanovic)1 — это хорошее, хоть и довольно объемное руководство. Тем, у кого не хватит на него времени, советую посмотреть видеоуроки Introduction to Python Джессики Маккеллар (Jessica McKellar) (издательство O’Reilly) (http://oreil.ly/2HOqSNM) — это отличный ресурс. Мне также понравилась книга Think Python моего бывшего профессора Аллена Дауни (Allen Downey) (издательство O’Reilly) (http://oreil.ly/2fjbT2F). Она особенно хороша для новичков в программировании. Это учебник не только по языку Python, но и по информатике вообще, а также по общим концепциям разработки ПО.
Технические книги часто посвящены какому-то одному языку или технологии. Однако веб-скрапинг — весьма разносторонняя тема, в которой задействованы базы данных, веб-серверы, HTTP, HTML, интернет-безопасность, обработка изображений, анализ данных и другие инструменты. В данной книге я постараюсь охватить все эти и другие темы с точки зрения сбора данных. Это не значит, что здесь они будут раскрыты полностью, однако я намерена раскрыть их достаточно подробно, чтобы вы начали писать веб-скраперы!
В части I подробно рассматриваются веб-скрапинг и веб-краулинг. Особое внимание уделяется нескольким полезным библиотекам. Часть I вполне может служить подробным справочником по этим библиотекам и методикам (за некоторыми исключениями; по ним будут предоставлены дополнительные ссылки). Приемы, описанные в первой части книги, полезны всем, кто пишет веб-скраперы независимо от их конкретной цели и области приложения.
В части II раскрыты дополнительные темы, также полезные при написании веб-скраперов, но не всегда и не любых. К сожалению, данные темы слишком широки и их нельзя уместить в одной главе. Поэтому я буду часто ссылаться на другие ресурсы, где вы найдете дополнительную информацию.
Структура этой книги позволяет легко переходить от одной главы к другой, чтобы найти описание только веб-скрапинга или другую нужную вам информацию. Если концепция или фрагмент кода основывается на чем-то, о чем говорилось в предыдущей главе, то я явно ссылаюсь на раздел, в котором это было рассмотрено.
Условные обозначения
В этой книге используются следующие условные обозначения.
Курсив
Курсивом выделены новые термины и важные слова.
Моноширинный шрифт
Используется для листингов программ, а также внутри абзацев, чтобы обратиться к элементам программы вроде переменных, функций, баз данных, типов данных, переменных среды, инструкций и ключевых слов, имен и расширений файлов.
Моноширинный жирный шрифт
Показывает команды или другой текст, который пользователь должен ввести самостоятельно.
Моноширинный курсивный шрифт
Показывает текст, который должен быть заменен значениями, введенными пользователем, или значениями, определяемыми контекстом.
Шрифт без засечек
Используется для обозначения URL, адресов электронной почты, названий кнопок, каталогов.
Этот рисунок указывает на совет или предложение.
Такой рисунок указывает на общее замечание.
Этот рисунок указывает на предупреждение.
Использование примеров кода
Дополнительный материал (примеры кода, упражнения и т.д.) можно скачать по адресу https://github.com/REMitchell/python-scraping.
Эта книга призвана помочь вам выполнять свою работу. Если какой-нибудь из приведенных примеров будет полезен для вас, то вы можете использовать его в своих программах и документации. Вам не нужно обращаться к нам за разрешением, если только вы не воспроизводите значительную часть кода. Так, для написания программы, в которой задействованы несколько фрагментов кода из данной книги, не требуется разрешения. А вот для продажи или распространения компакт-дисков с примерами из книг O’Reilly — требуется. Для ответа на вопрос с помощью этой книги и примера кода разрешение не нужно. Однако на включение значительного количества примеров кода из книги в документацию вашего продукта требуется разрешение.
Мы ценим ссылки на эту книгу, но не требуем их. Как правило, такая ссылка включает в себя название, автора, издателя и ISBN. Например: «Митчелл Райан. Современный скрапинг веб-сайтов с помощью Python. — СПб.: Питер, 2021. — 978-5-4461-1693-5».
Если вы считаете, что использование вами примеров кода выходит за рамки правомерного применения или предоставленных выше разрешений, то обратитесь к нам по адресу permissions@oreilly.com.
К сожалению, бумажные книги трудно поддерживать в актуальном состоянии. В случае веб-скрапинга это создает дополнительную проблему, так как многие библиотеки и сайты, на которые ссылается данная книга и от которых часто зависит код, изменяются, из-за чего примеры кода могут перестать работать или приводить к неожиданным результатам. Если вы захотите выполнить примеры кода, то не копируйте их непосредственно из книги, а скачайте из репозитория GitHub. Мы — и я, и читатели этой книги, которые решили внести свой вклад и поделиться своими примерами (включая, возможно, вас!), — постараемся поддерживать хранилище в актуальном состоянии, вовремя внося необходимые изменения и примечания.
Помимо примеров кода, в этой книге часто приводятся команды терминала, демонстрирующие установку и запуск программного обеспечения. Как правило, эти команды предназначены для операционных систем на основе Linux, но большинство из них применимы и в Windows с правильно настроенной средой Python и установленным pip. В отношении тех случаев, когда это не так, я предоставила инструкции для всех основных операционных систем или внешние ссылки для пользователей Windows, чтобы облегчить задачу.
Благодарности
Лучшие продукты часто появляются благодаря многочисленным отзывам пользователей. Эта книга тоже никогда не появилась бы в сколько-нибудь полезном виде без помощи многих соавторов, руководителей и редакторов. Спасибо сотрудникам O’Reilly за их удивительную поддержку этого необычного начинания; моим друзьям и семье, которые давали советы и мирились с импровизированными чтениями; а также моим коллегам из HedgeServ, которым я, вероятно, теперь должна много часов работы.
В частности, спасибо Элисон Макдональд (Allyson MacDonald), Брайану Андерсону (Brian Anderson), Мигелю Гринбергу (Miguel Grinberg) и Эрику Ванвику (Eric VanWyk) за отзывы и советы — иногда резкие, но справедливые. Довольно много разделов и примеров кода появились в книге именно в результате их вдохновляющих предложений.
Благодарю Йела Шпехта (Yale Specht) за его безграничное терпение в течение четырех лет и двух изданий, за его поддержку, которая помогала мне продолжать этот проект, и обратную связь по стилистике в процессе написания. Без него я написала бы книгу вдвое быстрее, но она вряд ли была бы настолько полезной.
Наконец, спасибо Джиму Уолдо (Jim Waldo) — именно с него все это началось много лет назад, когда он прислал впечатлительной девочке-подростку диск с Linux и книгу The Art and Science of C.
От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.
1 Любанович Б. Простой Python. Современный стиль программирования. 2-е изд. — СПб.: Питер, 2021.
Любанович Б. Простой Python. Современный стиль программирования. 2-е изд. — СПб.: Питер, 2021.
Если вы ищете более подробный учебник по Python, то рекомендую Introducing Python Билла Любановича (Bill Lubanovic)1 — это хорошее, хоть и довольно объемное руководство. Тем, у кого не хватит на него времени, советую посмотреть видеоуроки Introduction to Python Джессики Маккеллар (Jessica McKellar) (издательство O’Reilly) (http://oreil.ly/2HOqSNM) — это отличный ресурс. Мне также понравилась книга Think Python моего бывшего профессора Аллена Дауни (Allen Downey) (издательство O’Reilly) (http://oreil.ly/2fjbT2F). Она особенно хороша для новичков в программировании. Это учебник не только по языку Python, но и по информатике вообще, а также по общим концепциям разработки ПО.
Часть I. Разработка веб-скраперов
В первой части этой книги основное внимание будет уделено базовым механизмам веб-скрапинга: как на Python построить запрос к веб-серверу, выполнить базовую обработку полученного ответа и начать взаимодействие с сайтом в автоматическом режиме. В итоге вы сможете легко путешествовать по Интернету, создавая скраперы, способные переходить от одного домена к другому, собирать и сохранять информацию для последующего использования.
Честно говоря, веб-скрапинг — фантастическая отрасль: вложенные в нее относительно небольшие начальные инвестиции окупятся сторицей. Примерно 90 % проектов веб-скрапинга, которые вам встретятся, будут опираться на методы, описанные в следующих шести главах. Эта часть покажет, как обычные (хотя и технически подкованные) люди представляют себе работу веб-скраперов:
• извлечение HTML-данных из имени домена;
• анализ этих данных для получения требуемой информации;
• сохранение этой информации;
• возможен переход на другую страницу, чтобы повторить процедуру.
Прочитав эту часть, вы получите прочную основу, которая позволит вам перейти к более сложным проектам, описанным в части II. Не думайте, что первая половина книги менее важна, чем вторая, и более сложные проекты описаны во второй половине. При написании веб-скраперов вам придется каждый день использовать почти всю информацию, изложенную в первой половине данной книги!
Глава 1. Ваш первый веб-скрапер
С первых шагов веб-скрапинга вы сразу начнете ценить все те мелкие услуги, которые нам оказывают браузеры. Без HTML-форматирования, стилей CSS, скриптов JavaScript и рендеринга изображений Интернет с непривычки может показаться слегка пугающим. Но в этой и следующей главах мы посмотрим, как форматировать и интерпретировать данные, не прибегая к помощи браузера.
В этой главе мы начнем с основ отправки на веб-сервер GET-запросов — запросов на выборку или получение содержимого заданной веб-страницы, чтения полученного с нее HTML-кода и выполнения ряда простых операций по извлечению данных, чтобы выделить оттуда контент, который вы ищете.
Установка соединения
Если вам прежде не приходилось много работать с сетями или заниматься вопросами сетевой безопасности, то механика работы Интернета может показаться несколько загадочной. Вы же не хотите каждый раз задумываться о том, что именно делает сеть, когда вы открываете браузер и заходите на сайт http://google.com. Впрочем, в наше время это и не нужно. Компьютерные интерфейсы стали настолько совершенными, что большинство людей, пользующихся Интернетом, не имеют ни малейшего представления о том, как работает Сеть, и это, по-моему, здорово.
Однако веб-скрапинг требует сбросить покров с этого интерфейса — на уровне не только браузера (то, как он интерпретирует весь HTML-, CSS- и JavaScript-код), но иногда и сетевого подключения.
Чтобы дать вам представление об инфраструктуре, необходимой для получения информации в браузере, рассмотрим следующий пример. У Алисы есть веб-сервер, а у Боба — ПК, с которого он хочет подключиться к серверу Алисы. Когда одна машина собирается пообщаться с другой, происходит примерно следующий обмен данными.
1. Компьютер Боба посылает поток битов — единиц и нулей, которым соответствует высокое и низкое напряжение в кабеле. Из этих битов складывается некая информация, делимая на заголовок и тело. В заголовке содержится MAC-адрес ближайшего локального маршрутизатора и IP-адрес конечной точки — сервера Алисы. В тело включен запрос Боба к серверному приложению Алисы.
2. Локальный маршрутизатор Боба получает все эти нули и единицы и интерпретирует их как пакет, который передается с MAC-адреса Боба на IP-адрес Алисы. Маршрутизатор Боба ставит свой IP-адрес на пакете в графе «отправитель» и отправляет пакет через Интернет.
3. Пакет Боба проходит через несколько промежуточных серверов, которые по соответствующим кабелям передают пакет на сервер Алисы.
4. Сервер Алисы получает пакет по своему IP-адресу.
5. Сервер Алисы считывает из заголовка пакета номер порта-приемника и передает пакет соответствующему приложению веб-сервера. (Портом-приемником пакета в случае веб-приложений почти всегда является порт номер 80; это словно номер квартиры в адресе для пакетных данных, тогда как IP-адрес аналогичен названию улицы и номеру дома.)
6. Серверное приложение получает поток данных от серверного процессора. Эти данные содержат примерно такую информацию:
• вид запроса: GET;
• имя запрошенного файла: index.html.
7. Веб-сервер находит соответствующий HTML-файл, помещает его в новый пакет, созданный для Боба, и отправляет этот пакет на локальный маршрутизатор, откуда он уже знакомым нам способом передается на компьютер Боба.
Вуаля! Так работает Интернет.
В чем же при этом обмене данными участвует браузер? Ответ: ни в чем. В действительности браузеры — сравнительно недавнее изобретение в истории Интернета: Nexus появился всего в 1990 году.
Конечно, браузер — полезное приложение, которое создает эти информационные пакеты, сообщает операционной системе об их отправке и интерпретирует данные, превращая их в красивые картинки, звуки, видео и текст. Однако браузер — это всего лишь код, который можно разбить на части, выделить основные компоненты, переписать, использовать повторно и приспособить для выполнения чего угодно. Браузер может дать команду процессору отправлять данные в приложение, поддерживающее беспроводной (или проводной) сетевой интерфейс, но то же самое можно сделать и на Python с помощью всего трех строк кода:
from urllib.request import urlopen
html = urlopen('http://pythonscraping.com/pages/page1.html')
print(html.read())
Для выполнения этого кода можно использовать оболочку iPython, которая размещена в репозитории GitHub для главы 1 (https://github.com/REMitchell/python-scraping/blob/master/Chapter01_BeginningToScrape.ipynb), или же сохранить код на компьютере в файле scrapetest.py и запустить его в окне терминала с помощью следующей команды:
$ python scrapetest.py
Обратите внимание: если на вашем компьютере, кроме Python 3.x, также установлен Python 2.x, и вы используете обе версии Python параллельно, то может потребоваться явно вызвать Python 3.x, выполнив команду следующим образом:
$ python3 scrapetest.py
По этой команде выводится полный HTML-код страницы page1, расположенной по адресу http://pythonscraping.com/pages/page1.html. Точнее, выводится HTML-файл page1.html, размещенный в каталоге <корневой веб-каталог>/pages на сервере с доменным именем http://pythonscraping.com.
Почему так важно представлять себе эти адреса как «файлы», а не как «страницы»? Большинство современных веб-страниц связано с множеством файлов ресурсов. Ими могут быть файлы изображений, скриптов JavaScript, стилей CSS и любой другой контент, на который ссылается запрашиваемая страница. Например, встретив тег <imgsrc="cuteKitten.jpg">, браузер знает: чтобы сгенерировать страницу для пользователя, нужно сделать еще один запрос к серверу и получить данные из файла cuteKitten.jpg.
Разумеется, у нашего скрипта на Python нет логики, позволяющей вернуться и запросить несколько файлов (пока что); он читает только тот HTML-файл, который мы запросили напрямую:
from urllib.request import urlopen
Эта строка делает именно то, что кажется на первый взгляд: находит модуль Python для запросов (в библиотеке urllib) и импортирует оттуда одну функцию — urlopen.
Библиотека urllib — это стандартная библиотека Python (другими словами, для запуска данного примера ничего не нужно устанавливать дополнительно), в которой содержатся функции для запроса данных через Интернет, обработки файлов cookie и даже изменения метаданных, таких как заголовки и пользовательский программный агент. Мы будем активно применять urllib в данной книге, так что я рекомендую вам прочитать раздел документации Python, касающийся этой библиотеки (https://docs.python.org/3/library/urllib.html).
Функция urlopen открывает удаленный объект по сети и читает его. Поскольку это практически универсальная функция (она одинаково легко читает HTML-файлы, файлы изображений и другие файловые потоки), мы будем довольно часто использовать ее в данной книге.
Знакомство с BeautifulSoup
Прекрасный суп в столовой ждет.
Из миски жирный пар идет.
Не любит супа тот, кто глуп!
Прекрасный суп, вечерний суп!
Прекрасный суп, вечерний суп!
Алиса в Стране чудес. Издание 1958 г., пер. А. Оленича-Гнененко
Библиотека BeautifulSoup («Прекрасный суп») названа так в честь одноименного стихотворения Льюиса Кэрролла из книги «Алиса в Стране чудес». В книге это стихотворение поет Фальшивая Черепаха (пародия на популярное викторианское блюдо — фальшивый черепаховый суп, который варят не из черепахи, а из говядины).
Приложение BeautifulSoup стремится найти смысл в бессмысленном: помогает отформатировать и упорядочить «грязные» сетевые данные, исправляя ошибки в HTML-коде и создавая легко обходимые (traversable) объекты Python, являющиеся представлениями структур XML.
Установка BeautifulSoup
Поскольку BeautifulSoup не является стандартной библиотекой Python, ее необходимо установить. Если у вас уже есть опыт установки библиотек Python, то используйте любимый установщик и пропустите этот подраздел, сразу перейдя к следующему — «Работа с BeautifulSoup» на с. 29.
Для тех же, кому еще не приходилось устанавливать библиотеки Python (или кто подзабыл, как это делается), представлен общий подход, который мы будем использовать для установки библиотек по всей книге, так что впоследствии вы можете обращаться к данному подразделу.
В этой книге мы будем использовать библиотеку BeautifulSoup 4 (также известную как BS4). Подробная инструкция по установке BeautifulSoup 4 размещена на сайте Crummy.com (http://www.crummy.com/software/BeautifulSoup/bs4/doc/); здесь же описан самый простой способ для Linux:
$ sudo apt-get install python-bs4
И для Mac:
$ sudo easy_install pip
Эта команда устанавливает менеджер пакетов Python pip.
Затем выполните следующую команду для установки библиотеки:
$ pip install beautifulsoup4
Еще раз подчеркну: если на вашем компьютере установлены версии Python 2.x и 3.x, то лучше явно вызвать python3:
$ python3 myScript.py
То же самое касается установки пакетов, иначе пакеты могут установиться не для Python 3.x, а в Python 2.x:
$ sudo python3 setup.py install
При использовании pip с целью установки пакета для Python 3.x также можно вызвать pip3:
$ pip3 install beautifulsoup4
Установка пакетов для Windows практически не отличается от данного процесса для Mac и Linux. Скачайте последнюю версию BeautifulSoup 4 со страницы скачивания библиотеки (http://www.crummy.com/software/BeautifulSoup/#Download), перейдите в каталог, в котором вы ее распаковали, и выполните следующую команду:
> python setup.py install
Готово! Теперь компьютер будет распознавать BeautifulSoup как библиотеку Python. Чтобы в этом убедиться, откройте терминал Python и импортируйте ее:
$ python
> from bs4 import BeautifulSoup
Импорт должен выполниться без ошибок.
Кроме того, для Windows есть программа-установщик менеджера пакетов pip (https://pypi.python.org/pypi/setuptools), с помощью которой можно легко устанавливать пакеты и управлять ими:
> pip install beautifulsoup4
| Хранение библиотек непосредственно в виртуальных окружениях Если вы собираетесь работать параллельно с несколькими проектами Python, или вам нужен удобный способ связать проекты с их библиотеками, или вы хотите исключить потенциальные конфликты между установленными библиотеками, то можете установить виртуальное окружение Python, где все разделено и легко управляется. Если библиотека Python устанавливается без виртуального окружения, то она устанавливается глобально. Обычно такую установку должен выполнять администратор или пользователь root, и тогда библиотека Python станет доступна для всех пользователей и проектов на данном компьютере. К счастью, создать виртуальное окружение легко: $ virtualenv scrapingEnv Эта команда создает новое окружение с именем scrapingEnv. Чтобы использовать данное окружение, его необходимо активировать: $ cd scrapingEnv/ $ source bin/activate После активации окружения его имя появится в командной строке, подсказывая, в каком именно окружении вы работаете. Все устанавливаемые библиотеки и запускаемые скрипты будут находиться только в этом виртуальном окружении. В окружении scrapingEnv можно установить и использовать BeautifulSoup, например, так: (scrapingEnv)ryan$ pip install beautifulsoup4 (scrapingEnv)ryan$ python > from bs4 import BeautifulSoup > |
| Выйти из окружения можно с помощью команды deactivate, после чего библиотеки, которые были установлены внутри виртуального окружения, станут недоступными: (scrapingEnv)ryan$ deactivate ryan$ python > from bs4 import BeautifulSoup Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named 'bs4' Хранение всей библиотеки с разделением по проектам позволит легко заархивировать всю папку окружения и отправить ее кому-нибудь. Если на компьютере получателя установлена та же версия Python, что и у вас, то ваш код будет работать в его виртуальном окружении, не требуя дополнительной установки каких-либо библиотек. Я не буду настаивать, чтобы вы применяли виртуальное окружение при выполнении всех примеров этой книги, однако помните: вы можете воспользоваться им в любой момент, просто заранее его активировав. |
Работа с BeautifulSoup
Из всех объектов библиотеки BeautifulSoup чаще всего используется собственно, сам BeautifulSoup. Посмотрим, как он работает, изменив пример, приведенный в начале главы:
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('http://www.pythonscraping.com/pages/page1.html')
bs = BeautifulSoup(html.read(), 'html.parser')
print(bs.h1)
Результат выглядит так:
<h1>An Interesting Title</h1>
Обратите внимание: этот код возвращает только первый попавшийся ему на странице экземпляр тега h1. По существующему соглашению на странице может быть только один тег h1, однако принятые в Интернете соглашения часто нарушаются. Поэтому следует помнить, что таким образом будет получен только первый экземпляр тега и не обязательно тот, который вы ищете.
Как и в предыдущих примерах веб-скрапинга, мы импортируем функцию urlopen и вызываем html.read(), чтобы получить контент страницы в формате HTML. Помимо текстовой строки, BeautifulSoup также может принимать файловый объект, непосредственно возвращаемый функцией urlopen. Чтобы получить этот объект, не нужно вызывать функцию .read():
bs = BeautifulSoup(html, 'html.parser')
Здесь контент HTML-файла преобразуется в объект BeautifulSoup, имеющий следующую структуру:
html
— head
— title
— body
— h1
— div
Обратите внимание: тег h1, извлеченный из кода страницы, находится на втором уровне структуры объекта BeautifulSoup (html
bs.h1
На практике все следующие вызовы функций приведут к одинаковым результатам:
bs.html.body.h1
bs.body.h1
bs.html.h1
При создании объекта BeautifulSoup функции передаются два аргумента:
bs = BeautifulSoup(html.read(), 'html.parser')
Первый аргумент — это текст в формате HTML, на основе которого строится объект, а второй — синтаксический анализатор, который BeautifulSoup будет использовать для построения объекта. В большинстве случаев не имеет значения, какой именно синтаксический анализатор будет применяться.
Анализатор html.parser входит в состав Python 3 и не требует дополнительной настройки перед использованием. За редким исключением, в этой книге я буду применять именно его.
Еще один популярный анализатор — lxml (http://lxml.de/parsing.html). Он устанавливается через pip:
$ pip3 install lxml
Для того чтобы использовать lxml в BeautifulSoup, нужно изменить имя синтаксического анализатора в знакомой нам строке:
bs = BeautifulSoup(html.read(), 'lxml')
Преимущество lxml, по сравнению с html.parser, состоит в том, что lxml в целом лучше справляется с «грязным» или искаженным HTML-кодом. Анализатор lxml прощает неточности и исправляет такие проблемы, как незакрытые и неправильно вложенные теги, а также отсутствующие теги head или body. Кроме того, lxml работает несколько быстрее, чем html.parser, хотя при веб-скрапинге скорость анализатора не всегда является преимуществом, поскольку почти всегда главное узкое место — скорость самого сетевого соединения.
Недостатками анализатора lxml является то, что его необходимо специально устанавливать и он зависит от сторонних C-библиотек. Это может вызвать проблемы портируемости; кроме того, html.parser проще в использовании.
Еще один популярный синтаксический анализатор HTML называется html5lib. Подобно lxml, он чрезвычайно лоялен к ошибкам и прилагает еще больше усилий к исправлению некорректного HTML-кода. Он также имеет внешние зависимости и работает медленнее, чем lxml и html.parser. Тем не менее выбор html5lib может быть оправданным при работе с «грязными» или написанными вручную HTML-страницами.
Чтобы использовать этот анализатор, нужно установить его и передать объекту BeautifulSoup строку html5lib:
bs = BeautifulSoup(html.read(), 'html5lib')
Надеюсь, благодаря этой краткой дегустации BeautifulSoup вы составили представление о возможностях и удобстве данной библиотеки. В сущности, она позволяет извлечь любую информацию из любого файла в формате HTML (или XML), если содержимое данного файла заключено в идентифицирующий тег или этот тег хотя бы присутствует в принципе. В главе 2 мы подробно рассмотрим более сложные вызовы функций библиотеки, а также регулярные выражения и способы их использования с помощью BeautifulSoup для извлечения информации с сайтов.
Надежное соединение и обработка исключений
Сеть — «грязное» место. Данные плохо отформатированы, сайты то и дело «падают», а разработчики страниц постоянно забывают ставить закрывающие теги. Один из самых неприятных моментов, связанных с веб-скрапингом, — уйти спать и оставить работающий скрапер, рассчитывая назавтра иметь все данные в вашей базе, а утром обнаружить, что скрапер столкнулся с ошибкой в каком-то непредсказуемом формате данных и почти сразу прекратил работу, стоило вам отвернуться от экрана. В подобных случаях возникает соблазн проклясть разработчика, который создал тот сайт (и выбрал странный формат представления данных). Однако на самом деле если кому и стоит дать пинка, то это вам самим, поскольку именно вы не предусмотрели исключение!
Рассмотрим первую строку нашего скрапера, сразу после операторов импорта, и подумаем, как можно обрабатывать любые исключения, которые могли бы здесь возникнуть:
html = urlopen('http://www.pythonscraping.com/pages/page1.html')
Здесь могут случиться две основные неприятности:
• на сервере нет такой страницы (или при ее получении произошла ошибка);
• нет такого сервера.
В первой ситуации будет возвращена ошибка HTTP. Это может быть 404 Page Not Found, 500 Internal Server Error и т.п. Во всех таких случаях функция urlopen выдаст обобщенное исключение HTTPError. Его можно обработать следующим образом:
from urllib.request import urlopen
from urllib.error import HTTPError
try:
html = urlopen('http://www.pythonscraping.com/pages/page1.html')
except HTTPError as e:
print(e)
# Вернуть ноль, прекратить работу или
# выполнить еще какой-нибудь "план Б".
else:
# Продолжить выполнение программы.
# Примечание: если при перехвате исключений
# программа прерывает работу или происходит возврат
# из функции, то оператор else не нужен.
Теперь в случае возвращения кода HTTP-ошибки выводится сообщение о ней и остальная часть программы, которая находится в ветви else, не выполняется.
Если не найден весь сервер (например, сервер по адресу http://www.pythonscraping.com отключен или URL указан с ошибкой), то функция urlopen возвращает URLError. Эта ошибка говорит о том, что ни один из указанных серверов не доступен. Поскольку именно удаленный сервер отвечает за возвращение кодов состояния HTTP, ошибка HTTPError не может быть выдана и вместо нее следует обрабатывать более серьезную ошибку URLError. Для этого можно добавить в программу такую проверку:
from urllib.request import urlopen
from urllib.error import HTTPError
from urllib.error import URLError
try:
html = urlopen('https://pythonscrapingthisurldoesnotexist.com')
except HTTPError as e:
print(e)
except URLError as e:
print('The server could not be found!')
else:
print('It Worked!')
Конечно, даже если страница успешно получена с сервера, все равно остается проблема с ее контентом, который не всегда соответствует ожидаемому. Всякий раз, обращаясь к тегу в объекте BeautifulSoup, разумно добавить проверку того, существует ли этот тег. При попытке доступа к несуществующему тегу BeautifulSoup возвращает объект None. Проблема в том, что попытка обратиться к тегу самого объекта None приводит к возникновению ошибки AttributeError.
Следующая строка (в которой nonExistentTag — несуществующий тег, а не имя реальной функции BeautifulSoup) возвращает объект None:
print(bs.nonExistentTag)
Этот объект вполне доступен для обработки и проверки. Проблема возникает в том случае, если продолжать его использовать без проверки и попытаться вызвать для объекта None другую функцию:
print(bs.nonExistentTag.someTag)
Эта функция вернет исключение:
AttributeError: 'NoneType' object has no attribute 'someTag'
Как же застраховаться от этих ситуаций? Проще всего — явно проверить обе ситуации:
try:
badContent = bs.nonExistingTag.anotherTag
except AttributeError as e:
print('Tag was not found')
else:
if badContent == None:
print ('Tag was not found')
else:
print(badContent)
Такие проверка и обработка каждой ошибки поначалу могут показаться трудоемкими, однако если немного упорядочить код, то его станет проще писать (и, что еще важнее, гораздо проще читать). Вот, например, все тот же наш скрапер, написанный немного по-другому:
from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
def getTitle(url):
try:
html = urlopen(url)
except HTTPError as e:
return None
try:
bs = BeautifulSoup(html.read(), 'html.parser')
title = bs.body.h1
except AttributeError as e:
return None
return title
title = getTitle('http://www.pythonscraping.com/pages/page1.html')
if title == None:
print('Title could not be found')
else:
print(title)
В этом примере мы создаем функцию getTitle, которая возвращает либо заголовок страницы, либо, если получить его не удалось, — объект None. Внутри getTitle мы, как в предыдущем примере, проверяем наличие HTTPError и инкапсулируем две строки BeautifulSoup внутри оператора try. Ошибка AttributeError может возникнуть в любой из этих строк (если сервер не найден, то html вернет объект None, а html.read() выдаст AttributeError). Фактически внутри оператора try можно разместить любое количество строк или вообще вызвать другую функцию, которая будет генерировать AttributeError в любой момент.
При написании скраперов важно продумать общий шаблон кода, который бы обрабатывал исключения, но при этом был бы читабельным. Вы также, вероятно, захотите использовать код многократно. Наличие обобщенных функций, таких как getSiteHTML и getTitle (в сочетании с тщательной обработкой исключений), позволяет быстро — и надежно — собирать данные с веб-страниц в Сети.
Глава 2. Углубленный синтаксический анализ HTML-кода
Однажды Микеланджело спросили, как ему удалось создать такой шедевр, как «Давид». Известен его ответ: «Это легко. Вы просто срезаете ту часть камня, которая не похожа на Давида».
Большинство веб-скраперов мало напоминают мраморные статуи, однако при извлечении информации из сложных веб-страниц стоит придерживаться аналогичного подхода. Существует множество способов отбрасывать контент, не похожий на тот, что вы ищете, до тех пор, пока не доберетесь до нужной информации. В этой главе вы узнаете, как выполнять анализ сложных HTML-страниц, чтобы извлекать из них только необходимую вам информацию.
Иногда молоток не требуется
Столкнувшись с гордиевыми узлами тегов, многие испытывают острое желание углубиться в них с помощью многострочных операторов в расчете извлечь ценную информацию. Однако следует помнить: безрассудное наслаивание методов, описанных в этом разделе, может привести к тому, что код будет трудно отлаживать, или он станет постоянно сбоить, или же и то и другое. Прежде чем начать, рассмотрим несколько способов, которые позволяют вообще обойтись без углубленного синтаксического анализа HTML-кода!
Предположим, мы ищем некий контент: имя, статистические данные или блок текста. Возможно, этот контент похоронен под 20 тегами в каше из HTML-кода и не отличается никакими особыми HTML-тегами или атрибутами. Допустим, мы решили отбросить осторожность и написать примерно следующее в попытке извлечь нужные данные:
bs.find_all('table')[4].find_all('tr')[2].find('td').find_all('div')[1].find('a')
Выглядит не слишком красиво. Но дело не только в эстетике: стоит администратору сайта внести малейшее изменение, и весь наш веб-скрапер сломается. А если разработчик сайта решит добавить еще одну таблицу или еще один столбец данных? Или разместит в верхней части страницы еще один компонент (с несколькими тегами div)? Показанная выше строка кода нестабильна: она опирается на то, что структура сайта никогда не изменится.
Что же делать?
• Найдите ссылку Print This Page (Распечатать эту страницу) или, возможно, мобильную версию сайта с более удачным HTML-форматированием (подробнее о том, как выдать себя за мобильное устройство и получить мобильную версию сайта, см. в главе 14).
• Поищите информацию в файле JavaScript. Учтите, что для этого вам может понадобиться исследовать импортированные файлы JavaScript. Например, однажды я получила с сайта адреса улиц (вместе с широтой и долготой) в виде аккуратно отформатированного массива, заглянув в JavaScript-код встроенной карты Google, на которой были точно отмечены все адреса.
• Это больше касается заголовков, однако информация может находиться в URL самой страницы.
• Если информация, которую вы ищете, по какой-либо причине является уникальной для данного сайта, — вам не повезло. В противном случае попробуйте найти другие источники, из которых можно было бы получить эту информацию. Нет ли другого сайта с теми же данными? Возможно, на нем приводятся данные, скопированные или агрегированные с другого сайта?
Когда речь идет о скрытых или плохо отформатированных данных, особенно важно не зарываться в код, не загонять себя в кроличью нору, из которой потом можно и не выбраться. Лучше сделайте глубокий вдох и подумайте: нет ли других вариантов?
На тот случай, если вы уверены, что альтернатив не существует, в остальной части этой главы описываются стандартные и нестандартные способы выбора тегов по их расположению, контексту, атрибутам и содержимому. Представленные здесь методы, при условии правильного использования, способны значительно облегчить написание более стабильных и надежных веб-краулеров.
Еще одна тарелка BeautifulSoup
В главе 1 мы кратко рассмотрели установку и запуск BeautifulSoup, а также выбор объектов по одному. В этом разделе обсудим поиск тегов по атрибутам, работу со списками тегов и навигацию по дереву синтаксического анализа.
Почти любой сайт, с которым вам придется иметь дело, содержит таблицы стилей. На первый взгляд может показаться, что стили на сайтах предназначены исключительно для показа сайта пользователю в браузере. Однако это неверно: появление CSS стало настоящим благом для веб-скраперов. Чтобы назначать элементам разные стили, CSS опирается на дифференциацию HTML-элементов, которые в противном случае имели бы одинаковую разметку. Например, одни теги могут выглядеть так:
<span class="green"></span>
А другие — так:
<span class="red"></span>
Веб-скраперы легко различают эти два тега по их классам; например, с помощью BeautifulSoup веб-скрапер может собрать весь красный текст, игнорируя зеленый. Поскольку CSS использует эти идентифицирующие атрибуты для соответствующего оформления сайтов, мы можем быть практически уверены в том, что на большинстве современных сайтов будет много атрибутов class и id.
Создадим пример веб-скрапера, который сканирует страницу, расположенную по адресу http://www.pythonscraping.com/pages/warandpeace.html.
На этой странице строки, в которых содержатся реплики персонажей, выделены красным цветом, а имена персонажей — зеленым. В следующем примере исходного кода страницы показаны теги span, которым присвоены соответствующие классы CSS:
<span class="red">Heavens! what a virulent attack!</span> replied
<span class="green">the prince</span>, not in the least disconcerted
by this reception.
С помощью программы, аналогичной той, которая была рассмотрена в главе 1, можно прочитать всю страницу и создать на ее основе объект BeautifulSoup:
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('http://www.pythonscraping.com/pages/page1.html')
bs = BeautifulSoup(html.read(), 'html.parser')
С помощью этого объекта BeautifulSoup можно вызвать функцию find_all и извлечь Python-список всех имен персонажей, полученных путем выбора текста из тегов <spanclass="green"></span> (find_all — очень гибкая функция, которую мы будем широко использовать в этой книге):
nameList = bs.find_all('span', {'class':'green'})
for name in nameList:
print(name.get_text())
Результатом выполнения этого кода должен стать список всех персонажей «Войны и мира» в порядке их появления в тексте. Так что же здесь происходит? Раньше мы вызывали функцию bs.имяТега и получали первое появление тега на странице. Теперь мы вызываем функцию bs.find_all(имяТега,атрибутыТега), чтобы получить не только первый тег, а список всех тегов, присутствующих на странице.
Получив список персонажей, программа перебирает все имена в списке и использует функцию name.get_text(), чтобы очистить контент от тегов.
Когда использовать get_text(), а когда — сохранять теги
Функция .get_text() удаляет из документа, с которым вы работаете, все теги и возвращает строку, содержащую только текст в кодировке Unicode. Например, при работе с большим блоком текста, содержащим много гиперссылок, абзацев и других тегов, все эти теги будут удалены, останется только текст.
Учтите, что в объекте BeautifulSoup гораздо проще найти нужное, чем в текстовом фрагменте. Вызов .get_text() всегда должен быть последним, что вы делаете непосредственно перед выводом результата на экран, сохранением или манипулированием готовыми данными.
Как правило, следует как можно дольше сохранять структуру тегов документа.
Функции find() и find_all()
Функции BeautifulSoup find() и find_all() вы, скорее всего, будете использовать чаще других. С помощью этих функций можно легко фильтровать HTML-страницы, чтобы выделить списки нужных тегов или найти отдельный тег по всевозможным атрибутам.
Эти две функции очень похожи, о чем свидетельствуют их определения в документации BeautifulSoup:
find_all(tag, attributes, recursive, text, limit, keywords)
find(tag, attributes, recursive, text, keywords)
Скорее всего, в 95 % случаев вы будете использовать только первые два аргумента: tag и attributes. Однако мы подробно рассмотрим все аргументы.
Аргумент tag нам уже встречался; мы можем передать функции строку, содержащую имя тега, или даже Python-список имен тегов. Например, следующий код возвращает список всех тегов заголовков, встречающихся в документе2:
.find_all(['h1','h2','h3','h4','h5','h6'])
Аргумент attribute принимает Python-словарь атрибутов и ищет теги, которые содержат любой из этих атрибутов. Например, следующая функция ищет в HTML-документе теги span с классом greenилиred:
.find_all('span', {'class':{'green', 'red'}})
Аргумент recursive логический. Насколько глубоко вы хотите исследовать документ? Если recursive присвоено значение True, то функция find_all ищет теги, соответствующие заданным параметрам, в дочерних элементах и их потомках. Если же значение этого аргумента равно False, то функция будет просматривать только теги верхнего уровня документа. По умолчанию find_all работает рекурсивно (recurive имеет значение True); обычно лучше оставить все как есть, за исключением ситуаций, когда вы точно знаете, что делаете, и нужно обеспечить высокую производительность.
Аргумент text необычен из-за отношения не к свойствам тегов, а к их текстовому контенту. Так, чтобы узнать, сколько раз на странице встречается слово the prince, заключенное в теги, можно заменить функцию .find_all() из предыдущего примера на следующие строки:
nameList = bs.find_all(text='the prince')
print(len(nameList))
Результатом будет число 7.
Аргумент limit по понятным причинам используется только в методе find_all; функция
