Не так давно прошел конкурс Археолог: поиск подарков. Это уже второй официальный конкурс, в котором я побеждаю. Но на этот раз, вместо розыгрыша призов, я решил рассказать вам о методе, благодаря которому я победил.
Что нужно сделать? В подфоруме Ниндзя, в различных случайных темах Shurado отредактировал сообщения пользователей так, что в них появился подарок - картинка, похожая на эту:
Всего таких подарков спрятано 20. За каждый выдается награда, а тем, кто нашел больше всего, выдается прозвище "Археолог" и пять звезд активности.
И как искать? Простые смертные вынуждены вручную перебирать каждую страницу каждой темы, пока не найдут заветный подарок. Также Shurado предложил писать ему в личные сообщения, с просьбой о подсказке. Естественно, за небольшую плату. Для тех же, кто хоть немного знаком с программированием, открываются куда более широкие возможности. И если вам было абсолютно нечего делать вечером, вы могли потратить час на написание скрипта, который позволит в таком конкурсе победить. Как вы уже догадались, мне было нечего делать.
Используемые инструменты Python (у меня - 3.7) Библиотеки: requests bs4 fake-useragent re
Описание алгоритма К участию в конкурсе я присоединился довольно поздно, уже было найдено около семи подарков. Но благодаря тому, что часть подарков уже нашли за меня, я смог придумать этот алгоритм. Я заметил, что все подарки - это однотипные картинки. Раз так, то давайте взглянем на картинку чуть более подробно. А именно, нас интересует ее URL. Для приведенного на этой странице подарка: httрs://i.ibb.co/w6TSv6z/1.png, или, в более общем виде: httрs://i.ibb.co/{сколько-то произвольных символов}/{сколько-то произвольных цифр}.png Мы можем искать такую подстроку строку в html-коде страницы. Все, что осталось - получить этот html-код
К счастью, эта задача возникает довольно часто, и добрые люди уже давно написали для этого библиотеку: requests Можете запустить интерпретатор и исполнить следующий код:
Теперь переменная r - это Response object, содержащий информацию о запрошенной странице. Так, например r.content позволит получить тот заветный html-код страницы, а r.status_code - код ответа (например, 404, если страница не была найдена) Посмотрим на код ответа в нашем случае:
>>>print(r.status_code)
403
403 - код ответа "Запрещено", т.е. сайт понял, что мы пытаемся сделать автоматизированный запрос. Один из самых простых способов обойти это - сделать запрос более "человечным", т.е. передать правдоподобные headers (заголовки, мета-информация, которую браузер автоматически посылает вместе с запросом. В нашем коде никакого браузера не использовалось, поэтому заголовки подставлялись по-умолчанию) Для этого мы используем еще одну библиотеку: fake-useragent. Мы создадим правдоподобные заголовки и передадим их в метод get вместе с ссылкой на желаемую страницу
200 - код "ОК", т.е. теперь в r.content лежит нужный нам html-код (не буду его приводить, слишком много =) Можете сами посмотреть: ctrl+shift+j ). В этом коде мы можем найти подстроку, "похожую" на url подарка, и задача решена.
Но это все еще далеко от автоматического поиска: сейчас нам нужно вручную перебирать все темы, подавать их на вход программе. Не страшно, исправим. Как? Очередная библиотека, конечно же: bs4 (BeautifulSoup, прекрасный суп =) ). Она позволяет превратить html-код в дерево тегов. Звучит сложно, но использовать эту библиотеку очень легко, достаточно бегло взглянуть на документацию По сути, она сильно облегчит нам автоматический разбор html-кода.
Алгоритмично опишу, как мы автоматизируем перебор тем:
Каждая тема лежит в элементе <div> с атрибутом class="th-item__message"
В этом элементе найдем тэг <a> с атрибутом itemprop="url" и получим значение его атрибута href - это ссылка на тему
Ищем в этой теме подарок
Когда мы перебрали все темы на этой странице, переходим на следующую. Для этого к ссылке на первую страницу подставляем "/page-{номер страницы}"
Повторяем все на следующей, пока не переберем 21 страницу
Регулярные выражения Подробнее обратим внимание на пункт с поиском подарка. Во-первых, в теме с подарком может быть несколько страниц, поэтому придется реализовать алгоритм, похожий на перебор тем. Во-вторых, мы хотим искать картинку с "похожим url". Но что это значит? Здесь нам на помощь приходят регулярные выражения. Это специальный язык, позволяющий задать правило для поиска подстроки, т.е. формализировать понятие "похожий". Мы будем искать строку, определенную таким правилом:
https:\/\/i\.ibb\.co\/.*\/.*\.png
Не волнуйтесь, сейчас все объясню.
" \ " - это экранирующий символ. Он говорит, что символ, следующий за ним, нужно трактовать буквально. Он стоит перед спец-символами, которые в языке регулярных выражений задают другие правила.
" . " - обозначает любой символ, если не экранированно.
" * " - модификатор, говорящий, что предыдущий за ним символ может повториться сколько угодно раз.
Таким образом, это регулярное выражение как раз формально определяет правило для подстроки вида httрs://i.ibb.co/{сколько-то произвольных символов}/{сколько-то произвольных цифр}.png Теперь, с помощью библиотеки re мы можем встроить это в код и искать требуемую подстроку. Можно сильно ускорить поиск, если учесть, что эта строка всегда содержится в теге <img>.
Если с регулярными выражениями не получилось разобраться, то вот вам практическое задание: в этой статье спрятаны куски кода - 8 символов + символ '-' в конце. Чтобы у вас не получилось сжульничать и найти все вручную, одну часть я оставил у себя. Присылайте комментарии с регулярным выражением, с помощью которого нашли части кода, и первому, приславшему правильное рабочее решение, я в ЛС отправлю последнюю часть кода.
Результат Весь описанный в статье алгоритм на питоне занимает меньше 50 строчек, и написал я его раза в два быстрее, чем эту статью. Для удобства я добавил промежуточный вывод и звуковой сигнал при нахождении подарка. Я запускал код с ограничениями на число страниц в каждой теме, и мне удалось найти таким способом 5 подарков - на один больше, чем пользователю Dinze, из-за чего ему не досталось прозвище археолог( Меня же устраивает прозвище "Эксперт", поэтому я оставил его (еще бы, это первое пользовательское прозвище!) Для любопытных я также привожу исходный код:
# Да, ни одного комментария в коде.
# Да, мне стыдно за это
# Но это же одноразовый скрипт, кому какое дело. Все равно никто читать не будет =)
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import re
import winsound
frequency = 700
duration = 1000
winsound.Beep(frequency, duration)
present_re = re.compile(r'https://i.ibb.co/.*/.*.png')
every_present = []
for page_num in range(1, 22):
print('NINJA DIV PAGE {}'.format(page_num))
url = 'https://forum.jut.su/dninjas/page-{}/'.format(page_num)
r = requests.get(url, headers={'User-Agent': UserAgent().chrome})
soup = BeautifulSoup(r.content, "html.parser")
all_topics = soup.find_all("div", "th-item__message")
for div in all_topics:
topic_url_base = div.find("a").get('href')
topic_r = requests.get(topic_url_base, headers={'User-Agent': UserAgent().chrome})
topic_soup = BeautifulSoup(topic_r.content, "html.parser")
topic_name = div.find('span', {'itemprop': 'name'}).text
print('TOPIC {}'.format(topic_name))
try:
topic_page_nums = int(topic_soup.find("a", "pagination__link pagination__last").text)
except Exception as e:
topic_page_nums = 1
topic_url_base = str(div.find("a").get('href').replace('.html', ''))
for topic_page_num in range(1, topic_page_nums+1):
print('PAGE {}'.format(topic_page_num))
topic_url = topic_url_base + '-page-{}.html'.format(topic_page_num)
topic_r = requests.get(topic_url, headers={'User-Agent': UserAgent().chrome})
topic_soup = BeautifulSoup(topic_r.content, "html.parser")
found_present = topic_soup.find_all('img', src=present_re) # sWOP3MZE-
if found_present:
print('FOUND IT:: {}'.format(topic_url))
every_present.append(topic_url)
winsound.Beep(frequency, duration)
print('____________________________')
for present in every_present:
print(present)
winsound.Beep(frequency, duration)
К тем, кто полностью прочитал статью, или хотя бы промотал до сюда. Для меня написание подобной статьи - первый подобный опыт, поэтому я хотел бы получить какой-нибудь фидбек. Как вам статья? Может было слишком сухо? Или наоборот, чересчур много воды? Может, какие-то моменты недостаточно подробно раскрыл? Буду рад любой критике. Спасибо
Lait. (12.12.2019, 22:32) писал:Не так давно прошел конкурс Археолог: поиск подарков. Это уже второй официальный конкурс, в котором я побеждаю. Но на этот раз, вместо розыгрыша призов, я решил рассказать вам о методе, благодаря которому я победил.
Что нужно сделать? В подфоруме Ниндзя, в различных случайных темах Shurado отредактировал сообщения пользователей так, что в них появился подарок - картинка, похожая на эту:
Всего таких подарков спрятано 20. За каждый выдается награда, а тем, кто нашел больше всего, выдается прозвище "Археолог" и пять звезд активности.
И как искать? Простые смертные вынуждены вручную перебирать каждую страницу каждой темы, пока не найдут заветный подарок. Также Shurado предложил писать ему в личные сообщения, с просьбой о подсказке. Естественно, за небольшую плату. Для тех же, кто хоть немного знаком с программированием, открываются куда более широкие возможности. И если вам было абсолютно нечего делать вечером, вы могли потратить час на написание скрипта, который позволит в таком конкурсе победить. Как вы уже догадались, мне было нечего делать.
Используемые инструменты Python (у меня - 3.7) Библиотеки: requests bs4 fake-useragent re
Описание алгоритма К участию в конкурсе я присоединился довольно поздно, уже было найдено около семи подарков. Но благодаря тому, что часть подарков уже нашли за меня, я смог придумать этот алгоритм. Я заметил, что все подарки - это однотипные картинки. Раз так, то давайте взглянем на картинку чуть более подробно. А именно, нас интересует ее URL. Для приведенного на этой странице подарка: httрs://i.ibb.co/w6TSv6z/1.png, или, в более общем виде: httрs://i.ibb.co/{сколько-то произвольных символов}/{сколько-то произвольных цифр}.png Мы можем искать такую подстроку строку в html-коде страницы. Все, что осталось - получить этот html-код
К счастью, эта задача возникает довольно часто, и добрые люди уже давно написали для этого библиотеку: requests Можете запустить интерпретатор и исполнить следующий код:
Теперь переменная r - это Response object, содержащий информацию о запрошенной странице. Так, например r.content позволит получить тот заветный html-код страницы, а r.status_code - код ответа (например, 404, если страница не была найдена) Посмотрим на код ответа в нашем случае:
>>>print(r.status_code)
403
403 - код ответа "Запрещено", т.е. сайт понял, что мы пытаемся сделать автоматизированный запрос. Один из самых простых способов обойти это - сделать запрос более "человечным", т.е. передать правдоподобные headers (заголовки, мета-информация, которую браузер автоматически посылает вместе с запросом. В нашем коде никакого браузера не использовалось, поэтому заголовки подставлялись по-умолчанию) Для этого мы используем еще одну библиотеку: fake-useragent. Мы создадим правдоподобные заголовки и передадим их в метод get вместе с ссылкой на желаемую страницу
200 - код "ОК", т.е. теперь в r.content лежит нужный нам html-код (не буду его приводить, слишком много =) Можете сами посмотреть: ctrl+shift+j ). В этом коде мы можем найти подстроку, "похожую" на url подарка, и задача решена.
Но это все еще далеко от автоматического поиска: сейчас нам нужно вручную перебирать все темы, подавать их на вход программе. Не страшно, исправим. Как? Очередная библиотека, конечно же: bs4 (BeautifulSoup, прекрасный суп =) ). Она позволяет превратить html-код в дерево тегов. Звучит сложно, но использовать эту библиотеку очень легко, достаточно бегло взглянуть на документацию По сути, она сильно облегчит нам автоматический разбор html-кода.
Алгоритмично опишу, как мы автоматизируем перебор тем:
Каждая тема лежит в элементе <div> с атрибутом class="th-item__message"
В этом элементе найдем тэг <a> с атрибутом itemprop="url" и получим значение его атрибута href - это ссылка на тему
Ищем в этой теме подарок
Когда мы перебрали все темы на этой странице, переходим на следующую. Для этого к ссылке на первую страницу подставляем "/page-{номер страницы}"
Повторяем все на следующей, пока не переберем 21 страницу
Регулярные выражения Подробнее обратим внимание на пункт с поиском подарка. Во-первых, в теме с подарком может быть несколько страниц, поэтому придется реализовать алгоритм, похожий на перебор тем. Во-вторых, мы хотим искать картинку с "похожим url". Но что это значит? Здесь нам на помощь приходят регулярные выражения. Это специальный язык, позволяющий задать правило для поиска подстроки, т.е. формализировать понятие "похожий". Мы будем искать строку, определенную таким правилом:
https:\/\/i\.ibb\.co\/.*\/.*\.png
Не волнуйтесь, сейчас все объясню.
" \ " - это экранирующий символ. Он говорит, что символ, следующий за ним, нужно трактовать буквально. Он стоит перед спец-символами, которые в языке регулярных выражений задают другие правила.
" . " - обозначает любой символ, если не экранированно.
" * " - модификатор, говорящий, что предыдущий за ним символ может повториться сколько угодно раз.
Таким образом, это регулярное выражение как раз формально определяет правило для подстроки вида httрs://i.ibb.co/{сколько-то произвольных символов}/{сколько-то произвольных цифр}.png Теперь, с помощью библиотеки re мы можем встроить это в код и искать требуемую подстроку. Можно сильно ускорить поиск, если учесть, что эта строка всегда содержится в теге <img>.
Если с регулярными выражениями не получилось разобраться, то вот вам практическое задание: в этой статье спрятаны куски кода - 8 символов + символ '-' в конце. Чтобы у вас не получилось сжульничать и найти все вручную, одну часть я оставил у себя. Присылайте комментарии с регулярным выражением, с помощью которого нашли части кода, и первому, приславшему правильное рабочее решение, я в ЛС отправлю последнюю часть кода.
Результат Весь описанный в статье алгоритм на питоне занимает меньше 50 строчек, и написал я его раза в два быстрее, чем эту статью. Для удобства я добавил промежуточный вывод и звуковой сигнал при нахождении подарка. Я запускал код с ограничениями на число страниц в каждой теме, и мне удалось найти таким способом 5 подарков - на один больше, чем пользователю Dinze, из-за чего ему не досталось прозвище археолог( Меня же устраивает прозвище "Эксперт", поэтому я оставил его (еще бы, это первое пользовательское прозвище!) Для любопытных я также привожу исходный код:
# Да, ни одного комментария в коде.
# Да, мне стыдно за это
# Но это же одноразовый скрипт, кому какое дело. Все равно никто читать не будет =)
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import re
import winsound
frequency = 700
duration = 1000
winsound.Beep(frequency, duration)
present_re = re.compile(r'https://i.ibb.co/.*/.*.png')
every_present = []
for page_num in range(1, 22):
print('NINJA DIV PAGE {}'.format(page_num))
url = 'https://forum.jut.su/dninjas/page-{}/'.format(page_num)
r = requests.get(url, headers={'User-Agent': UserAgent().chrome})
soup = BeautifulSoup(r.content, "html.parser")
all_topics = soup.find_all("div", "th-item__message")
for div in all_topics:
topic_url_base = div.find("a").get('href')
topic_r = requests.get(topic_url_base, headers={'User-Agent': UserAgent().chrome})
topic_soup = BeautifulSoup(topic_r.content, "html.parser")
topic_name = div.find('span', {'itemprop': 'name'}).text
print('TOPIC {}'.format(topic_name))
try:
topic_page_nums = int(topic_soup.find("a", "pagination__link pagination__last").text)
except Exception as e:
topic_page_nums = 1
topic_url_base = str(div.find("a").get('href').replace('.html', ''))
for topic_page_num in range(1, topic_page_nums+1):
print('PAGE {}'.format(topic_page_num))
topic_url = topic_url_base + '-page-{}.html'.format(topic_page_num)
topic_r = requests.get(topic_url, headers={'User-Agent': UserAgent().chrome})
topic_soup = BeautifulSoup(topic_r.content, "html.parser")
found_present = topic_soup.find_all('img', src=present_re) # sWOP3MZE-
if found_present:
print('FOUND IT:: {}'.format(topic_url))
every_present.append(topic_url)
winsound.Beep(frequency, duration)
print('____________________________')
for present in every_present:
print(present)
winsound.Beep(frequency, duration)
К тем, кто полностью прочитал статью, или хотя бы промотал до сюда. Для меня написание подобной статьи - первый подобный опыт, поэтому я хотел бы получить какой-нибудь фидбек. Как вам статья? Может было слишком сухо? Или наоборот, чересчур много воды? Может, какие-то моменты недостаточно подробно раскрыл? Буду рад любой критике. Спасибо
Просто напомню В статье спрятано 3/4 кода, для изучения техник Чтобы получить четвертую часть, нужно написать регулярное выражение, которое ищет эти три части. Вот источники, которые помогут разобраться с регулярными выражениями: regex101.com Викиучебник Статья на хабре Каждая часть кода представляет из себя 8 символов + символ '-' в конце Если до 18:00 16.12.2019 никто не напишет такую регулярку, отдам код тому, кто просто скинет первые его три части в эту тему
Мне кажется, что был бы лучше разбор для конкретных чайников, объясняя, что такое библиотеки, что вообще нужно делать и куда вообще заходить. Питон что такое вообще понятия не имею. Библиотеки тоже хз, что за, кроме как не место для чтения. Нужно ли это всё скачивать? Если да, то где. Куча вопросов возникает, когда ты вообще понятия не имеешь, что нужно делать и куда заходить
А ты оказывается Питон знаешь.Знаю я еще одного програмиста, ток он сайты пишет, но думаю если понабрать, то может и получится хорошая команда разрабов, вот только потом будет делешка, и вряд ли он на такое пойдет.