Од­нажды кро­коди­лу Гене и Чебураш­ке поручи­ли написать сочине­ние на тему «Как я про­вел лето». Проб­лема была в том, что все лето друзья пили пиво. Гена, не уме­ющий врать, так и написал, поэто­му Чебураш­ке приш­лось заменить некото­рые сло­ва. А пос­коль­ку Чебураш­ка был кодером на питоне, то сде­лал он это при помощи стро­ковой фун­кции. В этой статье я покажу, как не отста­вать от Чебураш­ки и научить­ся работать со стро­ками, фай­лами и делать зап­росы к веб‑сай­там на Python.

От редакции

Не­дав­но мы про­вели опрос сре­ди читате­лей и выяс­нили, что мно­гие хотели бы изу­чить Python, при­чем начать с самого начала. В качес­тве экспе­римен­та мы опуб­ликова­ли статью «Python с абсо­лют­ного нуля. Учим­ся кодить без скуч­ных кни­жек», где рас­ска­зали об азах Python: перемен­ных, усло­виях, цик­лах и спис­ках. Откли­ки были позитив­ными, и мы решили про­дол­жить зна­комить читате­лей с Python в нашем фир­менном нес­кучном сти­ле.

Эта статья, как и пре­дыду­щая, дос­тупна без плат­ной под­писки, так что сме­ло делись эти­ми ссыл­ками с друзь­ями, которые меч­тают выучить Python!

Нач­нем со строк. Что­бы решить встав­шую перед друзь­ями проб­лему, Чебураш­ка исполь­зовал фун­кцию replace(), которая заменя­ет в стро­ке одну подс­тро­ку дру­гой.

Сна­чала он объ­явил перемен­ную s и помес­тил туда стро­ку, которую прис­лал ему Гена.

s = 'Все лето мы пили пиво. Вот как-то открываю дверь, а на пороге Чебурашка, весь такой пьяный-пьяный, и бутылка из кармана торчит.'

Даль­ше Чебураш­ка опре­делил сло­варь из слов, которые тре­бова­лось заменить.

slova = {'пили':'читали', 'пиво':'книги', 'пьяный':'начитанный', 'бутылка':'энциклопедия'}

И теперь при помощи цик­ла for Чебураш­ка переб­рал сло­варь, что­бы заменить каж­дое из слов (key) на соот­ветс­тву­ющее зна­чение из сло­варя (slova[key]):

for key in slova:
s = s.replace(key, slova[key])
print(s)

info

Сло­вари во мно­гом похожи на спис­ки, но зна­чения в них записа­ны парами: ключ и зна­чение. По клю­чу мож­но узнать зна­чение. Мож­но счи­тать, что в спис­ках клю­чи — это индексы (0, 1, 2...), а в сло­варях — стро­ки.

Фун­кцию replace() удоб­но исполь­зовать, что­бы начис­то уда­лить какие‑то сло­ва из стро­ки. Для это­го будем заменять их пус­той стро­кой (если открыть и зак­рыть кавыч­ку, то получит­ся пус­тая стро­ка):

s = '''Я не люблю пить пиво.
Оно невкусное и неполезное!'''
s = s.replace('не','')
print(s)

info

Что­бы записать в перемен­ную нес­коль­ко строк, мож­но обер­нуть их в три оди­нар­ные кавыч­ки и делать перено­сы пря­мо в коде.

Что­бы получить количес­тво сим­волов в стро­ке, исполь­зует­ся фун­кция len().

s = 'Если очень вам неймется, код пишите как придется!'
n = len(s)
print(n)

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

s = 'Меня зовут Бонд, Джеймс Бонд'
a = s[11:15]
print('Фамилия: ' + a)

Ес­ли нуж­но сде­лать срез с начала стро­ки, пер­вую циф­ру мож­но не писать.

Пред­положим, тебе нуж­но най­ти в спис­ке стро­ки, которые начина­ются на https. Переби­раем их с помощью for, для каж­дой про­веря­ем, сов­пада­ют ли пер­вые пять зна­ков со стро­кой https, и если да, то выводим стро­ку:

