Сломался старый скрипт поиска видео на youtube по запросу
Был у меня такой скрипт для поиска на ютубе (использовал в телеграм боте):
import urllib
import re
import random
link = urllib.parse.urlencode({"search_query": msg})
content = urllib.request.urlopen("https://www.youtube.com/results?" + link)
search_results = re.findall('href=\"\/watch\?v=(.*?)\"', content.read().decode())
if len(search_results)>0:
search_results = search_results[0:9:1]
choice_f = random.choice(search_results)
yt_link = "https://www.youtube.com/watch?v="+choice_f
Он был не аккуратный, работал через регулярки, и вообще, был не моим :)
А сейчас он ничего не возвращает и хотелось бы разобраться что поменялось и как починить его.
Ответы (1 шт):
Если коротко (хочется подробнее, посмотри ниже), то раньше скрипт работал потому-что результат поиска возвращался в ответе на запрос, в HTML.
Сейчас же результат поиска будет внутри javascript, его нужно извлечь и обработать.
import json
from typing import List, Tuple
import re
# pip install dpath
import dpath.util
import requests
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0'
PATTERN = re.compile(r'window\["ytInitialData"\] = (\{.+?\});')
def search_youtube(text: str) -> List[Tuple[str, str]]:
url = f'https://www.youtube.com/results?search_query={text}'
items = []
rs = requests.get(url, headers={'User-Agent': USER_AGENT})
m = PATTERN.search(rs.text)
if not m:
return items
data_str = m.group(1)
data = json.loads(data_str)
result = dpath.util.values(data, '**/videoRenderer')
for video in result:
if 'videoId' not in video:
continue
url = 'https://www.youtube.com/watch?v=' + video['videoId']
title = dpath.util.get(video, 'title/runs/0/text')
items.append((url, title))
return items
Теперь подробнее
Давайте попробуем разобраться с парсингом с самого начала. Внизу будет много скриншотов!
Для начала изучаем механизм поиска:
Получается, для поиска нужно оформить URL в таким виде:
https://www.youtube.com/results?search_query={text}
Тут ничего не поменялось.
Изучаем структуру через инспектор браузера
Тыкаем на правую кнопку мышки и выбираем Inspect Element:
Откроется панель такого вида:
Изучаем и ищем ссылку на видео:
Попробуем попарсить:
Например, поищем теги видео, например ytd-video-renderer:
from bs4 import BeautifulSoup
import requests
rs = requests.get('https://www.youtube.com/results?search_query=slipknot')
root = BeautifulSoup(rs.content, 'html.parser')
print(len(root.select('ytd-video-renderer')))
# 0
Ничего...
А если поискать ссылку, увиденную в инспекторе?
import requests
rs = requests.get('https://www.youtube.com/results?search_query=slipknot')
print(rs.text.count('/watch?v=5abamRO41fE'))
# 4
Интересно... Получается ссылка есть, но она не в том теге.
Посмотрим, что приходит в ответ при запросе поиска:
- Можно через браузер, через правой кнопкой на мышки и выбрать View Page Source
- Или скриптом:
import requests rs = requests.get('https://www.youtube.com/results?search_query=slipknot') with open('rs.html', 'wb') as f: f.write(rs.content)
Тег ytd-video-renderer не найден...
Нашли ytd-video-renderer, но это в javascript
А что насчет ссылки? Тоже в javascript (у меня сломался поиск в Page Source, поэтому показываю в Notepad++):

Получается, данные с поиском находятся в объекте, находящимся в window["ytInitialData"]:
Изучим window["ytInitialData"]
Как видим из структуры, видео находятся в объектах videoRenderer
Теперь попробуем вытащить значение из window["ytInitialData"] и обработать его.
Для вытаскивания используем регулярное выражение, а для парсинга модуль json.
Как видим, данные успешно вытаскиваются, парсятся и обрабатываются:
import json
import re
import requests
PATTERN = re.compile(r'window\["ytInitialData"\] = (\{.+?\});')
rs = requests.get('https://www.youtube.com/results?search_query=slipknot')
m = re.search(r'window\["ytInitialData"\] = (\{.+?\});', rs.text)
data_str = m.group(1)
print(data_str)
# {"responseContext":{"serviceTrackingParams":[{"service":"GUIDED_HELP","params":[{"key":"context","va ...
data = json.loads(data_str)
print(data['contents']['twoColumnSearchResultsRenderer']['primaryContents']['sectionListRenderer']['contents'][0]['itemSectionRenderer']['contents'][2]['videoRenderer'])
# {'videoId': '5abamRO41fE', 'thumbnail': {'thumbnails': [{'url': ...
Для удобства работы с словарем, я воспользуюсь модулем dpath.util, например так можно вытащить значения всех объектов с ключом videoRenderer:
import dpath.util
...
result = dpath.util.values(data, '**/videoRenderer')
print(len(result))
# 39








