[PARSING] urlparss.py

Google Docs neutral 7 чанков ~5 мин чтения
#!/usr/bin/env python3<br> # -*- coding: utf-8 -*-<br> <br> import sys<br> import time<br> import re<br> import requests<br> from pathlib import Path<br> from playwright.sync_api import sync_playwright<br> from newspaper import Article<br> from youtube_transcript_api import (<br> YouTubeTranscriptApi, <br> TranscriptsDisabled, <br> NoTranscriptFound, <br> VideoUnavailable<br> )<br> from pytube import YouTube<br> from bs4 import BeautifulSoup<br> <br> # Попытка импортировать Readability (readability-lxml)<br> try:<br> from readability import Document<br> except ImportError:<br> print("Установите readability-lxml (pip install readability-lxml)")<br> sys.exit(1)<br> <br> # -----------------------------------------------------------------------------<br> # Альтернативное извлечение с помощью Readability<br> # -----------------------------------------------------------------------------<br> def extract_with_readability(html):<br> try:<br> doc = Document(html)<br> title = doc.title() # извлекает заголовок<br> summary_html = doc.summary() # извлекает HTML основного контента<br> soup = BeautifulSoup(summary_html, "html.parser")<br> text = soup.get_text(separator="\n").strip()<br> return title, text<br> except Exception as e:<br> print("Ошибка извлечения через Readability:", e)<br> return "", ""<br> <br> # -----------------------------------------------------------------------------<br> # Базовое извлечение статьи с использованием Newspaper3k и альтернативных методов<br> # -----------------------------------------------------------------------------<br> def extract_article_info(html, url):<br> # Определяем язык страницы по атрибуту <html lang="..."><br> soup = BeautifulSoup(html, "html.parser")<br> html_tag = soup.find("html")<br> language = "ru"<br> if html_tag and html_tag.has_attr("lang"):<br> lang = html_tag["lang"].lower()<br> if lang.startswith("en"):<br> language = "en"<br> elif lang.startswith("kk"):<br> language = "ru"<br> elif lang.startswith("ru"):<br> language = "ru"<br> else:<br> language = lang<br> if "reuters.com" in url:<br> language = "en"<br> <br> # Попытка извлечь данные с помощью Newspaper3k<br> article = Article(url=url, language=language)<br> article.download(input_html=html)<br> article.parse()<br> data = {<br> "title": article.title.strip() if article.title else "",<br> "authors": article.authors or [],<br> "publish_date": str(article.publish_date).strip() if article.publish_date else "",<br> "text": article.text.strip() if article.text else ""<br> }<br> # Попытка извлечь метаданные из meta-тегов<br> meta_title = soup.find("meta", property="og:title")<br> if meta_title and meta_title.get("content"):<br> meta_title_val = meta_title["content"].strip()<br> if meta_title_val.lower() not in ["reuters.com", ""]:<br> data["title"] = meta_title_val<br> meta_date = soup.find("meta", property="article:published_time")<br> if meta_date and meta_date.get("content"):<br> data["publish_date"] = meta_date["content"].strip()<br> <br> # Если для Reuters заголовок некорректный или текст слишком короткий, используем Readability<br> if ("reuters.com" in url and (not data["title"] or data["title"].strip().lower() == "reuters.com")) or len(data["text"]) < 200:<br> rb_title, rb_text = extract_with_readability(html)<br> if rb_title and rb_title.lower() != "reuters.com":<br> data["title"] = rb_title<br> if rb_text and len(rb_text) > len(data["text"]):<br> data["text"] = rb_text<br> <br> # Дополнительное: если авторы не найдены, пытаемся извлечь строку, начинающуюся с "By"<br> if not data["authors"]:<br> alt_text = soup.get_text(separator="\n")<br> m_authors = re.search(r"By\s+(.+)", alt_text, re.IGNORECASE)<br> if m_authors:<br> authors_line = m_authors.group(1).strip()<br> # Разбиваем по запятым или "and"<br> authors = re.split(r",|\band\b", authors_line)<br> data["authors"] = [a.strip() for a in authors if a.strip()]<br> return data<br> <br> def post_process_article(data):<br> # Возможное дополнительное очищение текста и авторов<br> title = data.get("title", "").strip()<br> authors = data.get("authors", [])<br> pub_date = data.get("publish_date", "").strip()<br> text = data.get("text", "")<br> match = re.search(r"(\d{2}\.\d{2}\.\d{4}),\s*автор\s+([^.]+)", text)<br> if match:<br> pub_date = match.group(1).strip()<br> authors = [match.group(2).strip()]<br> text = text.replace(match.group(0), "")<br> filtered_authors = [a for a in authors if re.search(r"[A-Za-zА-ЯЁа-яё]", a)]<br> text = re.sub(r"\b(RU|KZ|EN)\b", "", text)<br> text = re.sub(r"media@sputniknews\.com\S*", "", text)<br> text = re.sub(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}\+\d{4}", "", text)<br> text = re.sub(r"\n\s*\n+", "\n\n", text).strip()<br> return {<br> "title": title,<br> "authors": filtered_authors,<br> "publish_date": pub_date,<br> "text": text,<br> }<br> <br> # -----------------------------------------------------------------------------<br> # Функции для обработки YouTube-ссылок (без изменений)<br> # -----------------------------------------------------------------------------<br> def format_duration(seconds):<br> m, s = divmod(seconds, 60)<br> h, m = divmod(m, 60)<br> return f"{h:02d}:{m:02d}:{s:02d}"<br> <br> def get_youtube_metadata(url):<br> try:<br> yt = YouTube(url)<br> video_title = yt.title<br> channel_name = yt.author<br> video_duration = format_duration(yt.length)<br> video_description = yt.description<br> if not video_title or not channel_name:<br> raise Exception("Неполучены метаданные через pytube")<br> return video_title, channel_name, video_duration, video_description<br> except Exception as e:<br> print(f"Ошибка получения метаданных через pytube: {e}")<br> try:<br> import yt_dlp<br> except ImportError:<br> print("Модуль yt_dlp не установлен. Установите его через: pip install yt-dlp")<br> return "", "", "", ""<br> ydl_opts = {<br> "skip_download": True,<br> "quiet": True,<br> "no_warnings": True,<br> "format": "best",<br> }<br> with yt_dlp.YoutubeDL(ydl_opts) as ydl:<br> try:<br> info = ydl.extract_info(url, download=False)<br> video_title = info.get("title", "")<br> channel_name = info.get("uploader", "")<br> duration = info.get("duration", 0)<br> video_duration = format_duration(duration)<br> video_description = info.get("description", "")<br> return video_title, channel_name, video_duration, video_description<br> except Exception as e:<br> print(f"Ошибка получения метаданных через yt_dlp: {e}")<br> return "", "", "", ""<br> <br> def get_video_id(yt_url):<br> patterns = [<br> r"v=([A-Za-z0-9_\-]{5,})",<br> r"youtu\.be/([A-Za-z0-9_\-]{5,})",<br> ]<br> for pat in patterns:<br> match = re.search(pat, yt_url)<br> if match:<br> return match.group(1)<br> return None<br> <br> def fetch_subtitles(video_id, languages=("ru", "en")):<br> try:<br> transcript_list = YouTubeTranscriptApi.get_transcript(video_id, languages=list(languages))<br> except TranscriptsDisabled:<br> return "Субтитры отключены для этого видео."<br> except NoTranscriptFound:<br> return "Субтитры для данного видео не найдены."<br> except VideoUnavailable:<br> return "Видео недоступно или удалено."<br> except Exception as e:<br> return f"Ошибка при получении субтитров: {e}"<br> lines = [item["text"] for item in transcript_list]<br> full_text = "\n".join(lines)<br> return full_text<br> <br> def process_youtube_link(url):<br> video_id = get_video_id(url)<br> if not video_id:<br> print("Не удалось определить video_id из ссылки. Пропускаем.")<br> return None, None, None, None, None, None<br> video_title, channel_name, video_duration, video_description = get_youtube_metadata(url)<br> subtitles_text = fetch_subtitles(video_id)<br> subtitles_text = " ".join(subtitles_text.split())<br> return video_id, subtitles_text, video_title, channel_name, video_duration, video_description<br> <br> # -----------------------------------------------------------------------------<br> # Основная функция для обработки ссылок<br> # -----------------------------------------------------------------------------<br> # Увеличенное время ожидания после загрузки страницы до 5000 мс<br> PLAYWRIGHT_TIMEOUT = 60000 <br> WAIT_AFTER_LOAD = 5000 <br> <br> def render_with_playwright(url):<br> with sync_playwright() as p:<br> browser = p.chromium.launch(headless=True)<br> context = browser.new_context(<br> user_agent=(<br> "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "<br> "AppleWebKit/537.36 (KHTML, как Gecko) "<br> "Chrome/98.0.4758.102 Safari/537.36"<br> ),<br> ignore_https_errors=True<br> )<br> page = context.new_page()<br> page.goto(url, timeout=PLAYWRIGHT_TIMEOUT)<br> page.wait_for_load_state("domcontentloaded", timeout=PLAYWRIGHT_TIMEOUT)<br> page.wait_for_timeout(WAIT_AFTER_LOAD)<br> html = page.content()<br> browser.close()<br> return html<br> <br> def fallback_get(url):<br> r = requests.get(url, timeout=60)<br> r.raise_for_status()<br> return r.text<br> <br> def process_link(url):<br> attempts = 0<br> html = None<br> while attempts < 3 and html is None:<br> try:<br> html = render_with_playwright(url)<br> except Exception as e:<br> print(f"[Playwright] Попытка {attempts+1} не удалась: {e}")<br> attempts += 1<br> time.sleep(2)<br> if html is None:<br> print(f"[Fallback] Обращаемся к requests для {url}")<br> try:<br> html = fallback_get(url)<br> except Exception as e:<br> print(f"Ошибка и в fallback для {url}: {e}")<br> return {"title": "", "authors": [], "publish_date": "", "text": "Не удалось обработать страницу."}<br> data = extract_article_info(html, url)<br> clean_data = post_process_article(data)<br> return clean_data<br> <br> # -----------------------------------------------------------------------------<br> # Главная функция обработки ссылок<br> # -----------------------------------------------------------------------------<br> def main():<br> try:<br> with open("link.txt", "r", encoding="utf-8") as f:<br> urls = [line.strip() for line in f if line.strip()]<br> except Exception as e:<br> print(f"Ошибка чтения link.txt: {e}")<br> sys.exit(1)<br> if not urls:<br> print("Файл link.txt пуст или не содержит ссылок.")<br> sys.exit(0)<br> for i, url in enumerate(urls, start=1):<br> # Игнорирование ссылок на telegram, instagram и gov.kz<br> if any(forbidden in url for forbidden in ["telegram", "instagram", "gov.kz"]):<br> print(f"Пропускаем ссылку (запрещенный домен): {url}")<br> continue<br> print(f"=== Обработка {i}/{len(urls)}: {url}")<br> video_id = get_video_id(url)<br> if video_id:<br> result = process_youtube_link(url)<br> if result[1] is None:<br> print("Ошибка при обработке YouTube ссылки, пропускаем.")<br> continue<br> video_id, subtitles_text, video_title, channel_name, video_duration, video_description = result<br> output_filename = Path(f"result_{i}.txt")<br> with open(output_filename, "w", encoding="utf-8") as fout:<br> fout.write(f"URL: {url}\n")<br> fout.write(f"VIDEO_ID: {video_id}\n")<br> fout.write(f"Канал: {channel_name}\n")<br> fout.write(f"Название видео: {video_title}\n")<br> fout.write(f"Длительность: {video_duration}\n")<br> fout.write(f"Описание:\n{video_description}\n\n")<br> fout.write("Субтитры:\n")<br> fout.write(subtitles_text + "\n")<br> print(f" => Сохранено в {output_filename}\n")<br> else:<br> data = process_link(url)<br> output_filename = Path(f"result_{i}.txt")<br> with open(output_filename, "w", encoding="utf-8") as fout:<br> fout.write(f"URL: {url}\n")<br> fout.write(f"Заголовок: {data['title']}\n")<br> fout.write(f"Автор(ы): {data['authors']}\n")<br> fout.write(f"Дата: {data['publish_date']}\n\n")<br> fout.write(data['text'] + "\n")<br> print(f" => Сохранено в {output_filename}\n")<br> <br> if __name__ == "__main__":<br> main()