mas = [ 'Это просто строка', 'https://xakep.ru', 'Еще одна строка', 'https://habr.ru' ]
for x in mas:
if x[:5] == 'https':
print(x)

Что­бы пос­читать количес­тво вхож­дений подс­тро­ки в стро­ку, мож­но исполь­зовать метод .count():

s = 'Прикинь, короче, я такой, короче, ему бах эксплоитом по порту, а он, короче, упал сразу!'
n = s.count('короче')
print(n)

Иног­да в начале или в кон­це стро­ки могут быть лиш­ние про­белы или перено­сы строк. Давай уда­лим их спе­циаль­ной коман­дой .strip():

s = ' Пива много не бывает! \n'
s = s.strip()
print(s)

info

Пе­рено­сы строк мож­но добавить с помощью сим­волов \n (исполь­зует­ся во всех ОС) либо \r\n (в Windows). Есть и дру­гие спец­симво­лы. Нап­ример, \t — знак табуля­ции.

Что­бы опре­делить наличие подс­тро­ки в стро­ке s, мож­но исполь­зовать метод .find():

n = s.find('строка, которую ищем')

Ес­ли иско­мая подс­тро­ка най­дена, то в перемен­ную n попадет ее позиция в стро­ке, а если не най­дена, n ста­нет рав­ной -1.

Да­вай поп­робу­ем опре­делить, есть ли в стро­ке адрес элек­трон­ной поч­ты с Xakep.ru, то есть будем искать подс­тро­ку @xakep.ru.

Но сна­чала нам понадо­бит­ся еще один стро­ковый метод — .split(). Он поз­воля­ет раз­делить стро­ку на час­ти, ука­зав в качес­тве аргу­мен­та стро­ку‑раз­делитель. Нап­ример, s.split('\n') раз­делит текст на абза­цы по сим­волу перено­са стро­ки. Если же оста­вить скоб­ки пус­тыми, то будет исполь­зован раз­делитель по умол­чанию — про­бел.

s = 'Это обычная строка, а в ней адрес почты vasya@xakep.ru'
words = s.split()
for w in words:
n = w.find('@xakep.ru')
if n != -1:
print('Найден e-mail: ' + str(w) + ' в позиции ' + str(n))

Ме­тод .join() поз­воля­ет, наобо­рот, скле­ивать стро­ки. Он при­нима­ет спи­сок и воз­вра­щает стро­ку, где каж­дый эле­мент спис­ка соеди­нен с дру­гим через стро­ку, у которой ты выз­вал этот метод.

s = 'вирус внедряется '
list1 = ['раз, ', 'два, ', 'три...']
print(s + s.join(list1))
 

Форматируем строки

Мы не раз печата­ли раз­ные вещи, соеди­няя стро­ки прос­тым сло­жени­ем. Это не всег­да удоб­но, осо­бен­но учи­тывая, что если попадут­ся чис­ла, то их при­дет­ся перево­дить в стро­ки фун­кци­ей str(). Есть более кра­сивый и удоб­ный спо­соб под­став­лять зна­чения перемен­ных внутрь строк. Точ­нее, два нем­ного раз­ных спо­соба.

 

Способ 1 — c помощью метода .format()

Мы можем вста­вить в стро­ку пар­ные фигур­ные скоб­ки, а затем выз­вать стро­ковый метод .format() и передать ему нуж­ные зна­чения в поряд­ке их под­ста­нов­ки в стро­ку.

name = 'Вася Пупкин'
age = 20
address = 'улица Пушкина, дом Колотушкина'
info = 'Имя: {}. Возраст: {}. Адрес: {}'.format(name, age, address)
print(info)

Мож­но передать информа­цию спис­ком через звез­дочку:

data = ['Вася Пупкин', 20, 'улица Пушкина, дом Колотушкина']
info = 'Имя: {}. Возраст: {}. Адрес: {}'.format(*data)
print(info)
 

Способ 2 — через f-строки

