Вопрос: Медленный парсинг на Python с BS4?

Лучший ответ: Задачи, связанные со скраппингом, относятся одновременно и к IO-bound и к CPU-bound (как вам подробно рассказали в предыдущем ответе). С одной стороны это порождает множество проблем для новичков, с другой - знание этих особенностей помогает с архитектурой - вам просто не остаётся выбора :)
Давайте теперь разберём подробно ваш вопрос.IO-bound
Всё что использует множественный ввод-вывод, должно быть асинхронным, кроме тех случаев, когда абсолютно плевать на время выполнения, а это не ваш случай :) Я думаю, причины этого очевидны - любые внешние  сетевые задержки вам неподконтрольны (да и внутренние не очень) - запрос на страницу сайта может занять как несколько миллисекунд, так и минуту (это редкость, но несколько секунд - вполне), то есть разница достигает 4-5 порядков! В случае синхронного однопоточного кода львиная доля работы вашего приложения - ожидание, что, согласитесь, обидно. Пример кода вам привёл Roman Kitaev - необязательно ориентироваться конкретно на эту реализацию - в сети очень много примеров современного питонячего асинхронного кода, скачивающего странички.CPU-bound
Здесь ещё проще - вам нужно загрузить равномерно все ядра, попутно, если мы говорим про Python, нивелировать влияние GIL. Обычно это решается созданием нескольких потоков - по числу ядер и очередью (или несколькими), из которой потоки берут данные для обработки.Память
Малое потребление памяти - признак хорошей архитектуры кода.
Обычные веб-страницы весят немного - сотни килобайт - и редко доставляют проблемы, но бывают и задачи обработки очень объёмных XML/HTML документов - десятки и сотни гигабайт. Но даже документ "весом" под сотню мегабайт может принести много радости. В этом случае прибегаю к помощи потоковых парсеров - они работают с небольшим буфером, вызывая обработчики для нужного контента. Опять же - не ваш случай, что хорошо )Медленный Python
О, как часто можно это услышать и прочитать!
К сожалению, эта легенда легко получает подтверждение у новичков, плохо представляющих себе экосистему питона и плохо понимая его внутреннее устройство, хотя эти знания легко приобретаются через пару месяцев использования языка. Отличительный признак подобных критиков - отсутствие внятной аргументации и советы типа: "пишите на нормальном языке! а нормальный это - [%random_lang%]". В этом треде, как видите, отличился DarthWazer.
Да, имеется достаточно задач, с которыми "чистый" питон справляется очень медленно, но для таких случаев всегда найдётся "батарейка-обёртка" для библиотеки, написанной на C и никаких особых тормозов вы не заметите.
Python - это язык-клей, позволяющий быстро, легко и элегантно соединить несколько низкоуровневых инструментов и получить отличный результат.
В качестве эксперимента давайте сравним Python и Rust ( DarthWazer, это достаточно быстрый язык или только шарп надо использовать?)
Сначала сохраним главную страницу в файл "wiki_front.html" - скачивание страниц сравнивать бессмысленно.PythonСоздаём окружение и устанавливаем lxml:pipenv install lxml --python=3.8Пишем код: main.pyfrom lxml.html import parse
import time


if __name__ == '__main__':
    with open('wiki_front.html', 'r') as contents:
        begin = time.monotonic()
        doc = parse(contents)
        links = doc.xpath("//a")
        time_total = time.monotonic() - begin
        print(f'Links counts: {len(links)}, time: {time_total:.9} sec')Запускаем: pipenv run ./main.py
Links counts: 333, time: 0.00371371489 secЗапоминаем результат: 0.00371 сек. Ваш результат, конечно, будет отличаться, это не критично, важно сравнение.Ок, переходим к RustСоздаём проект:cargo initДобавляем в зависимости scraper: Cargo.toml
.....
[dependencies]
scraper = "*"Пишем код: src/main.rsuse std::fs;
use std::time::Instant;

use scraper::{Html, Selector};


fn main() {

    let contents = fs::read_to_string("wiki_front.html")
        .expect("Something went wrong reading the file");

    let begin = Instant::now();
    let document = Html::parse_document(&contents);
    let selector = Selector::parse("a").unwrap();
    let links_count = document.select(&selector).count();

    println!("Links counts: {}, time: {} sec",
             links_count, begin.elapsed().as_secs_f32());

}Запускаем: cargo run --release
Links counts: 333, time: 0.002836701 secСравниваем результаты: разница - 0.8 милисекунд.Вывод: Rust тоже медленный, пишите свои скраберы на шарпе.Что же делать?
Ну и, наконец, ответ на ваш вопрос :)
Примерно год назад я задавал вопрос по вашей теме - Какой выбрать Python фреймворк для системы парсинга сайтов?
Можно использовать один из упомянутых фрейворков. Или написать свой, если достаточно компетенций.

Худший ответ: Сайт, может и позволять.
А твое железо может потянуть столько?
А канал позволяет столько передавать данных в секунду?
А как ты парсишь, синхронно, асинхронно или используешь потоки?
А ответ от сайта быстро приходит??