Первая — IP-адрес. Если запускать почтовый сервер дома с домашним интернет-провайдером, то некоторые почтовые серверы отклоняют все письма с ошибкой в стиле "у вас домашний ISP, с него нельзя отправлять почту" — даже если есть корректные SPF и DKIM. Я уже пытался.
Вторая — домен, к которому привязана почта. Допустим, я впадаю в кому и лежу в больнице три месяца, на карточке денег нет и автопродление домена не срабатывает, домен становится доступен для покупки и его кто-то покупает. Хорошо, если покупает киберсквоттер. Плохо, если покупает какой-нибудь мой злейший враг, который, получив доступ к домену моей почты, начинает получать доступ ко всем аккаунтам, которые привязаны к моей почте и на которых нет двухфакторки. Когда я наконец выйду из комы — как спасаться?
Какова мотивация тех, кто меняет id с интов на строки? Им становится мало приключений в жизни и однажды они решают угробить свою собственную БД и усложнить жизнь всем партнёрам?
Но всё-таки — если внешние id представимы в виде инта, значит их можно хранить в виде инта (как минимум в БД, ограничения браузеров это совсем другая история)
Всё ещё интересно посмотреть на примеры перехода на буквы
Ну, одно дело хранить, а другое дело работать. Можно в базе хранить инты, а в браузер отдавать строки. Я в одном своём проекте использую 64-битные snowflake-числа в качестве первичных ключей в БД, а наружу выставляю строго строки, содержащие base58-представление числа, таким образом косплея идентификаторы из одной запрещённой соцсети
хорошо бы обрабатывать ответы с кодом отличным от 200. Добавляем проверку, строчку будет лучше поместить в функцию.
Во-первых, зачем ради одной самоочевидной строки заводить целую функцию? Во-вторых, в requests и других подобных библиотеках уже есть встроенная функция raise_for_status, которая выбрасывает исключение при статусах 4xx и 5xx
return False
Пхпшники покусали? Во-первых, выбрасывать исключения при ошибках в питоне абсолютно нормально, та же raise_for_status так делает. Во-вторых, если есть аллергия на исключения, то можно хотя бы None вернуть, это всё ещё менее странно чем False
Логгирование, контроль исполнения и отладка
Всю эту информацию можно достать напрямую из объекта response или из возникшего исключения, функция response_res в показанном здесь виде не имеет никакого практического смысла. Кроме того, response_res ломает статическую типизацию, потому что заменяет объекты с аннотациями типов на нетипизированный словарь — для производства и будущей поддержки кода это ОЧЕНЬ плохо
автоматический сбор данных не очень приветствуется, даже в таком вроде бы легальном поле как получение рсс-ленты.
Бред, RSS по сути своей предназначен именно для автоматизированного сбора данных
словит капчу на второй-третий раз
Капча на RSS — опять бред
fake_useragent.UserAgent().random
И здесь сразу два подозрительных момента. Во-первых, клиент, меняющий свой юзер-агент при каждом запросе — это в принципе странно, а во-вторых, браузеры не умеют читать RSS (за редким исключением в виде браузерных расширений и прочей мелочи) и значит браузерного юзер-агента здесь быть в принципе не должно
не хорошо слать запросы с пустыми cookies, referer и так далее
Ничего из перечисленного быть в принципе не должно, это же RSS
except:
… и получаем невозможность прервать работу программы из-за перехваченных KeyboardInterrupt и SystemExit. К счастью, в следующем примере кода это уже исправлено, хоть и без пояснений
а вот с исключением возникает, вопрос, как его правильно обработать.
Точно не подменой на фейковый Response. Опять аллергия на исключения?
Для одновременных запросов многопоточность вообще хороша
Для одновременных запросов многопоточность вообще не подходит, потому что наплодит кучу жрущих ресурсы потоков, которые при этом будут простаивать без дела в ожидании ответа от сервера. Для io-bound задач специально изобрели асинхронность, которая замечательно работает в одном потоке
logging.info(f"многопоточность сломалась {ex}")
info для критических ошибок, скрытие информации о стеке, скрытие информации о типе ошибки, ужас… Есть же специальный logging.exception для такого
Если запустить программу в режиме планировщика (например каждые 30 секунд)
Нормальный планировщик должен дожидаться, когда предыдущий запуск завершится
Как выглядит итоговый код
feedparser.parse зачем-то вызывается два раза, пустая трата процессора. Причём во второй раз не перехватываются возможные исключения (а вдруг внутри feedparser баги есть)
Ещё feedparser сам по себе плох тем, что возвращает результат парсинга в виде нетипизированного словаря, но так как я не в курсе, есть ли достойные альтернативы, то ладно, пусть пока будет
Итого: меняем многопоточность на асинхронность, избавляемся от аллергии на исключения, возвращаем нормальные объекты вместо нетипизированного словаря, выкидываем бесполезный юзер-агент — и получаем гораздо более адекватный и при этом более компактный код:
Как на самом деле выглядит итоговый код
import asyncio
import logging
import aiohttp
import feedparser
async def harvest_all(url: str) -> None:
try:
timeout = aiohttp.ClientTimeout(total=10)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as response:
response.raise_for_status()
raw_feed = await response.text()
except Exception:
logging.exception("Failed to get feed %s", url)
return
try:
feed = feedparser.parse(raw_feed)
process_feed(url, feed)
except Exception:
logging.exception("Failed to process feed %s", url)
def process_feed(url: str, feed: feedparser.FeedParserDict) -> None:
if not feed["entries"]:
logging.info("Feed %s has no entries", url)
return
for entry in feed["entries"]:
logging.info("%s", entry["title"])
async def main() -> None:
logging.basicConfig(level=logging.INFO)
rss_list = [
"https://lenta.ru/rss",
"https://habr.com/ru/rss/all/",
]
while True:
tasks = {asyncio.create_task(harvest_all(url)) for url in rss_list}
await asyncio.wait(tasks)
await asyncio.sleep(60)
if __name__ == "__main__":
asyncio.run(main())
Не понимаю, почему в этой ветке до сих пор не упомянули это А нет, упомянули, но так мелко, что я не заметил. Но раз уж я уже отправил комментарий, то:
input:invalid {
border: 1px solid red;
}
Текст ошибки конечно не выведет, но по крайней мере это даст юзеру возможность увидеть красную рамочку вокруг ошибочного инпута сразу в момент ввода
Очевидно, датаклассы. Когда счёт объектов идёт на тысячи — уже начинает быть заметно, что манипуляции с датаклассами в несколько раз медленнее манипуляций с кортежами. А ещё вы не использовали slots, на чём потеряли ещё несколько процентов производительности
UPD: а впрочем, есть же typing.NamedTuple. Если задача не требует мутабельных объектов, то, наверно, можно попробовать позаменять датаклассы на NamedTuple, совместив таким образом удобство датаклассов и скорость кортежей. А если мутабельность всё-таки нужна, то там же рядышком есть typing.TypedDict — в рантайме обычный dict, немножко медленнее чем кортежи, но всё ещё быстрее датаклассов
1) От одного пользователя запускаем ровно один сервис, и проблемы нет
2) Не получает, кто ж его к ним пустит-то? Фраза «чисто локальные» подразумевает, что они будут слушать Unix-сокет, и, во-первых, взломщик в принципе не сможет узнать, где эти Unix-сокеты вообще лежат, а во-вторых, даже если узнает или догадается — система отлупит его с Permission denied, потому что пользователь не тот. Даже файрвол не нужен, всё делается банальными chown/chmod
В первом варианте pattern2.match не будет вызываться лишний раз. Во втором варианте он будет вызываться всегда, что потенциально может замедлить программу (иногда сильно)
Во-первых, распарсить xml и html может любой школьник за полчаса, это не какая-то сверхсложная наука.
Во-вторых, я не очень понял, почему вы вообще решили, что Stackoverflow будет что-то за кого-то парсить? В том бесплатном API, который я нашёл, отдаётся тот же самый html. Вы где-то вычитали, что платный API будет чем-то принципиально отличаться, или о чём речь?
Если почтовый сервер отклоняет почту на основании «неправильного» провайдера, то обратная зона вряд ли поможет
Криптой, что ли? Я наоборот был вынужден в марте-апреле 2022-го перенести всё своё из-за границы обратно в РФ из-за невозможности оплачивать
В общем случае N может оказаться недостаточно, вдруг меня посадят в тюрьму на N+1 лет 🤔
Со своей почтой есть две огромные проблемы.
Первая — IP-адрес. Если запускать почтовый сервер дома с домашним интернет-провайдером, то некоторые почтовые серверы отклоняют все письма с ошибкой в стиле "у вас домашний ISP, с него нельзя отправлять почту" — даже если есть корректные SPF и DKIM. Я уже пытался.
Вторая — домен, к которому привязана почта. Допустим, я впадаю в кому и лежу в больнице три месяца, на карточке денег нет и автопродление домена не срабатывает, домен становится доступен для покупки и его кто-то покупает. Хорошо, если покупает киберсквоттер. Плохо, если покупает какой-нибудь мой злейший враг, который, получив доступ к домену моей почты, начинает получать доступ ко всем аккаунтам, которые привязаны к моей почте и на которых нет двухфакторки. Когда я наконец выйду из комы — как спасаться?
Могу лишь ещё раз повторить:
Да
Мне опять нужно повторять, что это 128-битное число?
Зачем это делать именно в id, а не в каком-нибудь дополнительном поле типа slug?
Какова мотивация тех, кто меняет id с интов на строки? Им становится мало приключений в жизни и однажды они решают угробить свою собственную БД и усложнить жизнь всем партнёрам?
"на голове стоять" это хранить id в виде строк ¯\_(ツ)_/¯
Но всё-таки — если внешние id представимы в виде инта, значит их можно хранить в виде инта (как минимум в БД, ограничения браузеров это совсем другая история)
Всё ещё интересно посмотреть на примеры перехода на буквы
Ну, одно дело хранить, а другое дело работать. Можно в базе хранить инты, а в браузер отдавать строки. Я в одном своём проекте использую 64-битные snowflake-числа в качестве первичных ключей в БД, а наружу выставляю строго строки, содержащие base58-представление числа, таким образом косплея идентификаторы из одной запрещённой соцсети
guid это 128-битное число, буквы это всего лишь один из способов его представления
Есть какие-нибудь более реальные примеры перехода на буквы?
Во-первых, зачем ради одной самоочевидной строки заводить целую функцию? Во-вторых, в requests и других подобных библиотеках уже есть встроенная функция
raise_for_status
, которая выбрасывает исключение при статусах 4xx и 5xxПхпшники покусали? Во-первых, выбрасывать исключения при ошибках в питоне абсолютно нормально, та же
raise_for_status
так делает. Во-вторых, если есть аллергия на исключения, то можно хотя бы None вернуть, это всё ещё менее странно чем FalseВсю эту информацию можно достать напрямую из объекта response или из возникшего исключения, функция
response_res
в показанном здесь виде не имеет никакого практического смысла. Кроме того,response_res
ломает статическую типизацию, потому что заменяет объекты с аннотациями типов на нетипизированный словарь — для производства и будущей поддержки кода это ОЧЕНЬ плохоБред, RSS по сути своей предназначен именно для автоматизированного сбора данных
Капча на RSS — опять бред
И здесь сразу два подозрительных момента. Во-первых, клиент, меняющий свой юзер-агент при каждом запросе — это в принципе странно, а во-вторых, браузеры не умеют читать RSS (за редким исключением в виде браузерных расширений и прочей мелочи) и значит браузерного юзер-агента здесь быть в принципе не должно
Ничего из перечисленного быть в принципе не должно, это же RSS
… и получаем невозможность прервать работу программы из-за перехваченных KeyboardInterrupt и SystemExit. К счастью, в следующем примере кода это уже исправлено, хоть и без пояснений
Точно не подменой на фейковый Response. Опять аллергия на исключения?
Для одновременных запросов многопоточность вообще не подходит, потому что наплодит кучу жрущих ресурсы потоков, которые при этом будут простаивать без дела в ожидании ответа от сервера. Для io-bound задач специально изобрели асинхронность, которая замечательно работает в одном потоке
info для критических ошибок, скрытие информации о стеке, скрытие информации о типе ошибки, ужас… Есть же специальный
logging.exception
для такогоНормальный планировщик должен дожидаться, когда предыдущий запуск завершится
feedparser.parse
зачем-то вызывается два раза, пустая трата процессора. Причём во второй раз не перехватываются возможные исключения (а вдруг внутри feedparser баги есть)Ещё feedparser сам по себе плох тем, что возвращает результат парсинга в виде нетипизированного словаря, но так как я не в курсе, есть ли достойные альтернативы, то ладно, пусть пока будет
Итого: меняем многопоточность на асинхронность, избавляемся от аллергии на исключения, возвращаем нормальные объекты вместо нетипизированного словаря, выкидываем бесполезный юзер-агент — и получаем гораздо более адекватный и при этом более компактный код:
Чем это принципиально лучше какого-нибудь KeePass?
Тем не менее это полностью удовлетворяет поставленному вами условию «сразу показывая клиенту неверный ввод, до отправки формы без JS»
Не понимаю, почему в этой ветке до сих пор не упомянули этоА нет, упомянули, но так мелко, что я не заметил. Но раз уж я уже отправил комментарий, то:Текст ошибки конечно не выведет, но по крайней мере это даст юзеру возможность увидеть красную рамочку вокруг ошибочного инпута сразу в момент ввода
Заманиваем пользователя на свой сайт, а на своём сайте пихаем:
Браузер, отправляя такую форму, без вопросов отошлёт все куки, отправка которых не запрещена через SameSite
Очевидно, датаклассы. Когда счёт объектов идёт на тысячи — уже начинает быть заметно, что манипуляции с датаклассами в несколько раз медленнее манипуляций с кортежами. А ещё вы не использовали slots, на чём потеряли ещё несколько процентов производительности
UPD: а впрочем, есть же typing.NamedTuple. Если задача не требует мутабельных объектов, то, наверно, можно попробовать позаменять датаклассы на NamedTuple, совместив таким образом удобство датаклассов и скорость кортежей. А если мутабельность всё-таки нужна, то там же рядышком есть typing.TypedDict — в рантайме обычный dict, немножко медленнее чем кортежи, но всё ещё быстрее датаклассов
1) От одного пользователя запускаем ровно один сервис, и проблемы нет
2) Не получает, кто ж его к ним пустит-то? Фраза «чисто локальные» подразумевает, что они будут слушать Unix-сокет, и, во-первых, взломщик в принципе не сможет узнать, где эти Unix-сокеты вообще лежат, а во-вторых, даже если узнает или догадается — система отлупит его с Permission denied, потому что пользователь не тот. Даже файрвол не нужен, всё делается банальными chown/chmod
Чем это принципиально отличается от банального запуска разных сервисов под разными юзерами?
В первом варианте pattern2.match не будет вызываться лишний раз. Во втором варианте он будет вызываться всегда, что потенциально может замедлить программу (иногда сильно)
Во-первых, распарсить xml и html может любой школьник за полчаса, это не какая-то сверхсложная наука.
Во-вторых, я не очень понял, почему вы вообще решили, что Stackoverflow будет что-то за кого-то парсить? В том бесплатном API, который я нашёл, отдаётся тот же самый html. Вы где-то вычитали, что платный API будет чем-то принципиально отличаться, или о чём речь?