Вот отсюда и растёт. Раз кодировка не задана, её угадывают за вас, и каждая библиотека угадывает по-своему.
requests берёт charset из заголовка Content-Type. Заголовка нет, и для text/* ставится latin-1 по старому правилу. Дальше вы получаете строку с мусором ещё до разбора.
r = requests.get(url)
print(r.headers.get("content-type")) # text/html, без charset
print(r.encoding) # ISO-8859-1
r.encoding = "utf-8" # ставим руками
tekst = r.text
Порядок такой, он же и порядок разбора в браузере.
Сначала заголовок ответа. Есть в Content-Type внятный charset, он и главный. Нет его, лезем в первые 1024 байта тела и ищем meta charset. Пусто и там, пробуем utf-8, при ошибке декодирования откатываемся на cp1251. Для русских сайтов эта пара покрывает почти всё.
Врут, кстати, обе стороны. Бывает заголовок с utf-8, а внутри cp1251, потому что шаблон правили одни люди, отдачу настраивали другие. Поэтому после декодирования полезно быстро глянуть на результат: считаете долю символов U+FFFD и одиночных вопросительных знаков, и если она выше пары процентов, кодировка выбрана мимо. Проверка занимает три строки и ловит ровно тот случай, когда формально всё сошлось.
И отдельно про запись. Файл открывайте с явной кодировкой, иначе Windows подставит свою системную и вы получите второй такой же проход уже на выходе. Excel по двойному щелчку читает CSV в системной кодировке, поэтому под него пишут utf-8 с меткой в начале файла.
with open("vygruzka.csv", "w", encoding="utf-8-sig", newline="") as f:
...