Дру­гой вари­ант — написать бук­ву f перед стро­кой и затем в фигур­ных скоб­ках ука­зывать непос­редс­твен­но перемен­ные.

name = 'Вася Пупкин'
age = 20
address = 'улица Пушкина, дом Колотушкина'
info = f'Имя: {name.upper()}. Возраст: {age}. Адрес: {address}'
print(info)

Глав­ное пре­иму­щес­тво это­го спо­соба в том, что ты можешь вста­вить зна­чение в стро­ку нес­коль­ко раз. К тому же мож­но менять зна­чения пря­мо в фигур­ных скоб­ках: спер­ва Python выпол­нит все дей­ствия в них, а затем под­ста­вит получен­ный резуль­тат в стро­ку. Так, метод .upper() в при­мере выше дела­ет все бук­вы заг­лавны­ми.

 

Файлы

Пе­речис­ленных методов дос­таточ­но, что­бы ты мог делать со стро­ками что угод­но. Но отку­да эти стро­ки возь­мут­ся? Чаще все­го они записа­ны в фай­лах, поэто­му сей­час я рас­ска­жу, как в Python с ними управлять­ся.

Что­бы работать с фай­лом, его нуж­но открыть. Для это­го слу­жит фун­кция open(), а работа­ет она вот так:

f = open('имя файла с путем и расширением', 'режим работы с файлом', encoding='Кодировка текста')

Ре­жимов работы с фай­лами нес­коль­ко, но тебя инте­ресу­ет в основном:

  • r — открыть файл для чте­ния из него информа­ции;
  • w — открыть файл для записи в него информа­ции (соз­дает новый файл);
  • a — откры­тие фай­ла для дозапи­си информа­ции в конец фай­ла (дописы­вает информа­цию в конец сущес­тву­юще­го фай­ла);
  • a+ — дозапись и чте­ние.

Что­бы избе­жать проб­лем с путями в Windows, исполь­зуй в них двой­ной слеш '\', а так­же перед откры­вающей кавыч­кой пути фай­ла ставь бук­ву u, ука­зыва­ющую на то, что стро­ка в кодиров­ке Unicode:

f = open(u'D:\\test.txt', 'r', encoding='UTF-8')

Чи­тать стро­ки из фай­ла мож­но методом .read():

f = open('test.txt', 'r', encoding='UTF-8')
s = f.read()
print(s)

Как вари­ант — мож­но пос­ледова­тель­но читать из фай­ла отдель­ные стро­ки с помощью цик­ла for:

f = open('test.txt', 'r', encoding='UTF-8')
for x in f:
print(x)

Пос­ле того как работа с фай­лом закон­чена, нуж­но зак­рыть его.

f.close()

info

Для работы с бинар­ными фай­лами при откры­тии фай­ла добавь к режиму бук­ву b:

f = open('myfile.bin', 'rb')
d = f.read()
print("d = ", d)

Под­робнее о бинар­ных дан­ных мы погово­рим в одной из сле­дующих ста­тей.

Да­вай теперь поп­робу­ем соз­дать новый тек­сто­вый файл в одном катало­ге с нашим скрип­том и записать в него зна­чения каких‑то перемен­ных.

s1 = 'Раз, два, три, четыре, пять\n'
s2 = иду сервак ломать...\n'
f = open('poems.txt', 'w', encoding='UTF-8')
f.write(s1)
f.write(s2)
f.close()

Об­рати вни­мание, что в кон­це каж­дой стро­ки сто­ит сим­вол \n — переход на новую стро­ку.

До­пус­тим, ты хочешь дописать третью строч­ку в конец это­го фай­ла. Тут‑то и при­годит­ся режим дозапи­си!

s3 = 'Ох, устанут поднимать!\n'
f = open('poems.txt', 'a', encoding='UTF-8')
f.write(s3)
f.close()

Для откры­тия фай­лов так­же очень удоб­но исполь­зовать конс­трук­цию with open('имя файла с путем и расширением', 'режим работы с файлом') as f, потому что бла­года­ря сло­ву with файл зак­роет­ся авто­мати­чес­ки и тебе не при­дет­ся думать об этом.

