Текст лекции: Будылин Р.Я., Щуров И.В., НИУ ВШЭ
Данный notebook является конспектом лекции по курсу «Программирование на языке Python для сбора и анализа данных» (НИУ ВШЭ, 2015-16). Он распространяется на условиях лицензии Creative Commons Attribution-Share Alike 4.0. При использовании обязательно упоминание автора курса и аффилиации. При наличии технической возможности необходимо также указать активную гиперссылку на страницу курса. Фрагменты кода, включенные в этот notebook, публикуются как общественное достояние.
Другие материалы курса, включая конспекты и видеозаписи лекций, а также наборы задач, можно найти на странице курса.
В прошлый раз мы обсуждали работу с файлами, но я не успел рассказать про важную конструкцию with
, которая часто используется для того, чтобы автоматически закрывать открытые файлы. Рассмотрим применение этой конструкции на примере.
Для начала создадим файл, который будем открывать. Пусть это будет test_123.py
. Если у вас в папке с ноутбуком лежит файл с таким названием, который очень ценен для вас, то замените значение переменной filename
на что-то другое.
filename = 'test_123.py'
f = open(filename, 'w')
f.write("print('Hello, world!!!')")
f.close()
Стандартная проблема с файлами состоит в том, чтобы любой открытый файл должен быть закрыт. На предыдущей лекции я показал вам такой синтаксис:
open(filename).read()
"print('Hello, world!!!')"
Это очень краткий, но не очень хороший синтаксис, потому что закрытие файла оставляется на откуп так называемой системе сборки мусора (garbage collector), и когда файл будет закрыт, точно неизвестно.
В стандартной реализации Python, которая называется CPython, garbage collector устроен таким образом, что файл закрывается сразу после выполнения этой строчки, но другие реализации могут вести себя по-другому и в некоторых ситуациях с таким кодом могут возникнуть проблемы.
Лучше действовать вот так:
with open(filename) as f:
print(f.read())
print('Hello, world!!!')
При входе в конструкцию with
выполняется строчка, эквивалентная f = open(filename)
. Дальше выполняются строчки с отступом, а когда отступ закончится, автоматически выполнится закрытие файла. Так что эти две строчки эквивалентны таким:
f = open(filename)
print(f.read())
f.close()
print('Hello, world!!!')
Вот ещё несколько примеров:
with open(filename) as f:
print(f.read())
f.seek(0)
print(f.read())
print('Hello, world!!!') print('Hello, world!!!')
Здесь мы используем конструкцию f.seek(0)
, чтобы «перемотать» файл на начало — в этом случае повторный f.read()
опять выдаст его содержимое.
Теперь попробуем что-нибудь сделать с файлом после отступа.
with open(filename) as f:
print(f.read())
f.seek(0)
print(f.read())
print(f.read())
print('Hello, world!!!') print('Hello, world!!!')
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-7-2b57747c6b28> in <module>() 3 f.seek(0) 4 print(f.read()) ----> 5 print(f.read()) ValueError: I/O operation on closed file.
Как видим, сразу после окончания блока (выделенного, как обычно, отступом), файл оказывается закрытым.
И синтаксиса with
есть несколько плюсов по сравнению с традиционным подходом. Во-первых, вы уж точно не забудете закрыть файл, потому что можно забыть написать f.close()
, но нельзя забыть убрать отступ. Во-вторых, даже если вы не забудете f.close()
, вы можете не дойти до него, потому что произошла какая-то ошибка по дороге.
Это продвинутый материал. Вы можете смело его пропустить на текущем этапе.
В коде ниже после того, как файл был открыт, происходит деление на 0. Конструкция
try:
something
except Name_of_some_error:
do_something_else
позволяет в случае, если произошла ошибка типа Name_of_some_error не заканчивать программу со словами «Все пропало! Ошибка!», а тут же передать управление блоку do_something_else, который что-нибудь сделает. Интересно, что в блоке do_something_else в примере ниже файл оказался все еще открытым, что плохо. Это можно сравнить с ситуацией: вы поставили чайник на плиту, но тут вам срочно позвонили и вы убежали, а огонь остался непогашенным.
try:
f = open(filename)
print(f.read())
print(10/0)
print('This is never been printed')
f.close()
except ZeroDivisionError:
print("Ups, I did it again!")
f.seek(0)
print(f.read())
print('Hello, world!!!') Ups, I did it again! print('Hello, world!!!')
А здесь ситуация такая: хотя мы и убежали по срочному звонку, но умный чайник тут же сам выключился. Как видим, при попытке читать из файла в блоке except
мы получаем ошибку, и это хорошо, значит, файл закрылся, несмотря на ошибку.
try:
with open(filename) as f:
print(f.read())
print(10/0)
print('This is never been printed')
except ZeroDivisionError:
print("Ups, I did it again!")
print(f.read())
print('Hello, world!!!') Ups, I did it again!
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-9-6c966bd01232> in <module>() 3 print(f.read()) ----> 4 print(10/0) 5 print('This is never been printed') ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: ValueError Traceback (most recent call last) <ipython-input-9-6c966bd01232> in <module>() 6 except ZeroDivisionError: 7 print("Ups, I did it again!") ----> 8 print(f.read()) 9 ValueError: I/O operation on closed file.
(Конец продвинутого материала.)
Нам часто нужно сделать с файлом что-то одно — или прочитать, или записать. Иногда нам нужно модифицировать файл. Чаще всего это делается так: файл сначала считывается в память, затем в памяти модифицируется и записывается «с нуля» на то же место, что и раньше. Если речь идёт о не очень больших файлах, то этот метод нормально работает.
В то же время, иногда нам нужно не перезаписать файл с нуля, а дописать какую-то информацию в конец файла. Чаще всего это приходится делать для записи логов, в которых сохраняется какая-то информация о работе программы (например, веб-сервер таким образом протоколирует, с каких адресов к нему обращались и какие страницы запрашивали). Чтобы дописать что-то в конец файла, его нужно открыть с модификатором 'a'
(от слова append
) вот так:
with open(filename, 'a') as f:
print("\n" + "print('Some new string')", file = f)
Проверим, что старое содержимое осталось на месте
with open(filename) as f:
print(f.read())
print('Hello, world!!!') print('Some new string')
Как видим, все ок.
requests
¶Если у вас не сработает строчка ниже, то сделайте pip install requests
или conda install requests
в командной строке (например, в Anaconda Prompt).
import requests
Модуль requests
позволяет получать доступ к веб-страницам. Я не буду вдаваться в подробности протокола http
, но надо понимать, что есть два распространенных способа доступа к веб-страницам: запрос типа get и типа post (хотя на самом деле видов http-запросов гораздо больше). Запрос типа get - это когда вы передаете серверу какую-то информацию в адресной строке. Например, если вы перейдете по такому адресу: https://www.google.ru/?q=python+%D0%B0%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7+%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85+%D0%B2%D1%88%D1%8D, то этим вы просите гугл искать по запросу "python анализ данных вшэ". post-запрос - это когда вам нужно ввести информацию в какую-нибудь форму, например, ввести логин-пароль, который не будет отображать в адресной строке браузера.
Мы пока будем использовать get-запросы.
r = requests.get('http://vyshka.math.ru')
Чтобы проверить, что страница нормально загрузилась есть команда
r.ok
True
Значение True говорит о том, что все прошло нормально.
q = requests.get('http://vyshka.math.ru/ajlfdjalsdjf')
print(q.ok)
False
Мы попытались перейти по несуществующей странице и она не загрузилась. Вернемся к успешному запросу r
. Посмотрим на html исходник страницы командой
print(r.text)
<html> <head> <title>HSE: Math department</title> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=koi8-r"> </head> <body bgcolor="#F5DEB3"> <!--begin of Top100 code --> <a href="http://top100.rambler.ru/top100/"> <img src="http://counter.rambler.ru/top100.cnt?188597" alt="" width=1 height=1 border=0></a> <!--end of Top100 code--> <h1>æÁËÕÌØÔÅÔ ÍÁÔÅÍÁÔÉËÉ îéõ ÷ûü</h1> <table cellspacing=10> <tr> <td align="left" valign="center" width=195> <img src="img/matfak.jpg" alt="äÏÍ ÎÁ ÷Á×ÉÌÏ×Á" width=193 height=398 align="right"> </td> <td align="left"> üÔÏÔ ÓÁÊÔ, ÐÏÄÄÅÒÖÉ×ÁÅÍÙÊ ÎÅÓËÏÌØËÉÍÉ ÓÏÔÒÕÄÎÉËÁÍÉ ÆÁËÕÌØÔÅÔÁ, ÚÁÄÕÍÁÎ ËÁË ÄÏÐÏÌÎÅÎÉÅ Ë <a href="http://math.hse.ru/">ÏÆÉÃÉÁÌØÎÏÍÕ ÓÁÊÔÕ ÆÁËÕÌØÔÅÔÁ ÍÁÔÅÍÁÔÉËÉ</a> ÷ÙÓÛÅÊ ÛËÏÌÙ ÜËÏÎÏÍÉËÉ (<a href="http://hse.ru">îéõ ÷ûü</a>). íÙ ÐÌÁÎÉÒÕÅÍ ÒÁÚÍÅÝÁÔØ ÚÄÅÓØ ÍÁÔÅÒÉÁÌÙ ËÕÒÓÏ× É ÉÎÆÏÒÍÁÃÉÀ Ï ÔÅËÕÝÉÈ ÄÅÌÁÈ. </font> </td> </table> <h2>ëÕÒÓÙ 2015/16 ÕÞÅÂÎÏÇÏ ÇÏÄÁ</h2> <ul> <li>1 ËÕÒÓ ÂÁËÁÌÁ×ÒÉÁÔÁ. <li>2 ËÕÒÓ ÂÁËÁÌÁ×ÒÉÁÔÁ. <ul> <li><a href="1516/topology2.php">÷×ÅÄÅÎÉÅ × ÔÏÐÏÌÏÇÉÀ</a> <li><a href="1516/algebra2.php">áÌÇÅÂÒÁ</a> </ul> <li>3–4 ËÕÒÓÙ ÂÁËÁÌÁ×ÒÉÁÔÁ. <br>(×ÓÅ ÐÒÅÄÍÅÔÙ ÐÏ ×ÙÂÏÒÕ) <ul> <li><a href="1516/funcan.php">æÕÎËÃÉÏÎÁÌØÎÙÊ ÁÎÁÌÉÚ</a> <li><a href="1516/DiffGeom.php">äÉÆÆÅÒÅÎÃÉÁÌØÎÁÑ ÇÅÏÍÅÔÒÉÑ</a> </ul> <li>1 ËÕÒÓ ÍÁÇÉÓÔÒÁÔÕÒÙ. <br>îÁ 2 ËÕÒÓÅ ÍÁÇÉÓÔÒÁÔÕÒÙ É × ÁÓÐÉÒÁÎÔÕÒÅ ÏÂÑÚÁÔÅÌØÎÙÈ ÚÁÎÑÔÉÊ ÎÅÔ, ÔÏÌØËÏ ÓÐÅÃËÕÒÓÙ É ÓÐÅÃÓÅÍÉÎÁÒÙ. <li>óÐÅÃËÕÒÓÙ <ul> <li><a href="1516/tvs.php">ôÏÐÏÌÏÇÉÞÅÓËÉÅ ×ÅËÔÏÒÎÙÅ ÐÒÏÓÔÒÁÎÓÔ×Á</a> </ul> <li>óÐÅÃÓÅÍÉÎÁÒÙ <ul> <li><a href="1516/basic.php">ïÓÎÏ×ÎÙÅ ÐÏÎÑÔÉÑ ÍÁÔÅÍÁÔÉËÉ</a> </ul> </ul> <h2>ëÕÒÓÙ ÐÒÏÛÌÙÈ ÌÅÔ</h2> <ul> <li><a href="0809/0809.html">2008/09</a> <li><a href="0910/0910.html">2009/10</a> <li><a href="1011/1011.html">2010/11</a> <li><a href="1112/1112.html">2011/12</a> <li><a href="1213/1213.php">2012/13</a> <li><a href="1415/1415.php">2014/15</a> </ul> </body> </html>
То, что вы видите выше — HTML-страница. HTML (HyperText Markup Language) — это такой язык разметки, являющийся частным случаем стандарта SGML. Другим частным случаем SGML является XML, с которым мы еще встретимся.
Напишем простенькую HTML-страницу. Удобнее всего это делать в каком-либо редакторе. Но я запишу ее в файл через ноутбук.
my_html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset = "UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello</h1>
<p>I'm a paragraph.</p>
<hr>
<ol>
<li>One</li>
<li>Two</li>
</ol>
</body>
</html>
'''
with open('my.html', 'w') as f:
f.write(my_html)
Откройте my.html
браузером и вы увидите простую веб-страничку. Видно что HTML разбит на специальные фрагменты, которые называются тегами. В тексте выше есть теги: <html>
, <head>
, <title>
и т.д. Каждый тег отмечает какой-то кусочек веб-страницы. Тег <title>
— это заголовок страницы. Тег <ol>
отмечает упорядоченный список. Тег <li>
отвечает элементу списка. Тег <p>
— абзац (paragraph). Все перечисленные теги являются парными: они отмечают какой-то фрагмент текста (возможно, содержащий другие теги), помещая его между соответствующим открывающим и закрывающим тегом (например, <li>
— открывающий тег, а </li>
— закрывающий; всё, что между ними — это элемент списка). Исключением здесь является тег <hr>
, который рисует горизонтальную линию (он работает и без </hr>
).
Фактически HTML-страница представляет собой набор вложенных тегов. Можно сказать, что это дерево с корнем в теге <html>
. У каждого тега есть потомки - те теги, которые непосредственно вложены в него. Например, у тега <body>
потомками будут <h1>
, <p>
, <hr>
, <ol>
. Получается такое как бы генеалогическое древо.
HTML нас интересует с целью извлечения информации из такого дерева. Одним из наиболее популярных объектов для хранения информации являются таблицы, поэтмоу давайте вставим в наш файл небольшую таблицу: она обозначается тегом <table>
, каждая строка таблицы выделяется тегом <tr>
внутри <table>
, а каждая ячейка — тегом <td>
внутри <tr>
.
my_html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset = "UTF-8">
<title>Title</title>
<style type='text/css;'>
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
</style>
</head>
<body>
<h1>Hello</h1>
<p>I'm a paragraph.</p>
<hr>
<ol>
<li>One</li>
<li>Two</li>
</ol>
<table>
<tr>
<td>
Cell 1
</td>
<td>
Cell 2
</td>
</tr>
<tr>
<td>
Cell 3
</td>
<td>
Cell 4
</td>
</tr>
</table>
</body>
</html>
'''
with open('my.html', 'w') as f:
f.write(my_html)
Вот так выглядит эта страница:
Допустим, что она лежит где-то на удалённом сайте. Давайте загрузим ее с помощью requests
и попробуем извлечь какую-то информацию.
r = requests.get('http://math-info.hse.ru/f/2015-16/all-py/my.html')
Для обработки веб-страниц существует множество пакетов. Проблема с HTML в том, что большинство браузеров ведет себя «прощающе», и поэтому в вебе много плохо-написанных (не по стандарту HTML) HTML-страниц. Впрочем, обработка даже не вполне корректного HTML-кода не так сложна, если под рукой есть подходящие инструменты.
Мы будем пользоваться пакетом Beautiful Soup 4. Он входит в стандартную поставку Anaconda, но если вы используете другой дистрибутив Python, возможно, вам придётся его установить вручную с помощью pip install beautifulsoup4
.
Пакет под названием
BeautifulSoup
— скорее всего, не то, что вам нужно. Это третья версия (Beautiful Soup 3), а мы будем использовать четвертую. Так что нам нужен пакетbeautifulsoup4
. Чтобы было совсем весело, при импорте нужно указывать другое название пакета —bs4
, а импортировать функцию под названиемBeautifulSoup
. В общем, сначала легко запутаться, но эти трудности нужно преодолеть однажды, а потом будет проще.
from bs4 import BeautifulSoup
Чтобы использовать Beautiful Soup, нужно передать функции BeautifulSoup
текст веб-страницы (в виде одной строки). Чтобы он не ругался, я также вручную указываю название парсера (той программы, которая как раз и осуществляет обработку HTML) — с целью совместимости я использую html.parser
(он входит в поставку Python и не требует установки), но вы можете также попробовать использовать lxml
, если он у вас установлен.
page = BeautifulSoup(r.text, 'html.parser')
Что теперь лежит в переменной page
? Давайте посмотрим.
page
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Title</title> <style type="text/css;"> table { border-collapse: collapse; } table, th, td { border: 1px solid black; } </style> </meta></head> <body> <h1>Hello</h1> <p>I'm a paragraph.</p> <hr> <ol> <li>One</li> <li>Two</li> </ol> <table> <tr> <td> Cell 1 </td> <td> Cell 2 </td> </tr> <tr> <td> Cell 3 </td> <td> Cell 4 </td> </tr> </table> </hr></body> </html>
Мы видим, что объект page
очень похож на строку, но, на самом деле, это не просто строка. К page
можно делать запросы. Например:
page.html
<html lang="en"> <head> <meta charset="utf-8"> <title>Title</title> <style type="text/css;"> table { border-collapse: collapse; } table, th, td { border: 1px solid black; } </style> </meta></head> <body> <h1>Hello</h1> <p>I'm a paragraph.</p> <hr> <ol> <li>One</li> <li>Two</li> </ol> <table> <tr> <td> Cell 1 </td> <td> Cell 2 </td> </tr> <tr> <td> Cell 3 </td> <td> Cell 4 </td> </tr> </table> </hr></body> </html>
Мы видим то, что внутри тега <html>
(это почти вся страница, но самая первая строчка «отрезалась»). Можно пойти вглубь и посмотреть на содержимое <head>
.
page.html.head
<head> <meta charset="utf-8"> <title>Title</title> <style type="text/css;"> table { border-collapse: collapse; } table, th, td { border: 1px solid black; } </style> </meta></head>
Теперь мы видим только то, что внутри тега <head>
. Мы можем пойти еще глубже, и получить то, что находится внутри тега <title>
, который в свою очередь находится внутри тега <head>
(говорят, что <title>
является потомком <head>
:
page.html.head.title
<title>Title</title>
Впрочем, можно было бы и не писать так подробно — поскольку в документе есть только один тег <title>
, мы бы могли не указывать, что он находится внутри <head>
, который находится внутри <html>
.
page.head.title
<title>Title</title>
page.title
<title>Title</title>
Одним из потомков <body>
является <table>
. Ее можно получить вот так.
page.body.table
<table> <tr> <td> Cell 1 </td> <td> Cell 2 </td> </tr> <tr> <td> Cell 3 </td> <td> Cell 4 </td> </tr> </table>
Допустим, что мне нужно получить несколько элементов с одинаковым тегом, например, все строки <tr>
. Для этого используется такой синтаксис:
rows = page.body.table.findAll('tr')
rows
[<tr> <td> Cell 1 </td> <td> Cell 2 </td> </tr>, <tr> <td> Cell 3 </td> <td> Cell 4 </td> </tr>]
len(rows)
2
Мы видим, что это список из двух элементов. Так что по нему можно пройти циклом.
for i, row in enumerate(rows):
print(i)
print(row)
0 <tr> <td> Cell 1 </td> <td> Cell 2 </td> </tr> 1 <tr> <td> Cell 3 </td> <td> Cell 4 </td> </tr>
У нас есть 2 строчки и каждая из них является таким же объектом BeautifulSoup, как и все предыдущие. Так что к ним можно применить конструкцию row.td
for i, row in enumerate(rows):
print(i)
print(row.td)
0 <td> Cell 1 </td> 1 <td> Cell 3 </td>
Мы видим, что если внутри тега <row>
есть несколько тегов <td>
, то row.td возьмет первый из них. Поэтому мы получили первый столбец. Но нас интересует не сам тег <td>
, а строка, которая там лежит. Её можно напечатать вот так.
for i, row in enumerate(rows):
print(i)
print(row.td.string)
0 Cell 1 1 Cell 3
Видно, что перед строкой идут ненужные пробелы. Удалим их командой strip
for i, row in enumerate(rows):
print(i)
print(row.td.string.strip())
0 Cell 1 1 Cell 3
Давайте загрузим таблицу в виде списка списков
table = []
for i, row in enumerate(rows):
table.append([])
for cell in row.findAll('td'):
table[-1].append(cell.string.strip())
print(table)
[['Cell 1', 'Cell 2'], ['Cell 3', 'Cell 4']]
Вот то же самое, но короче с помощью list comprehensions:
table = []
for row in rows:
table.append([cell.string.strip() for cell in row.findAll('td')])
print(table)
[['Cell 1', 'Cell 2'], ['Cell 3', 'Cell 4']]
Или еще короче (но заковыристее):
table = [[cell.string.strip() for cell in row.findAll('td')]
for row in rows]
print(table)
[['Cell 1', 'Cell 2'], ['Cell 3', 'Cell 4']]
Заметим, что вместо some_beautiful_soup_objec.findAll('sometag')
можно писать короче some_beautiful_soup_object('sometag')
. Так что можно написать еще короче
table = [[cell.string.strip() for cell in row('td')]
for row in rows]
print(table)
[['Cell 1', 'Cell 2'], ['Cell 3', 'Cell 4']]
У тегов, кроме названия, бывают еще свойства — например, в строчке <html lang="en">
мы видим свойство lang
у тега <html>
, имеющее значение "en"
. Другим важным примером тега со свойствами является тег <a>
, который создает ссылку. У него есть свойство href
, которое хранит собственно ссылку.
Например, строка
<a href="http://math-info.hse.ru/s15/m">Курс по Python</a>
превращается в ссылку Курс по Python, ведущую на страницу нашего курса.
Теперь представим себе, что мы хотим сделать робота, который будет ходить по веб-страницам, и переходить с одной страницы на другую по ссылкам. Тогда мы сталкиваемся с задачей извлечь из страницы все гиперссылки.
Для этого нужно найти все теги <a>
на странице, и у всех них взять параметр <href>
. Для начала покажем как получить свойство объекта, например, lang
у html
. Это делается так как будто наш объект словарь, и мы берем его значение по ключу.
page.html['lang']
'en'
Если запросить свойство, которое тег не имеет, то мы получим KeyError, как и со словарем.
page.html['strange']
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-104-e19396244582> in <module>() ----> 1 page.html['strange'] /usr/local/lib/python3.5/site-packages/bs4/element.py in __getitem__(self, key) 956 """tag[key] returns the value of the 'key' attribute for the tag, 957 and throws an exception if it's not there.""" --> 958 return self.attrs[key] 959 960 def __iter__(self): KeyError: 'strange'
Так же, как у словаря, есть метод get()
, который ничего не возвращает, если такого свойства нет. Или возвращает значение по умолчанию, определенное нами.
page.html.get('strange')
page.html.get('strange', 'no-such-tag')
'no-such-tag'
Теперь извлечем все ссылки с какого-нибудь сайта
r = requests.get('http://vyshka.math.ru')
page = BeautifulSoup(r.text, 'html.parser')
Вот все ссылки на нашей странице.
page('a')
[<a href="http://top100.rambler.ru/top100/"> <img alt="" border="0" height="1" src="http://counter.rambler.ru/top100.cnt?188597" width="1"/></a>, <a href="http://math.hse.ru/">ÏÆÉÃÉÁÌØÎÏÍÕ ÓÁÊÔÕ ÆÁËÕÌØÔÅÔÁ ÍÁÔÅÍÁÔÉËÉ</a>, <a href="http://hse.ru">îéõ ÷ûü</a>, <a href="1516/topology2.php">÷×ÅÄÅÎÉÅ × ÔÏÐÏÌÏÇÉÀ</a>, <a href="1516/algebra2.php">áÌÇÅÂÒÁ</a>, <a href="1516/funcan.php">æÕÎËÃÉÏÎÁÌØÎÙÊ ÁÎÁÌÉÚ</a>, <a href="1516/DiffGeom.php">äÉÆÆÅÒÅÎÃÉÁÌØÎÁÑ ÇÅÏÍÅÔÒÉÑ</a>, <a href="1516/tvs.php">ôÏÐÏÌÏÇÉÞÅÓËÉÅ ×ÅËÔÏÒÎÙÅ ÐÒÏÓÔÒÁÎÓÔ×Á</a>, <a href="1516/basic.php">ïÓÎÏ×ÎÙÅ ÐÏÎÑÔÉÑ ÍÁÔÅÍÁÔÉËÉ</a>, <a href="0809/0809.html">2008/09</a>, <a href="0910/0910.html">2009/10</a>, <a href="1011/1011.html">2010/11</a>, <a href="1112/1112.html">2011/12</a>, <a href="1213/1213.php">2012/13</a>, <a href="1415/1415.php">2014/15</a>]
Как видим, метод findAll()
(или его сокращённая форма записи в виде просто скобочек) ищет не только по непосредственным «детям» какой-то вершины (в генеалогических терминах), но и по всем потомкам.
Напечатаем сами ссылки
for link in page("a"):
print(link['href'])
http://top100.rambler.ru/top100/ http://math.hse.ru/ http://hse.ru 1516/topology2.php 1516/algebra2.php 1516/funcan.php 1516/DiffGeom.php 1516/tvs.php 1516/basic.php 0809/0809.html 0910/0910.html 1011/1011.html 1112/1112.html 1213/1213.php 1415/1415.php
Тут есть внешние гиперссылки, которые начинаются с http
, и локальные, которые ведут на тот же сайт и носят относительный характер (то есть перед 1516/topology2.php
нужно написать http://math.hse.ru/
, чтобы получить полную ссылку на соответствующий документ).
Теперь понятно, как должен действовать наш робот: для каждой из полученных ссылок он должен загрузить соответствующую страницу, найти на ней все ссылки, добавить их в очередь для исследования и т.д. Примерно так работают веб-краулеры поисковых систем. (Хотя, конечно, они устроены гораздо сложнее.)
До сих пор мы старались включать в конспекты все материалы, которые необходимы для выполнения домашних заданий. Начиная с этого момента ситуация меняется: мы изучаем библиотеки, обладающие огромным количеством функций, и не можем включить их полное описание в конспект. Отныне лекции будут скорее примером использования конкретной библиотеки, демонстрирующим общие принципы, но описание всех функций и способов работы с ними вам придётся искать в официальной документации. Чаще всего её можно найти на странице соответствующей библиотеки, которая находится любым веб-поиском по названию. Для Beautiful Soup документация лежит здесь, а для requests здесь (начните с Quickstart). Конечно, она на английском языке, но, как говорил мой преподаватель по программированию, «через полгода занятий программированием вы будете считать английский язык подмножеством русского».
Другой источник информации о библиотеках — всё тот же веб-поиск, который чаще всего будет выдавать ссылки на сайт с вопросами и ответами http://stackoverflow.com/. Например, набрав how to parse table with beautifulsoup вы получите несколько ссылок на stackoverflow с примерами кода. Кстати, на stackoverflow можно задавать и свои вопросы — но прежде нужно убедиться, что на них не ответили раньше.