s = 'Если вы закроете этот файл, ваш диск будет отформатирован!\nШутка\n'
with open('test.txt', 'w', encoding='UTF-8') as f:
f.write(s)
 

Работа с вебом

Да­вай научим­ся получать информа­цию с веб‑стра­ниц. Для начала нуж­но уста­новить нес­коль­ко модулей. Пишем в коман­дной стро­ке:

pip install requests
pip install html2text

Мо­дуль requests поз­воля­ет делать GET- и POST-зап­росы к веб‑стра­ницам.
Мо­дуль html2text слу­жит для пре­обра­зова­ния HTML-кода веб‑стра­ниц в обыч­ный текст, то есть чис­тит его от тегов HTML.

Им­порти­руем наши новые модули в начале прог­раммы и поп­робу­ем получить какую‑нибудь стра­ницу из интерне­та.

import requests
# Делаем GET-запрос
s = requests.get('http://xakep.ru')
# Печатаем код ответа сервера
print(s.status_code)
# Печатаем HTML-код
print(s.text)

Прог­рамма напеча­тает мно­го HTML-кода, из которо­го сос­тоит глав­ная стра­ница жур­нала. Но что, если тебе нужен толь­ко текст сай­та, а не мешани­на из тегов? Здесь поможет html2text. Он выделит из кода текст, заголов­ки и кар­тинки и отдаст их уже без HTML-тегов.

import requests
import html2text
# Делаем GET-запрос
s = requests.get('http://xakep.ru')
# Код ответа сервера
print(s.status_code)
# Создается экземпляр парсера
d = html2text.HTML2Text()
# Параметр, влияющий на то, как парсятся ссылки
d.ignore_links = True
# Текст без HTML-тегов
c=d.handle(s.text)
print(c)

Кро­ме GET-зап­росов, сущес­тву­ют так называ­емые POST-зап­росы, которые при­меня­ются для отсылки на сер­вер боль­ших тек­стов или каких‑то фай­лов. Если видишь на сай­те фор­му, осо­бен­но с заг­рузкой фай­ла, зна­чит, ско­рее все­го, при нажатии на кноп­ку «Отпра­вить» будет сде­лан POST-зап­рос.

Биб­лиоте­ка requests тоже поз­воля­ет делать POST-зап­росы. Тебе это может при­годить­ся для ими­тации дей­ствий поль­зовате­ля — нап­ример, если нуж­но авто­мати­зиро­вать работу с сай­том. Можешь даже исполь­зовать это в качес­тве самопис­ного ана­лога Burp!

Да­вай пос­мотрим, как пос­лать обыч­ный POST-зап­рос. Пред­положим, на сай­те site.ru сущес­тву­ет скрипт guest.php, который POST-зап­росом при­нима­ет от фор­мы имя поль­зовате­ля name и сооб­щение message, а затем пос­тит их в гос­тевую кни­гу.

import requests
# Переменные, которые нужно отправить POST-запросом
user = 'coolhacker'
message = 'You have beeh pwned!!!'
# Делаем POST-запрос и передаем словарь из полей
r = requests.post("http://site.ru/guest.php", data={'user': user, 'message': message})
print(r.status_code)

Те­перь давай отпра­вим зап­рос с фай­лом payload.php во вло­жении и теми же дву­мя полями фор­мы, что и в пре­дыду­щем зап­росе. Файл при­дет на сер­вер под име­нем misc.php.

import requests
user = 'kitty2007'
message = '(* ^ ω ^)'
# Открываем файл в бинарном режиме
with open('payload.php', 'rb') as f:
# POST-запрос с отправкой файла
r = requests.post('http://site.ru/upload.php', files={'misc.php': f}, data={'user': user, 'message': message})

Ос­талось научить­ся ска­чивать фай­лы. Это во мно­гом похоже на зап­рос стра­ниц, но делать это луч­ше в потоко­вом режиме (stream=True). Так­же нам понадо­бит­ся модуль shutil, в котором есть удоб­ная фун­кция copyfileobj. Она поз­воля­ет копиро­вать содер­жимое дво­ичных фай­лов — в нашем слу­чае из интерне­та к нам на диск.

import requests
import shutil
import os
# Файл, который надо скачать
s = 'https://xakep.ru/robots.txt'
# С помощью функции os.path.split(s) вытаскиваем из строки путь к файлу и его имя
dirname, filename = os.path.split(s)
# GET-запрос в режиме stream=True для скачивания файла
r = requests.get(s, stream=True)
# Если ответ сервера удачен (200)
if r.status_code == 200:
# Создаем файл и открываем его в бинарном режиме для записи
with open(filename, 'wb') as f:
# Декодируем поток данных на основе заголовка content-encoding
r.raw.decode_content = True
# Копируем поток данных из интернета в файл с помощью модуля shutil
shutil.copyfileobj(r.raw, f)

info

Ко­ды отве­та сер­вера помога­ют понять, как про­шел зап­рос. Код 200 озна­чает, что сер­вер успешно обра­ботал зап­рос и отдал нам ответ, код 404 — стра­ница не была най­дена, 500 — внут­ренняя ошиб­ка сер­вера, 503 — сер­вер недос­тупен и так далее. Пол­ный спи­сок кодов ты най­дешь в Википе­дии.

 

Обработка ошибок

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

Час­то при работе прог­раммы компь­ютер стал­кива­ется с раз­ными проб­лемами. Нап­ример, файл не най­ден, сеть недос­тупна, кон­чилось мес­то на дис­ке. Если прог­раммист об этом не позабо­тил­ся, то интер­пре­татор Python прос­то завер­шит работу с ошиб­кой. Но есть спо­соб пре­дус­мотреть неуря­дицы пря­мо в коде и про­дол­жать работу — конс­трук­ция try... except.

Выг­лядит она вот так:

try:
# Тут какие-то команды,
# которые могут привести к ошибке
except:
# Наши действия, если ошибка произошла

Мож­но ловить кон­крет­ные типы оши­бок, если пос­ле сло­ва except ука­зать наз­вание типа. К при­меру, KeyboardInterrupt сра­баты­вает, если поль­зователь пыта­ется завер­шить прог­рамму, нажав Ctrl-C. В нашей влас­ти зап­ретить это делать!

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

try:
k = 1 / 0
except ZeroDivisionError:
k = 'over 9000'
print(k)
 

Пишем сканер портов

А теперь мы напишем собс­твен­ный ска­нер пор­тов! Он будет прос­тень­ким, но впол­не рабочим. Поможет нам в этом модуль socket, где реали­зова­на работа с сокета­ми.

info

Cокет — это интерфейс обме­на дан­ными меж­ду про­цес­сами. Сущес­тву­ют кли­ент­ские и сер­верные сокеты. Сер­верный сокет слу­шает опре­делен­ный порт в ожи­дании под­клю­чения кли­ентов, а кли­ент­ский под­клю­чает­ся к сер­веру. Пос­ле того как было уста­нов­лено соеди­нение, начина­ется обмен дан­ными.

Вот как будет выг­лядеть код.

import socket
# Список портов для сканирования
ports = [20, 21, 22, 23, 25, 42, 43, 53, 67, 69, 80, 110, 115, 123, 137, 138, 139, 143, 161, 179, 443, 445, 514, 515, 993, 995, 1080, 1194, 1433, 1702, 1723, 3128, 3268, 3306, 3389, 5432, 5060, 5900, 5938, 8080, 10000, 20000]
host = input('Введи имя сайта без http/https или IP-адрес: ')
print ("Ожидай, идет сканирование портов!")
# В цикле перебираем порты из списка
for port in ports:
# Создаем сокет
s = socket.socket()
# Ставим тайм-аут в одну cекунду
s.settimeout(1)
# Ловим ошибки
try:
# Пробуем соединиться, хост и порт передаем как список
s.connect((host, port))
# Если соединение вызвало ошибку
except socket.error:
# тогда ничего не делаем
pass
else:
print(f"{host}: {port} порт активен")
# Закрываем соединение
s.close
print ("Сканирование завершено!")

Как видишь, ничего слож­ного!

 

Домашнее задание

  1. Сде­лай, что­бы ска­нер пор­тов получал спи­сок IP из одно­го фай­ла, а резуль­таты ска­ниро­вания записы­вал в дру­гой.

  2. В прош­лой статье ты научил­ся работать с буфером обме­на. Напиши прог­рамму, которая пос­тоян­но запуще­на и пери­оди­чес­ки получа­ет содер­жимое буфера обме­на. Если оно изме­нилось, то дописы­вает его в конец фай­ла monitoring.txt. Поп­робуй записы­вать в лог толь­ко те перех­вачен­ные стро­ки, в которых есть латин­ские бук­вы и циф­ры, так более веро­ятно пой­мать пароли.

  3. На­пиши прог­рамму, которая чита­ет файл такого вида:

    Иван Иванов|ivanov@mail.ru|Password123
    Дима Лапушок|superman1993@xakep.ru|1993superman
    Вася Пупкин|pupok@yandex.ru|qwerty12345
    Фродо Бэггинс|Frodo@mail.ru|MoRdOr100500
    Кевин Митник|kevin@xakep.ru|dontcrackitplease
    Юзер Юзерович|uswer@yandex.ru|aaaa321

    Прог­рамма дол­жна сор­тировать стро­ки по доменам из email, для каж­дого домена соз­давать файл и в каж­дый файл помещать спи­сок поч­товых адре­сов.

  4. На­пиши прог­рамму, которая про­ходит сай­ты по спис­ку, ска­чива­ет фай­лы robots.txt и sitemap.xml и сох­раня­ет на диск. В слу­чае если файл не най­ден, выводит­ся сооб­щение об этом.

На сегод­ня всё. Из сле­дующей статьи ты узна­ешь, как работать с фай­ловой сис­темой ОС, раз­берешь­ся с фун­кци­ями, поз­наешь силу регуляр­ных выраже­ний и напишешь прос­той ска­нер SQL-уяз­вимос­тей. Не про­пус­ти!

15 комментариев

  1. Аватар

    Novosedoff

    07.07.2021 в 17:24

    Не хватает, видать, раз-раз-рабов, да? 🙂 Так активно отечественный агитпро этот беспонтовый Питон рекламирует последние лет 10..

  2. Аватар

    Unvictis

    07.07.2021 в 20:33

    Рад что наш человек добрался и до «Хакера»! Ваня, так держать! Привет из Кокшетау!

  3. Аватар

    Novosedoff

    07.07.2021 в 22:40

    в принципе мне пара обезьян с такими скилзами тоже пригодилась бы, цифирки считать, графики строить

    • Аватар

      Иван Сараев

      08.07.2021 в 15:59

      А вы наверное сразу стали сеньором, и программировали сходу в машинных кодах ))
      Начинать то с чего-то нужно людям юного возраста.

  4. Аватар

    toxicshadow

    08.07.2021 в 10:33

    Про обработку исключений во 2-й статье цикла — это правильно!

  5. Аватар

    serge007gr

    10.07.2021 в 10:57

    Спасибо! Очень интересный цикл статей. Жду продолжения.

  6. Аватар

    Oleg2

    14.07.2021 в 02:24

    Мне понравилась статья.Но вот что делать,если в теории код понятен и ты себе можешь объяснить каждую строчку,а с написанием своих программ очень туго?

    • Андрей Письменный

      Андрей Письменный

      14.07.2021 в 13:39

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

  7. Аватар

    k4nx

    16.07.2021 в 15:02

    Thank you for the article, i love the new python series !

  8. Аватар

    Oleg2

    23.07.2021 в 22:08

    У меня сегодня получилась первая рабочая программка.До этого был только копипаст)))

Оставить мнение